AbteeXAILabs commited on
Commit
fc2f507
·
verified ·
1 Parent(s): 8ab9393

Publish expanded LumynaX product platform package

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +29 -3
  2. SMOKE_TESTS.md +2 -0
  3. architecture.md +95 -95
  4. configs/default_policy.yaml +26 -26
  5. configs/gateway.local.json +13 -0
  6. configs/provider_aliases.yaml +30 -30
  7. configs/routing_policy.yaml +20 -20
  8. configs/service.local.json +14 -0
  9. examples/capsule.personal-sovereignty-profile.json +45 -45
  10. examples/capsule.restricted-nz-code.json +28 -28
  11. examples/opencode.marama-route.json +28 -28
  12. examples/request.allowed-local-edit.json +15 -15
  13. examples/request.code-restricted.json +14 -14
  14. examples/request.denied-training.json +15 -15
  15. examples/request.openai-chat-code.json +44 -44
  16. examples/request.personal-memory-read.json +19 -19
  17. integrations/opencode-compatible-provider.md +96 -96
  18. integrations/opencode-provider.json +28 -28
  19. marama_route/__init__.py +4 -0
  20. marama_route/_ui_server.py +8 -2
  21. marama_route/cli.py +25 -0
  22. marama_route/configs/default_policy.yaml +26 -26
  23. marama_route/configs/gateway.local.json +13 -0
  24. marama_route/configs/provider_aliases.yaml +30 -30
  25. marama_route/configs/routing_policy.yaml +20 -20
  26. marama_route/configs/service.local.json +14 -0
  27. marama_route/examples/capsule.personal-sovereignty-profile.json +45 -45
  28. marama_route/examples/capsule.restricted-nz-code.json +28 -28
  29. marama_route/examples/opencode.marama-route.json +28 -28
  30. marama_route/examples/request.allowed-local-edit.json +15 -15
  31. marama_route/examples/request.code-restricted.json +14 -14
  32. marama_route/examples/request.denied-training.json +15 -15
  33. marama_route/examples/request.openai-chat-code.json +44 -44
  34. marama_route/examples/request.personal-memory-read.json +19 -19
  35. marama_route/gateway.py +204 -204
  36. marama_route/integrations/opencode-compatible-provider.md +96 -96
  37. marama_route/integrations/opencode-provider.json +28 -28
  38. marama_route/platform.py +359 -359
  39. marama_route/registry.py +150 -150
  40. marama_route/router.py +113 -113
  41. marama_route/schemas/data_capsule.schema.json +88 -88
  42. marama_route/schemas/openai_chat_route_request.schema.json +95 -95
  43. marama_route/server.py +312 -0
  44. policy-packs/nz-personal-sovereignty.yaml +59 -59
  45. product_blueprint.md +73 -73
  46. product_manifest.json +11 -3
  47. pyproject.toml +1 -1
  48. quickstart.py +4 -0
  49. schemas/data_capsule.schema.json +88 -88
  50. schemas/openai_chat_route_request.schema.json +95 -95
README.md CHANGED
@@ -19,7 +19,7 @@ language:
19
  ---
20
 
21
  # AbteeX SovereignCode
22
- <!-- abteex-sovereigncode-card:v2 -->
23
 
24
  Standalone release package for `AbteeXAILab/sovereigncode`.
25
 
@@ -41,7 +41,10 @@ python -m sovereigncode.cli policy-matrix --capsule examples/capsule.restricted-
41
  python -m sovereigncode.cli tool-check --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --tool-name workspace_reader --action read_context
42
  python -m sovereigncode.cli opencode-config
43
  python -m sovereigncode.cli ui --smoke
 
 
44
  python -m sovereigncode.cli ui --port 8788 --open
 
45
  ```
46
 
47
  # AbteeX SovereignCode
@@ -139,6 +142,26 @@ Run the browser operator console:
139
  py -3 -m tinyluminax.products.sovereigncode.cli ui --port 8788 --open
140
  ```
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  Smoke-check the UI routes without opening a browser:
143
 
144
  ```bash
@@ -152,8 +175,9 @@ py -3 -m tinyluminax.products.sovereigncode.cli ui --smoke
152
  | Workspace Indexer | Builds a local map of files, policies, secrets, data classes, and repository ownership. |
153
  | Data Capsule PDP | Decides whether a request is allowed, denied, or allowed with obligations. |
154
  | Tool Broker | Wraps shell, file, git, network, package, and model actions with policy checks. |
 
155
  | LumynaX Runtime Adapter | Routes model calls to local GGUF, local API, or approved LumynaX model endpoints. |
156
- | Audit Ledger | Stores decision records, prompt/output hashes, file diffs, and approval metadata. |
157
  | Operator Console | Shows the plan, policy decision, diff, tests, and approval gate before external effects. |
158
  | Policy Matrix | Evaluates common tool/action scenarios against the same Data Capsule. |
159
  | Provider Exporter | Emits OpenCode-compatible workspace config pointing through MaramaRoute. |
@@ -180,6 +204,8 @@ py -3 -m tinyluminax.products.sovereigncode.cli ui --smoke
180
  | Personal profile capsule | `examples/capsule.personal-sovereignty-profile.json` |
181
  | Personal-memory request | `examples/request.personal-memory-read.json` |
182
  | Browser operator console | `python -m tinyluminax.products.sovereigncode.cli ui` |
 
 
183
  | Policy/tool matrix | `python -m tinyluminax.products.sovereigncode.cli policy-matrix` |
184
  | Tool gate check | `python -m tinyluminax.products.sovereigncode.cli tool-check` |
185
  | OpenCode workspace export | `python -m tinyluminax.products.sovereigncode.cli opencode-config` |
@@ -191,7 +217,7 @@ The sovereignty model is inspired by the Data Capsule pattern described in the S
191
 
192
  ## Stage
193
 
194
- This is an executable platform scaffold, not the final commercial application. The policy engine, router integration, CLI package, policy matrix, tool gate checks, capsule summaries, OpenCode config export, operator checklist, and browser operator console are working now; the live tool broker, terminal loop, and full autonomous agent loop are the next implementation stage.
195
 
196
 
197
  # AbteeX SovereignCode Product Blueprint
 
19
  ---
20
 
21
  # AbteeX SovereignCode
22
+ <!-- abteex-sovereigncode-card:v3 -->
23
 
24
  Standalone release package for `AbteeXAILab/sovereigncode`.
25
 
 
41
  python -m sovereigncode.cli tool-check --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --tool-name workspace_reader --action read_context
42
  python -m sovereigncode.cli opencode-config
43
  python -m sovereigncode.cli ui --smoke
44
+ python -m sovereigncode.cli serve --smoke
45
+ python -m sovereigncode.cli audit --limit 5
46
  python -m sovereigncode.cli ui --port 8788 --open
47
+ python -m sovereigncode.cli serve --port 8788 --open
48
  ```
49
 
50
  # AbteeX SovereignCode
 
142
  py -3 -m tinyluminax.products.sovereigncode.cli ui --port 8788 --open
143
  ```
144
 
145
+ Run the local policy API, persistent audit ledger, and browser console:
146
+
147
+ ```bash
148
+ py -3 -m tinyluminax.products.sovereigncode.cli serve --port 8788 --open
149
+ ```
150
+
151
+ Smoke-check the service without opening a browser:
152
+
153
+ ```bash
154
+ py -3 -m tinyluminax.products.sovereigncode.cli serve --smoke
155
+ ```
156
+
157
+ Read the audit ledger:
158
+
159
+ ```bash
160
+ py -3 -m tinyluminax.products.sovereigncode.cli audit --limit 10
161
+ ```
162
+
163
+ The service exposes `GET /health`, `GET /v1/audit`, `POST /v1/evaluate`, `POST /v1/plan-turn`, `POST /v1/tool-check`, `POST /v1/policy-matrix`, and the existing browser `/api/*` routes. It writes JSONL audit records to `.sovereigncode/audit.jsonl` by default.
164
+
165
  Smoke-check the UI routes without opening a browser:
166
 
167
  ```bash
 
175
  | Workspace Indexer | Builds a local map of files, policies, secrets, data classes, and repository ownership. |
176
  | Data Capsule PDP | Decides whether a request is allowed, denied, or allowed with obligations. |
177
  | Tool Broker | Wraps shell, file, git, network, package, and model actions with policy checks. |
178
+ | Policy API Service | Serves policy evaluation, turn planning, tool checks, policy matrix, and audit reads over local HTTP. |
179
  | LumynaX Runtime Adapter | Routes model calls to local GGUF, local API, or approved LumynaX model endpoints. |
180
+ | Audit Ledger | Stores append-only JSONL decision records, prompt/output hashes, file diffs, and approval metadata. |
181
  | Operator Console | Shows the plan, policy decision, diff, tests, and approval gate before external effects. |
182
  | Policy Matrix | Evaluates common tool/action scenarios against the same Data Capsule. |
183
  | Provider Exporter | Emits OpenCode-compatible workspace config pointing through MaramaRoute. |
 
204
  | Personal profile capsule | `examples/capsule.personal-sovereignty-profile.json` |
205
  | Personal-memory request | `examples/request.personal-memory-read.json` |
206
  | Browser operator console | `python -m tinyluminax.products.sovereigncode.cli ui` |
207
+ | Local policy API service | `python -m tinyluminax.products.sovereigncode.cli serve` |
208
+ | Audit ledger reader | `python -m tinyluminax.products.sovereigncode.cli audit` |
209
  | Policy/tool matrix | `python -m tinyluminax.products.sovereigncode.cli policy-matrix` |
210
  | Tool gate check | `python -m tinyluminax.products.sovereigncode.cli tool-check` |
211
  | OpenCode workspace export | `python -m tinyluminax.products.sovereigncode.cli opencode-config` |
 
217
 
218
  ## Stage
219
 
220
+ This is a local runtime product surface, not the final commercial application. The policy engine, router integration, CLI package, policy matrix, tool gate checks, capsule summaries, OpenCode config export, operator checklist, browser operator console, local policy API, and persistent audit ledger are working now. The full terminal editing loop remains a later layer, but policy, routing, audit, and OpenCode-facing configuration are executable today.
221
 
222
 
223
  # AbteeX SovereignCode Product Blueprint
SMOKE_TESTS.md CHANGED
@@ -6,8 +6,10 @@ Run from the package root:
6
  pip install -e .
7
  python quickstart.py
8
  python -m sovereigncode.cli ui --smoke
 
9
  python -m sovereigncode.cli policy-matrix --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json
10
  python -m sovereigncode.cli tool-check --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --tool-name workspace_reader --action read_context
 
11
  python -m sovereigncode.cli evaluate --capsule examples/capsule.personal-sovereignty-profile.json --request examples/request.personal-memory-read.json
12
  ```
13
 
 
6
  pip install -e .
7
  python quickstart.py
8
  python -m sovereigncode.cli ui --smoke
9
+ python -m sovereigncode.cli serve --smoke
10
  python -m sovereigncode.cli policy-matrix --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json
11
  python -m sovereigncode.cli tool-check --capsule examples/capsule.restricted-nz-code.json --request examples/request.allowed-local-edit.json --tool-name workspace_reader --action read_context
12
+ python -m sovereigncode.cli audit --limit 5
13
  python -m sovereigncode.cli evaluate --capsule examples/capsule.personal-sovereignty-profile.json --request examples/request.personal-memory-read.json
14
  ```
15
 
architecture.md CHANGED
@@ -1,95 +1,95 @@
1
- # AbteeX SovereignCode Architecture
2
-
3
- ## North Star
4
-
5
- SovereignCode should feel like a capable local coding agent, but every action must be accountable to data sovereignty and AI sovereignty controls. The product should never silently send sensitive code or governed data to a remote model, execute an external command, or publish a change without a visible decision trail.
6
-
7
- ## Control Plane
8
-
9
- ```text
10
- User intent
11
- -> Workspace indexer
12
- -> Data Capsule resolver
13
- -> Sovereignty policy decision point
14
- -> LumynaX MaramaRoute model selection
15
- -> Tool broker
16
- -> Human review gate
17
- -> Audit ledger
18
- ```
19
-
20
- ## Core Concepts
21
-
22
- ### Data Capsule
23
-
24
- A Data Capsule is the policy envelope attached to a workspace, dataset, tenant, case, source file set, or prompt context. It carries:
25
-
26
- - `allowed_purposes`
27
- - `denied_purposes`
28
- - `resident_regions`
29
- - `retention_days`
30
- - `training_allowed`
31
- - `export_allowed`
32
- - `data_classes`
33
- - `schema_context`
34
- - `consent_record`
35
-
36
- ### Policy Decision Point
37
-
38
- The policy decision point answers one question before every sensitive action: can this actor, for this purpose, in this region, using this model/tool, touch this capsule?
39
-
40
- The first implementation lives at `src/tinyluminax/products/sovereigncode/policy.py`.
41
-
42
- ### Tool Broker
43
-
44
- The broker is the enforcement layer for:
45
-
46
- - Shell commands
47
- - File writes
48
- - Git commits
49
- - Network calls
50
- - Package installs
51
- - Model calls
52
- - Retrieval queries
53
- - Training or distillation jobs
54
-
55
- Each tool call receives a decision: allow, deny, or allow with obligations.
56
-
57
- ### Audit Ledger
58
-
59
- Every decision creates a record containing:
60
-
61
- - Capsule id
62
- - Actor
63
- - Purpose
64
- - Action
65
- - Model id
66
- - Decision
67
- - Reasons
68
- - Obligations
69
- - Request hash
70
- - Timestamp
71
-
72
- The first implementation lives at `src/tinyluminax/products/sovereigncode/audit.py`.
73
-
74
- ## Launch Milestones
75
-
76
- | Milestone | Outcome |
77
- | --- | --- |
78
- | P0 scaffold | Policy engine, audit records, CLI, examples, docs. |
79
- | P1 terminal loop | Local terminal agent with plan/edit/test workflow. |
80
- | P2 tool broker | Policy wrappers for shell, git, file writes, package installs, and HTTP. |
81
- | P3 MaramaRoute integration | Sovereign model routing for every model call. |
82
- | P4 workspace UI | Browser console showing plan, policy, diffs, tests, and approvals. |
83
- | P5 enterprise controls | Tenant policies, SSO hooks, signed audit exports, policy packs. |
84
-
85
- ## Aesthetic Direction
86
-
87
- The product should follow the AbteeX/LumynaX visual system:
88
-
89
- - White or warm paper background.
90
- - Obsidian text.
91
- - Warm amber accent.
92
- - Thin rule-based layouts.
93
- - Editorial headings.
94
- - Mono labels for governance, provenance, and runtime details.
95
- - No generic purple AI gradients.
 
1
+ # AbteeX SovereignCode Architecture
2
+
3
+ ## North Star
4
+
5
+ SovereignCode should feel like a capable local coding agent, but every action must be accountable to data sovereignty and AI sovereignty controls. The product should never silently send sensitive code or governed data to a remote model, execute an external command, or publish a change without a visible decision trail.
6
+
7
+ ## Control Plane
8
+
9
+ ```text
10
+ User intent
11
+ -> Workspace indexer
12
+ -> Data Capsule resolver
13
+ -> Sovereignty policy decision point
14
+ -> LumynaX MaramaRoute model selection
15
+ -> Tool broker
16
+ -> Human review gate
17
+ -> Audit ledger
18
+ ```
19
+
20
+ ## Core Concepts
21
+
22
+ ### Data Capsule
23
+
24
+ A Data Capsule is the policy envelope attached to a workspace, dataset, tenant, case, source file set, or prompt context. It carries:
25
+
26
+ - `allowed_purposes`
27
+ - `denied_purposes`
28
+ - `resident_regions`
29
+ - `retention_days`
30
+ - `training_allowed`
31
+ - `export_allowed`
32
+ - `data_classes`
33
+ - `schema_context`
34
+ - `consent_record`
35
+
36
+ ### Policy Decision Point
37
+
38
+ The policy decision point answers one question before every sensitive action: can this actor, for this purpose, in this region, using this model/tool, touch this capsule?
39
+
40
+ The first implementation lives at `src/tinyluminax/products/sovereigncode/policy.py`.
41
+
42
+ ### Tool Broker
43
+
44
+ The broker is the enforcement layer for:
45
+
46
+ - Shell commands
47
+ - File writes
48
+ - Git commits
49
+ - Network calls
50
+ - Package installs
51
+ - Model calls
52
+ - Retrieval queries
53
+ - Training or distillation jobs
54
+
55
+ Each tool call receives a decision: allow, deny, or allow with obligations.
56
+
57
+ ### Audit Ledger
58
+
59
+ Every decision creates a record containing:
60
+
61
+ - Capsule id
62
+ - Actor
63
+ - Purpose
64
+ - Action
65
+ - Model id
66
+ - Decision
67
+ - Reasons
68
+ - Obligations
69
+ - Request hash
70
+ - Timestamp
71
+
72
+ The first implementation lives at `src/tinyluminax/products/sovereigncode/audit.py`.
73
+
74
+ ## Launch Milestones
75
+
76
+ | Milestone | Outcome |
77
+ | --- | --- |
78
+ | P0 scaffold | Policy engine, audit records, CLI, examples, docs. |
79
+ | P1 terminal loop | Local terminal agent with plan/edit/test workflow. |
80
+ | P2 tool broker | Policy wrappers for shell, git, file writes, package installs, and HTTP. |
81
+ | P3 MaramaRoute integration | Sovereign model routing for every model call. |
82
+ | P4 workspace UI | Browser console showing plan, policy, diffs, tests, and approvals. |
83
+ | P5 enterprise controls | Tenant policies, SSO hooks, signed audit exports, policy packs. |
84
+
85
+ ## Aesthetic Direction
86
+
87
+ The product should follow the AbteeX/LumynaX visual system:
88
+
89
+ - White or warm paper background.
90
+ - Obsidian text.
91
+ - Warm amber accent.
92
+ - Thin rule-based layouts.
93
+ - Editorial headings.
94
+ - Mono labels for governance, provenance, and runtime details.
95
+ - No generic purple AI gradients.
configs/default_policy.yaml CHANGED
@@ -1,26 +1,26 @@
1
- policy_id: abx-sovereigncode-default-v0
2
- default_jurisdiction: NZ
3
- default_resident_regions:
4
- - NZ
5
- high_impact_sensitivity:
6
- - personal
7
- - restricted
8
- - health
9
- - iwi
10
- - taonga
11
- default_obligations:
12
- - write_immutable_audit_record
13
- - preserve_capsule_id_in_agent_trace
14
- - show_diff_before_write_or_commit
15
- denied_without_human_approval:
16
- - delete_file
17
- - execute_shell
18
- - network_export
19
- - publish
20
- - commit
21
- remote_model_rule:
22
- restricted_data_requires_local_or_lumynax: true
23
- training_rule:
24
- requires_capsule_training_allowed: true
25
- export_rule:
26
- requires_capsule_export_allowed: true
 
1
+ policy_id: abx-sovereigncode-default-v0
2
+ default_jurisdiction: NZ
3
+ default_resident_regions:
4
+ - NZ
5
+ high_impact_sensitivity:
6
+ - personal
7
+ - restricted
8
+ - health
9
+ - iwi
10
+ - taonga
11
+ default_obligations:
12
+ - write_immutable_audit_record
13
+ - preserve_capsule_id_in_agent_trace
14
+ - show_diff_before_write_or_commit
15
+ denied_without_human_approval:
16
+ - delete_file
17
+ - execute_shell
18
+ - network_export
19
+ - publish
20
+ - commit
21
+ remote_model_rule:
22
+ restricted_data_requires_local_or_lumynax: true
23
+ training_rule:
24
+ requires_capsule_training_allowed: true
25
+ export_rule:
26
+ requires_capsule_export_allowed: true
configs/gateway.local.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mode": "route_only",
3
+ "prompt_retention": "not_stored_by_default",
4
+ "default_timeout_seconds": 120,
5
+ "backends": {
6
+ "example-local-openai-compatible": {
7
+ "type": "openai_compatible",
8
+ "base_url": "http://127.0.0.1:8000/v1",
9
+ "api_key_env": "",
10
+ "model": "local-model-id"
11
+ }
12
+ }
13
+ }
configs/provider_aliases.yaml CHANGED
@@ -1,30 +1,30 @@
1
- provider_id: abteex-marama
2
- base_path: /v1
3
- default_model_alias: lumynax/auto
4
- aliases:
5
- lumynax/auto:
6
- task_type: general
7
- requires_local: true
8
- description: Select the best resident LumynaX model for the request.
9
- lumynax/code:
10
- task_type: code
11
- requires_local: true
12
- requires_json: true
13
- description: Prefer coder-tagged LumynaX models with tool and JSON support.
14
- lumynax/reasoning:
15
- task_type: reasoning
16
- requires_local: true
17
- description: Prefer reasoning-tagged models inside residency constraints.
18
- lumynax/multimodal:
19
- task_type: multimodal
20
- requires_local: false
21
- description: Prefer text-plus-image LumynaX models when policy allows.
22
- default_route:
23
- jurisdiction: NZ
24
- data_sensitivity: internal
25
- min_context_tokens: 4096
26
- max_fallbacks: 3
27
- telemetry:
28
- retain_prompt_by_default: false
29
- retain_route_decision_days: 365
30
- hash_request_payload: true
 
1
+ provider_id: abteex-marama
2
+ base_path: /v1
3
+ default_model_alias: lumynax/auto
4
+ aliases:
5
+ lumynax/auto:
6
+ task_type: general
7
+ requires_local: true
8
+ description: Select the best resident LumynaX model for the request.
9
+ lumynax/code:
10
+ task_type: code
11
+ requires_local: true
12
+ requires_json: true
13
+ description: Prefer coder-tagged LumynaX models with tool and JSON support.
14
+ lumynax/reasoning:
15
+ task_type: reasoning
16
+ requires_local: true
17
+ description: Prefer reasoning-tagged models inside residency constraints.
18
+ lumynax/multimodal:
19
+ task_type: multimodal
20
+ requires_local: false
21
+ description: Prefer text-plus-image LumynaX models when policy allows.
22
+ default_route:
23
+ jurisdiction: NZ
24
+ data_sensitivity: internal
25
+ min_context_tokens: 4096
26
+ max_fallbacks: 3
27
+ telemetry:
28
+ retain_prompt_by_default: false
29
+ retain_route_decision_days: 365
30
+ hash_request_payload: true
configs/routing_policy.yaml CHANGED
@@ -1,20 +1,20 @@
1
- policy_id: lumynax-marama-route-default-v0
2
- default_jurisdiction: NZ
3
- default_requires_local: true
4
- high_sensitivity:
5
- - personal
6
- - restricted
7
- - health
8
- - iwi
9
- - taonga
10
- required_for_high_sensitivity:
11
- min_sovereignty_tier: 2
12
- residency_must_match_request_jurisdiction: true
13
- retain_prompt_by_default: false
14
- preferred_runtimes:
15
- - llama_cpp
16
- - transformers_multimodal
17
- - python_embedding
18
- fallbacks:
19
- max_default_fallbacks: 3
20
- include_rejection_reasons: true
 
1
+ policy_id: lumynax-marama-route-default-v0
2
+ default_jurisdiction: NZ
3
+ default_requires_local: true
4
+ high_sensitivity:
5
+ - personal
6
+ - restricted
7
+ - health
8
+ - iwi
9
+ - taonga
10
+ required_for_high_sensitivity:
11
+ min_sovereignty_tier: 2
12
+ residency_must_match_request_jurisdiction: true
13
+ retain_prompt_by_default: false
14
+ preferred_runtimes:
15
+ - llama_cpp
16
+ - transformers_multimodal
17
+ - python_embedding
18
+ fallbacks:
19
+ max_default_fallbacks: 3
20
+ include_rejection_reasons: true
configs/service.local.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ledger_path": ".sovereigncode/audit.jsonl",
3
+ "default_region": "NZ",
4
+ "fail_closed": true,
5
+ "require_human_review_for": [
6
+ "write_files",
7
+ "execute_shell",
8
+ "network_export",
9
+ "commit",
10
+ "publish",
11
+ "train_model"
12
+ ],
13
+ "marama_route_base_url": "http://127.0.0.1:8787/v1"
14
+ }
examples/capsule.personal-sovereignty-profile.json CHANGED
@@ -1,45 +1,45 @@
1
- {
2
- "capsule_id": "cap-personal-profile-001",
3
- "subject_id": "operator-local-profile",
4
- "jurisdiction": "NZ",
5
- "sensitivity": "personal",
6
- "allowed_purposes": [
7
- "personal_memory",
8
- "coding_assistance",
9
- "inference"
10
- ],
11
- "denied_purposes": [
12
- "ad_training",
13
- "third_party_resale",
14
- "public_leaderboard"
15
- ],
16
- "resident_regions": [
17
- "NZ"
18
- ],
19
- "data_classes": [
20
- "personal",
21
- "preferences",
22
- "source_code",
23
- "runtime_logs"
24
- ],
25
- "retention_days": 7,
26
- "export_allowed": false,
27
- "training_allowed": false,
28
- "personal_detail_level": "pseudonymous",
29
- "consent_scopes": [
30
- "personal_memory",
31
- "coding_assistance"
32
- ],
33
- "data_subject_rights": [
34
- "access",
35
- "correction",
36
- "deletion_request",
37
- "processing_objection"
38
- ],
39
- "schema_context": "https://schema.org",
40
- "consent_record": "local-profile-consent-v0",
41
- "metadata": {
42
- "storage": "local_encrypted_profile_store",
43
- "prompt_rule": "summarise preferences without exposing raw personal notes"
44
- }
45
- }
 
1
+ {
2
+ "capsule_id": "cap-personal-profile-001",
3
+ "subject_id": "operator-local-profile",
4
+ "jurisdiction": "NZ",
5
+ "sensitivity": "personal",
6
+ "allowed_purposes": [
7
+ "personal_memory",
8
+ "coding_assistance",
9
+ "inference"
10
+ ],
11
+ "denied_purposes": [
12
+ "ad_training",
13
+ "third_party_resale",
14
+ "public_leaderboard"
15
+ ],
16
+ "resident_regions": [
17
+ "NZ"
18
+ ],
19
+ "data_classes": [
20
+ "personal",
21
+ "preferences",
22
+ "source_code",
23
+ "runtime_logs"
24
+ ],
25
+ "retention_days": 7,
26
+ "export_allowed": false,
27
+ "training_allowed": false,
28
+ "personal_detail_level": "pseudonymous",
29
+ "consent_scopes": [
30
+ "personal_memory",
31
+ "coding_assistance"
32
+ ],
33
+ "data_subject_rights": [
34
+ "access",
35
+ "correction",
36
+ "deletion_request",
37
+ "processing_objection"
38
+ ],
39
+ "schema_context": "https://schema.org",
40
+ "consent_record": "local-profile-consent-v0",
41
+ "metadata": {
42
+ "storage": "local_encrypted_profile_store",
43
+ "prompt_rule": "summarise preferences without exposing raw personal notes"
44
+ }
45
+ }
examples/capsule.restricted-nz-code.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "capsule_id": "cap-nz-code-001",
3
- "subject_id": "abx-workspace",
4
- "jurisdiction": "NZ",
5
- "sensitivity": "restricted",
6
- "allowed_purposes": [
7
- "coding_assistance",
8
- "inference",
9
- "test_generation"
10
- ],
11
- "denied_purposes": [
12
- "ad_training",
13
- "third_party_resale"
14
- ],
15
- "resident_regions": [
16
- "NZ"
17
- ],
18
- "data_classes": [
19
- "source_code",
20
- "policy",
21
- "runtime_logs"
22
- ],
23
- "retention_days": 14,
24
- "export_allowed": false,
25
- "training_allowed": false,
26
- "schema_context": "https://schema.org",
27
- "consent_record": "local-operator-policy-v0"
28
- }
 
1
+ {
2
+ "capsule_id": "cap-nz-code-001",
3
+ "subject_id": "abx-workspace",
4
+ "jurisdiction": "NZ",
5
+ "sensitivity": "restricted",
6
+ "allowed_purposes": [
7
+ "coding_assistance",
8
+ "inference",
9
+ "test_generation"
10
+ ],
11
+ "denied_purposes": [
12
+ "ad_training",
13
+ "third_party_resale"
14
+ ],
15
+ "resident_regions": [
16
+ "NZ"
17
+ ],
18
+ "data_classes": [
19
+ "source_code",
20
+ "policy",
21
+ "runtime_logs"
22
+ ],
23
+ "retention_days": 14,
24
+ "export_allowed": false,
25
+ "training_allowed": false,
26
+ "schema_context": "https://schema.org",
27
+ "consent_record": "local-operator-policy-v0"
28
+ }
examples/opencode.marama-route.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json",
3
- "provider": {
4
- "abteex-marama": {
5
- "npm": "@ai-sdk/openai-compatible",
6
- "name": "AbteeX MaramaRoute",
7
- "options": {
8
- "baseURL": "http://127.0.0.1:8787/v1",
9
- "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
- "headers": {
11
- "X-AbteeX-Tenant": "{env:ABTEEX_TENANT_ID}",
12
- "X-AbteeX-Workspace-Capsule": "{env:SOVEREIGNCODE_CAPSULE_ID}"
13
- }
14
- },
15
- "models": {
16
- "lumynax/auto": {
17
- "name": "LumynaX Auto Sovereign Route"
18
- },
19
- "lumynax/code": {
20
- "name": "LumynaX Code Route"
21
- },
22
- "lumynax/reasoning": {
23
- "name": "LumynaX Reasoning Route"
24
- }
25
- }
26
- }
27
- }
28
- }
 
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "provider": {
4
+ "abteex-marama": {
5
+ "npm": "@ai-sdk/openai-compatible",
6
+ "name": "AbteeX MaramaRoute",
7
+ "options": {
8
+ "baseURL": "http://127.0.0.1:8787/v1",
9
+ "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
+ "headers": {
11
+ "X-AbteeX-Tenant": "{env:ABTEEX_TENANT_ID}",
12
+ "X-AbteeX-Workspace-Capsule": "{env:SOVEREIGNCODE_CAPSULE_ID}"
13
+ }
14
+ },
15
+ "models": {
16
+ "lumynax/auto": {
17
+ "name": "LumynaX Auto Sovereign Route"
18
+ },
19
+ "lumynax/code": {
20
+ "name": "LumynaX Code Route"
21
+ },
22
+ "lumynax/reasoning": {
23
+ "name": "LumynaX Reasoning Route"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
examples/request.allowed-local-edit.json CHANGED
@@ -1,15 +1,15 @@
1
- {
2
- "actor": "developer",
3
- "purpose": "coding_assistance",
4
- "action": "read_context",
5
- "region": "NZ",
6
- "model_id": "AbteeXAILab/lumynax-infused-qwen3-8b-gguf",
7
- "data_classes": [
8
- "source_code"
9
- ],
10
- "tool_name": "workspace_reader",
11
- "writes_files": false,
12
- "exports_data": false,
13
- "trains_model": false,
14
- "human_approved": false
15
- }
 
1
+ {
2
+ "actor": "developer",
3
+ "purpose": "coding_assistance",
4
+ "action": "read_context",
5
+ "region": "NZ",
6
+ "model_id": "AbteeXAILab/lumynax-infused-qwen3-8b-gguf",
7
+ "data_classes": [
8
+ "source_code"
9
+ ],
10
+ "tool_name": "workspace_reader",
11
+ "writes_files": false,
12
+ "exports_data": false,
13
+ "trains_model": false,
14
+ "human_approved": false
15
+ }
examples/request.code-restricted.json CHANGED
@@ -1,14 +1,14 @@
1
- {
2
- "prompt": "Refactor this private Python service and explain the diff.",
3
- "task_type": "code",
4
- "modalities": [
5
- "text"
6
- ],
7
- "jurisdiction": "NZ",
8
- "data_sensitivity": "restricted",
9
- "min_context_tokens": 4096,
10
- "requires_local": true,
11
- "requires_tools": false,
12
- "requires_json": true,
13
- "max_fallbacks": 3
14
- }
 
1
+ {
2
+ "prompt": "Refactor this private Python service and explain the diff.",
3
+ "task_type": "code",
4
+ "modalities": [
5
+ "text"
6
+ ],
7
+ "jurisdiction": "NZ",
8
+ "data_sensitivity": "restricted",
9
+ "min_context_tokens": 4096,
10
+ "requires_local": true,
11
+ "requires_tools": false,
12
+ "requires_json": true,
13
+ "max_fallbacks": 3
14
+ }
examples/request.denied-training.json CHANGED
@@ -1,15 +1,15 @@
1
- {
2
- "actor": "developer",
3
- "purpose": "coding_assistance",
4
- "action": "train_adapter",
5
- "region": "NZ",
6
- "model_id": "local/lumynax",
7
- "data_classes": [
8
- "source_code"
9
- ],
10
- "tool_name": "trainer",
11
- "writes_files": true,
12
- "exports_data": false,
13
- "trains_model": true,
14
- "human_approved": true
15
- }
 
1
+ {
2
+ "actor": "developer",
3
+ "purpose": "coding_assistance",
4
+ "action": "train_adapter",
5
+ "region": "NZ",
6
+ "model_id": "local/lumynax",
7
+ "data_classes": [
8
+ "source_code"
9
+ ],
10
+ "tool_name": "trainer",
11
+ "writes_files": true,
12
+ "exports_data": false,
13
+ "trains_model": true,
14
+ "human_approved": true
15
+ }
examples/request.openai-chat-code.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "model": "lumynax/code",
3
- "messages": [
4
- {
5
- "role": "system",
6
- "content": "You are a governed coding assistant for a New Zealand workspace."
7
- },
8
- {
9
- "role": "user",
10
- "content": "Refactor this private Python repository function and return a JSON diff plan."
11
- }
12
- ],
13
- "response_format": {
14
- "type": "json_object"
15
- },
16
- "tools": [
17
- {
18
- "type": "function",
19
- "function": {
20
- "name": "propose_patch",
21
- "description": "Propose a patch without writing it.",
22
- "parameters": {
23
- "type": "object",
24
- "properties": {
25
- "files": {
26
- "type": "array",
27
- "items": { "type": "string" }
28
- }
29
- }
30
- }
31
- }
32
- }
33
- ],
34
- "route": {
35
- "jurisdiction": "NZ",
36
- "data_sensitivity": "restricted",
37
- "task_type": "code",
38
- "requires_local": true,
39
- "requires_tools": true,
40
- "requires_json": true,
41
- "min_context_tokens": 4096,
42
- "max_fallbacks": 3
43
- }
44
- }
 
1
+ {
2
+ "model": "lumynax/code",
3
+ "messages": [
4
+ {
5
+ "role": "system",
6
+ "content": "You are a governed coding assistant for a New Zealand workspace."
7
+ },
8
+ {
9
+ "role": "user",
10
+ "content": "Refactor this private Python repository function and return a JSON diff plan."
11
+ }
12
+ ],
13
+ "response_format": {
14
+ "type": "json_object"
15
+ },
16
+ "tools": [
17
+ {
18
+ "type": "function",
19
+ "function": {
20
+ "name": "propose_patch",
21
+ "description": "Propose a patch without writing it.",
22
+ "parameters": {
23
+ "type": "object",
24
+ "properties": {
25
+ "files": {
26
+ "type": "array",
27
+ "items": { "type": "string" }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ],
34
+ "route": {
35
+ "jurisdiction": "NZ",
36
+ "data_sensitivity": "restricted",
37
+ "task_type": "code",
38
+ "requires_local": true,
39
+ "requires_tools": true,
40
+ "requires_json": true,
41
+ "min_context_tokens": 4096,
42
+ "max_fallbacks": 3
43
+ }
44
+ }
examples/request.personal-memory-read.json CHANGED
@@ -1,19 +1,19 @@
1
- {
2
- "actor": "developer",
3
- "purpose": "personal_memory",
4
- "action": "read_context",
5
- "region": "NZ",
6
- "model_id": "local/lumynax",
7
- "data_classes": [
8
- "personal",
9
- "preferences"
10
- ],
11
- "tool_name": "personal_profile_reader",
12
- "writes_files": false,
13
- "exports_data": false,
14
- "trains_model": false,
15
- "human_approved": false,
16
- "personal_detail_level": "pseudonymous",
17
- "consent_scope": "personal_memory",
18
- "requested_retention_days": 7
19
- }
 
1
+ {
2
+ "actor": "developer",
3
+ "purpose": "personal_memory",
4
+ "action": "read_context",
5
+ "region": "NZ",
6
+ "model_id": "local/lumynax",
7
+ "data_classes": [
8
+ "personal",
9
+ "preferences"
10
+ ],
11
+ "tool_name": "personal_profile_reader",
12
+ "writes_files": false,
13
+ "exports_data": false,
14
+ "trains_model": false,
15
+ "human_approved": false,
16
+ "personal_detail_level": "pseudonymous",
17
+ "consent_scope": "personal_memory",
18
+ "requested_retention_days": 7
19
+ }
integrations/opencode-compatible-provider.md CHANGED
@@ -1,96 +1,96 @@
1
- # OpenCode-Compatible Provider Integration
2
-
3
- ## Goal
4
-
5
- Make AbteeX SovereignCode usable from OpenCode and similar coding agents without
6
- requiring those tools to understand Data Capsules directly.
7
-
8
- The integration shape is:
9
-
10
- ```text
11
- OpenCode
12
- -> OpenAI-compatible provider config
13
- -> MaramaRoute gateway `/v1`
14
- -> SovereignCode policy and tool broker
15
- -> LumynaX model runtime
16
- ```
17
-
18
- ## Current Compatibility Target
19
-
20
- OpenCode supports custom OpenAI-compatible providers through
21
- `@ai-sdk/openai-compatible` and a provider `baseURL`. OpenRouter exposes an
22
- OpenAI-like chat endpoint at `/api/v1/chat/completions`, with normalized request
23
- and response payloads. MaramaRoute should therefore expose:
24
-
25
- - `GET /v1/models`
26
- - `POST /v1/chat/completions`
27
- - `POST /v1/route`
28
- - `GET /v1/route/{decision_id}`
29
-
30
- References checked on 2026-05-17:
31
-
32
- - https://opencode.ai/docs/providers
33
- - https://openrouter.ai/docs/api-reference/overview/
34
- - https://openrouter.ai/docs/api-reference/chat-completion
35
-
36
- ## OpenCode Provider Config
37
-
38
- Use `examples/opencode.marama-route.json` as the project-local provider file.
39
-
40
- The important fields are:
41
-
42
- | Field | Value |
43
- | --- | --- |
44
- | `provider.abteex-marama.npm` | `@ai-sdk/openai-compatible` |
45
- | `provider.abteex-marama.options.baseURL` | Local or hosted MaramaRoute `/v1` URL |
46
- | `provider.abteex-marama.options.apiKey` | Environment backed key |
47
- | `provider.abteex-marama.models` | LumynaX model aliases exposed by MaramaRoute |
48
-
49
- ## SovereignCode Responsibilities
50
-
51
- OpenCode sends a normal chat request. SovereignCode and MaramaRoute add:
52
-
53
- - capsule resolution from workspace policy files
54
- - purpose and personal-detail checks before prompt assembly
55
- - model routing based on residency, modality, task, and sensitivity
56
- - visible approval gates before file writes, shell commands, network export, or commit
57
- - audit records for policy decisions and route decisions
58
-
59
- ## Workspace Files
60
-
61
- A governed workspace should carry:
62
-
63
- ```text
64
- .sovereigncode/
65
- capsule.json
66
- tenant-policy.yaml
67
- approvals/
68
- audit/
69
- opencode.json
70
- ```
71
-
72
- The agent can start with `capsule.json` and `opencode.json`. The full tool
73
- broker can add approvals and audit persistence in the next build stage.
74
-
75
- ## Minimum Viable Flow
76
-
77
- 1. User opens a project in OpenCode.
78
- 2. OpenCode uses the `abteex-marama` provider.
79
- 3. MaramaRoute dry-runs the chat payload and selects a LumynaX model.
80
- 4. SovereignCode checks the workspace Data Capsule before exposing context.
81
- 5. The coding agent proposes a plan.
82
- 6. File writes require a visible diff and an audit record.
83
- 7. Shell, network, commit, and publish actions require explicit approval.
84
-
85
- ## Similar Clients
86
-
87
- Any client that can point at an OpenAI-compatible endpoint should use the same
88
- gateway:
89
-
90
- | Client type | Expected integration |
91
- | --- | --- |
92
- | OpenCode | `opencode.json` custom provider |
93
- | Continue-style IDE assistant | OpenAI-compatible base URL and model ids |
94
- | Aider-style terminal assistant | OpenAI-compatible base URL and key |
95
- | Internal agent runner | Direct `/v1/route` and `/v1/chat/completions` calls |
96
- | Browser console | Same API behind tenant auth |
 
1
+ # OpenCode-Compatible Provider Integration
2
+
3
+ ## Goal
4
+
5
+ Make AbteeX SovereignCode usable from OpenCode and similar coding agents without
6
+ requiring those tools to understand Data Capsules directly.
7
+
8
+ The integration shape is:
9
+
10
+ ```text
11
+ OpenCode
12
+ -> OpenAI-compatible provider config
13
+ -> MaramaRoute gateway `/v1`
14
+ -> SovereignCode policy and tool broker
15
+ -> LumynaX model runtime
16
+ ```
17
+
18
+ ## Current Compatibility Target
19
+
20
+ OpenCode supports custom OpenAI-compatible providers through
21
+ `@ai-sdk/openai-compatible` and a provider `baseURL`. OpenRouter exposes an
22
+ OpenAI-like chat endpoint at `/api/v1/chat/completions`, with normalized request
23
+ and response payloads. MaramaRoute should therefore expose:
24
+
25
+ - `GET /v1/models`
26
+ - `POST /v1/chat/completions`
27
+ - `POST /v1/route`
28
+ - `GET /v1/route/{decision_id}`
29
+
30
+ References checked on 2026-05-17:
31
+
32
+ - https://opencode.ai/docs/providers
33
+ - https://openrouter.ai/docs/api-reference/overview/
34
+ - https://openrouter.ai/docs/api-reference/chat-completion
35
+
36
+ ## OpenCode Provider Config
37
+
38
+ Use `examples/opencode.marama-route.json` as the project-local provider file.
39
+
40
+ The important fields are:
41
+
42
+ | Field | Value |
43
+ | --- | --- |
44
+ | `provider.abteex-marama.npm` | `@ai-sdk/openai-compatible` |
45
+ | `provider.abteex-marama.options.baseURL` | Local or hosted MaramaRoute `/v1` URL |
46
+ | `provider.abteex-marama.options.apiKey` | Environment backed key |
47
+ | `provider.abteex-marama.models` | LumynaX model aliases exposed by MaramaRoute |
48
+
49
+ ## SovereignCode Responsibilities
50
+
51
+ OpenCode sends a normal chat request. SovereignCode and MaramaRoute add:
52
+
53
+ - capsule resolution from workspace policy files
54
+ - purpose and personal-detail checks before prompt assembly
55
+ - model routing based on residency, modality, task, and sensitivity
56
+ - visible approval gates before file writes, shell commands, network export, or commit
57
+ - audit records for policy decisions and route decisions
58
+
59
+ ## Workspace Files
60
+
61
+ A governed workspace should carry:
62
+
63
+ ```text
64
+ .sovereigncode/
65
+ capsule.json
66
+ tenant-policy.yaml
67
+ approvals/
68
+ audit/
69
+ opencode.json
70
+ ```
71
+
72
+ The agent can start with `capsule.json` and `opencode.json`. The full tool
73
+ broker can add approvals and audit persistence in the next build stage.
74
+
75
+ ## Minimum Viable Flow
76
+
77
+ 1. User opens a project in OpenCode.
78
+ 2. OpenCode uses the `abteex-marama` provider.
79
+ 3. MaramaRoute dry-runs the chat payload and selects a LumynaX model.
80
+ 4. SovereignCode checks the workspace Data Capsule before exposing context.
81
+ 5. The coding agent proposes a plan.
82
+ 6. File writes require a visible diff and an audit record.
83
+ 7. Shell, network, commit, and publish actions require explicit approval.
84
+
85
+ ## Similar Clients
86
+
87
+ Any client that can point at an OpenAI-compatible endpoint should use the same
88
+ gateway:
89
+
90
+ | Client type | Expected integration |
91
+ | --- | --- |
92
+ | OpenCode | `opencode.json` custom provider |
93
+ | Continue-style IDE assistant | OpenAI-compatible base URL and model ids |
94
+ | Aider-style terminal assistant | OpenAI-compatible base URL and key |
95
+ | Internal agent runner | Direct `/v1/route` and `/v1/chat/completions` calls |
96
+ | Browser console | Same API behind tenant auth |
integrations/opencode-provider.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json",
3
- "provider": {
4
- "abteex-marama": {
5
- "npm": "@ai-sdk/openai-compatible",
6
- "name": "AbteeX MaramaRoute",
7
- "options": {
8
- "baseURL": "http://127.0.0.1:8787/v1",
9
- "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
- "headers": {
11
- "X-AbteeX-Route-Jurisdiction": "NZ",
12
- "X-AbteeX-Route-Sensitivity": "restricted"
13
- }
14
- },
15
- "models": {
16
- "lumynax/auto": {
17
- "name": "LumynaX Auto"
18
- },
19
- "lumynax/code": {
20
- "name": "LumynaX Code"
21
- },
22
- "lumynax/local": {
23
- "name": "LumynaX Local"
24
- }
25
- }
26
- }
27
- }
28
- }
 
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "provider": {
4
+ "abteex-marama": {
5
+ "npm": "@ai-sdk/openai-compatible",
6
+ "name": "AbteeX MaramaRoute",
7
+ "options": {
8
+ "baseURL": "http://127.0.0.1:8787/v1",
9
+ "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
+ "headers": {
11
+ "X-AbteeX-Route-Jurisdiction": "NZ",
12
+ "X-AbteeX-Route-Sensitivity": "restricted"
13
+ }
14
+ },
15
+ "models": {
16
+ "lumynax/auto": {
17
+ "name": "LumynaX Auto"
18
+ },
19
+ "lumynax/code": {
20
+ "name": "LumynaX Code"
21
+ },
22
+ "lumynax/local": {
23
+ "name": "LumynaX Local"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
marama_route/__init__.py CHANGED
@@ -15,6 +15,7 @@ from .platform import (
15
  )
16
  from .registry import ModelEndpoint, RoutingRequest, load_model_registry
17
  from .router import RouteDecision, SovereignModelRouter
 
18
  from .ui import smoke_ui as smoke_ui
19
 
20
  __all__ = [
@@ -28,9 +29,12 @@ __all__ = [
28
  "build_registry_analytics",
29
  "catalog_models",
30
  "compare_models",
 
 
31
  "load_model_registry",
32
  "route_chat_payload",
33
  "route_scenario_matrix",
34
  "routing_request_from_chat_payload",
 
35
  "smoke_ui",
36
  ]
 
15
  )
16
  from .registry import ModelEndpoint, RoutingRequest, load_model_registry
17
  from .router import RouteDecision, SovereignModelRouter
18
+ from .server import handle_gateway_request, load_gateway_config, smoke_gateway
19
  from .ui import smoke_ui as smoke_ui
20
 
21
  __all__ = [
 
29
  "build_registry_analytics",
30
  "catalog_models",
31
  "compare_models",
32
+ "handle_gateway_request",
33
+ "load_gateway_config",
34
  "load_model_registry",
35
  "route_chat_payload",
36
  "route_scenario_matrix",
37
  "routing_request_from_chat_payload",
38
+ "smoke_gateway",
39
  "smoke_ui",
40
  ]
marama_route/_ui_server.py CHANGED
@@ -37,8 +37,14 @@ def serve_dashboard(
37
  host: str,
38
  port: int,
39
  open_browser: bool = False,
 
 
40
  ) -> int:
41
  actual_port = find_available_port(host, port)
 
 
 
 
42
 
43
  class Handler(BaseHTTPRequestHandler):
44
  server_version = "AbteeXProductUI/0.1"
@@ -48,14 +54,14 @@ def serve_dashboard(
48
  if path == "/":
49
  self._send_text(200, html, "text/html; charset=utf-8")
50
  return
51
- if path.startswith("/api/"):
52
  self._send_api("GET", path, None)
53
  return
54
  self._send_json(404, {"ok": False, "error": "not_found"})
55
 
56
  def do_POST(self) -> None: # noqa: N802 - stdlib handler method name
57
  path = urlparse(self.path).path
58
- if not path.startswith("/api/"):
59
  self._send_json(404, {"ok": False, "error": "not_found"})
60
  return
61
  try:
 
37
  host: str,
38
  port: int,
39
  open_browser: bool = False,
40
+ api_path_prefixes: tuple[str, ...] = ("/api/",),
41
+ api_exact_paths: tuple[str, ...] = (),
42
  ) -> int:
43
  actual_port = find_available_port(host, port)
44
+ exact_paths = set(api_exact_paths)
45
+
46
+ def is_api_path(path: str) -> bool:
47
+ return path in exact_paths or any(path.startswith(prefix) for prefix in api_path_prefixes)
48
 
49
  class Handler(BaseHTTPRequestHandler):
50
  server_version = "AbteeXProductUI/0.1"
 
54
  if path == "/":
55
  self._send_text(200, html, "text/html; charset=utf-8")
56
  return
57
+ if is_api_path(path):
58
  self._send_api("GET", path, None)
59
  return
60
  self._send_json(404, {"ok": False, "error": "not_found"})
61
 
62
  def do_POST(self) -> None: # noqa: N802 - stdlib handler method name
63
  path = urlparse(self.path).path
64
+ if not is_api_path(path):
65
  self._send_json(404, {"ok": False, "error": "not_found"})
66
  return
67
  try:
marama_route/cli.py CHANGED
@@ -109,6 +109,19 @@ def _ui(args: argparse.Namespace) -> int:
109
  )
110
 
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  def build_parser() -> argparse.ArgumentParser:
113
  parser = argparse.ArgumentParser(
114
  prog="lumynax-marama-route",
@@ -191,6 +204,18 @@ def build_parser() -> argparse.ArgumentParser:
191
  ui.add_argument("--open", action=argparse.BooleanOptionalAction, default=False)
192
  ui.add_argument("--smoke", action=argparse.BooleanOptionalAction, default=False)
193
  ui.set_defaults(handler=_ui)
 
 
 
 
 
 
 
 
 
 
 
 
194
  return parser
195
 
196
 
 
109
  )
110
 
111
 
112
+ def _serve(args: argparse.Namespace) -> int:
113
+ from .server import serve_gateway
114
+
115
+ return serve_gateway(
116
+ registry_path=args.registry,
117
+ config_path=args.config,
118
+ host=args.host,
119
+ port=args.port,
120
+ open_browser=args.open,
121
+ smoke=args.smoke,
122
+ )
123
+
124
+
125
  def build_parser() -> argparse.ArgumentParser:
126
  parser = argparse.ArgumentParser(
127
  prog="lumynax-marama-route",
 
204
  ui.add_argument("--open", action=argparse.BooleanOptionalAction, default=False)
205
  ui.add_argument("--smoke", action=argparse.BooleanOptionalAction, default=False)
206
  ui.set_defaults(handler=_ui)
207
+
208
+ serve = subparsers.add_parser(
209
+ "serve",
210
+ help="Run the local OpenAI-compatible MaramaRoute gateway and browser console.",
211
+ )
212
+ serve.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
213
+ serve.add_argument("--config", type=Path, default=None, help="Gateway backend config JSON.")
214
+ serve.add_argument("--host", type=str, default="127.0.0.1")
215
+ serve.add_argument("--port", type=int, default=8787)
216
+ serve.add_argument("--open", action=argparse.BooleanOptionalAction, default=False)
217
+ serve.add_argument("--smoke", action=argparse.BooleanOptionalAction, default=False)
218
+ serve.set_defaults(handler=_serve)
219
  return parser
220
 
221
 
marama_route/configs/default_policy.yaml CHANGED
@@ -1,26 +1,26 @@
1
- policy_id: abx-sovereigncode-default-v0
2
- default_jurisdiction: NZ
3
- default_resident_regions:
4
- - NZ
5
- high_impact_sensitivity:
6
- - personal
7
- - restricted
8
- - health
9
- - iwi
10
- - taonga
11
- default_obligations:
12
- - write_immutable_audit_record
13
- - preserve_capsule_id_in_agent_trace
14
- - show_diff_before_write_or_commit
15
- denied_without_human_approval:
16
- - delete_file
17
- - execute_shell
18
- - network_export
19
- - publish
20
- - commit
21
- remote_model_rule:
22
- restricted_data_requires_local_or_lumynax: true
23
- training_rule:
24
- requires_capsule_training_allowed: true
25
- export_rule:
26
- requires_capsule_export_allowed: true
 
1
+ policy_id: abx-sovereigncode-default-v0
2
+ default_jurisdiction: NZ
3
+ default_resident_regions:
4
+ - NZ
5
+ high_impact_sensitivity:
6
+ - personal
7
+ - restricted
8
+ - health
9
+ - iwi
10
+ - taonga
11
+ default_obligations:
12
+ - write_immutable_audit_record
13
+ - preserve_capsule_id_in_agent_trace
14
+ - show_diff_before_write_or_commit
15
+ denied_without_human_approval:
16
+ - delete_file
17
+ - execute_shell
18
+ - network_export
19
+ - publish
20
+ - commit
21
+ remote_model_rule:
22
+ restricted_data_requires_local_or_lumynax: true
23
+ training_rule:
24
+ requires_capsule_training_allowed: true
25
+ export_rule:
26
+ requires_capsule_export_allowed: true
marama_route/configs/gateway.local.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mode": "route_only",
3
+ "prompt_retention": "not_stored_by_default",
4
+ "default_timeout_seconds": 120,
5
+ "backends": {
6
+ "example-local-openai-compatible": {
7
+ "type": "openai_compatible",
8
+ "base_url": "http://127.0.0.1:8000/v1",
9
+ "api_key_env": "",
10
+ "model": "local-model-id"
11
+ }
12
+ }
13
+ }
marama_route/configs/provider_aliases.yaml CHANGED
@@ -1,30 +1,30 @@
1
- provider_id: abteex-marama
2
- base_path: /v1
3
- default_model_alias: lumynax/auto
4
- aliases:
5
- lumynax/auto:
6
- task_type: general
7
- requires_local: true
8
- description: Select the best resident LumynaX model for the request.
9
- lumynax/code:
10
- task_type: code
11
- requires_local: true
12
- requires_json: true
13
- description: Prefer coder-tagged LumynaX models with tool and JSON support.
14
- lumynax/reasoning:
15
- task_type: reasoning
16
- requires_local: true
17
- description: Prefer reasoning-tagged models inside residency constraints.
18
- lumynax/multimodal:
19
- task_type: multimodal
20
- requires_local: false
21
- description: Prefer text-plus-image LumynaX models when policy allows.
22
- default_route:
23
- jurisdiction: NZ
24
- data_sensitivity: internal
25
- min_context_tokens: 4096
26
- max_fallbacks: 3
27
- telemetry:
28
- retain_prompt_by_default: false
29
- retain_route_decision_days: 365
30
- hash_request_payload: true
 
1
+ provider_id: abteex-marama
2
+ base_path: /v1
3
+ default_model_alias: lumynax/auto
4
+ aliases:
5
+ lumynax/auto:
6
+ task_type: general
7
+ requires_local: true
8
+ description: Select the best resident LumynaX model for the request.
9
+ lumynax/code:
10
+ task_type: code
11
+ requires_local: true
12
+ requires_json: true
13
+ description: Prefer coder-tagged LumynaX models with tool and JSON support.
14
+ lumynax/reasoning:
15
+ task_type: reasoning
16
+ requires_local: true
17
+ description: Prefer reasoning-tagged models inside residency constraints.
18
+ lumynax/multimodal:
19
+ task_type: multimodal
20
+ requires_local: false
21
+ description: Prefer text-plus-image LumynaX models when policy allows.
22
+ default_route:
23
+ jurisdiction: NZ
24
+ data_sensitivity: internal
25
+ min_context_tokens: 4096
26
+ max_fallbacks: 3
27
+ telemetry:
28
+ retain_prompt_by_default: false
29
+ retain_route_decision_days: 365
30
+ hash_request_payload: true
marama_route/configs/routing_policy.yaml CHANGED
@@ -1,20 +1,20 @@
1
- policy_id: lumynax-marama-route-default-v0
2
- default_jurisdiction: NZ
3
- default_requires_local: true
4
- high_sensitivity:
5
- - personal
6
- - restricted
7
- - health
8
- - iwi
9
- - taonga
10
- required_for_high_sensitivity:
11
- min_sovereignty_tier: 2
12
- residency_must_match_request_jurisdiction: true
13
- retain_prompt_by_default: false
14
- preferred_runtimes:
15
- - llama_cpp
16
- - transformers_multimodal
17
- - python_embedding
18
- fallbacks:
19
- max_default_fallbacks: 3
20
- include_rejection_reasons: true
 
1
+ policy_id: lumynax-marama-route-default-v0
2
+ default_jurisdiction: NZ
3
+ default_requires_local: true
4
+ high_sensitivity:
5
+ - personal
6
+ - restricted
7
+ - health
8
+ - iwi
9
+ - taonga
10
+ required_for_high_sensitivity:
11
+ min_sovereignty_tier: 2
12
+ residency_must_match_request_jurisdiction: true
13
+ retain_prompt_by_default: false
14
+ preferred_runtimes:
15
+ - llama_cpp
16
+ - transformers_multimodal
17
+ - python_embedding
18
+ fallbacks:
19
+ max_default_fallbacks: 3
20
+ include_rejection_reasons: true
marama_route/configs/service.local.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ledger_path": ".sovereigncode/audit.jsonl",
3
+ "default_region": "NZ",
4
+ "fail_closed": true,
5
+ "require_human_review_for": [
6
+ "write_files",
7
+ "execute_shell",
8
+ "network_export",
9
+ "commit",
10
+ "publish",
11
+ "train_model"
12
+ ],
13
+ "marama_route_base_url": "http://127.0.0.1:8787/v1"
14
+ }
marama_route/examples/capsule.personal-sovereignty-profile.json CHANGED
@@ -1,45 +1,45 @@
1
- {
2
- "capsule_id": "cap-personal-profile-001",
3
- "subject_id": "operator-local-profile",
4
- "jurisdiction": "NZ",
5
- "sensitivity": "personal",
6
- "allowed_purposes": [
7
- "personal_memory",
8
- "coding_assistance",
9
- "inference"
10
- ],
11
- "denied_purposes": [
12
- "ad_training",
13
- "third_party_resale",
14
- "public_leaderboard"
15
- ],
16
- "resident_regions": [
17
- "NZ"
18
- ],
19
- "data_classes": [
20
- "personal",
21
- "preferences",
22
- "source_code",
23
- "runtime_logs"
24
- ],
25
- "retention_days": 7,
26
- "export_allowed": false,
27
- "training_allowed": false,
28
- "personal_detail_level": "pseudonymous",
29
- "consent_scopes": [
30
- "personal_memory",
31
- "coding_assistance"
32
- ],
33
- "data_subject_rights": [
34
- "access",
35
- "correction",
36
- "deletion_request",
37
- "processing_objection"
38
- ],
39
- "schema_context": "https://schema.org",
40
- "consent_record": "local-profile-consent-v0",
41
- "metadata": {
42
- "storage": "local_encrypted_profile_store",
43
- "prompt_rule": "summarise preferences without exposing raw personal notes"
44
- }
45
- }
 
1
+ {
2
+ "capsule_id": "cap-personal-profile-001",
3
+ "subject_id": "operator-local-profile",
4
+ "jurisdiction": "NZ",
5
+ "sensitivity": "personal",
6
+ "allowed_purposes": [
7
+ "personal_memory",
8
+ "coding_assistance",
9
+ "inference"
10
+ ],
11
+ "denied_purposes": [
12
+ "ad_training",
13
+ "third_party_resale",
14
+ "public_leaderboard"
15
+ ],
16
+ "resident_regions": [
17
+ "NZ"
18
+ ],
19
+ "data_classes": [
20
+ "personal",
21
+ "preferences",
22
+ "source_code",
23
+ "runtime_logs"
24
+ ],
25
+ "retention_days": 7,
26
+ "export_allowed": false,
27
+ "training_allowed": false,
28
+ "personal_detail_level": "pseudonymous",
29
+ "consent_scopes": [
30
+ "personal_memory",
31
+ "coding_assistance"
32
+ ],
33
+ "data_subject_rights": [
34
+ "access",
35
+ "correction",
36
+ "deletion_request",
37
+ "processing_objection"
38
+ ],
39
+ "schema_context": "https://schema.org",
40
+ "consent_record": "local-profile-consent-v0",
41
+ "metadata": {
42
+ "storage": "local_encrypted_profile_store",
43
+ "prompt_rule": "summarise preferences without exposing raw personal notes"
44
+ }
45
+ }
marama_route/examples/capsule.restricted-nz-code.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "capsule_id": "cap-nz-code-001",
3
- "subject_id": "abx-workspace",
4
- "jurisdiction": "NZ",
5
- "sensitivity": "restricted",
6
- "allowed_purposes": [
7
- "coding_assistance",
8
- "inference",
9
- "test_generation"
10
- ],
11
- "denied_purposes": [
12
- "ad_training",
13
- "third_party_resale"
14
- ],
15
- "resident_regions": [
16
- "NZ"
17
- ],
18
- "data_classes": [
19
- "source_code",
20
- "policy",
21
- "runtime_logs"
22
- ],
23
- "retention_days": 14,
24
- "export_allowed": false,
25
- "training_allowed": false,
26
- "schema_context": "https://schema.org",
27
- "consent_record": "local-operator-policy-v0"
28
- }
 
1
+ {
2
+ "capsule_id": "cap-nz-code-001",
3
+ "subject_id": "abx-workspace",
4
+ "jurisdiction": "NZ",
5
+ "sensitivity": "restricted",
6
+ "allowed_purposes": [
7
+ "coding_assistance",
8
+ "inference",
9
+ "test_generation"
10
+ ],
11
+ "denied_purposes": [
12
+ "ad_training",
13
+ "third_party_resale"
14
+ ],
15
+ "resident_regions": [
16
+ "NZ"
17
+ ],
18
+ "data_classes": [
19
+ "source_code",
20
+ "policy",
21
+ "runtime_logs"
22
+ ],
23
+ "retention_days": 14,
24
+ "export_allowed": false,
25
+ "training_allowed": false,
26
+ "schema_context": "https://schema.org",
27
+ "consent_record": "local-operator-policy-v0"
28
+ }
marama_route/examples/opencode.marama-route.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json",
3
- "provider": {
4
- "abteex-marama": {
5
- "npm": "@ai-sdk/openai-compatible",
6
- "name": "AbteeX MaramaRoute",
7
- "options": {
8
- "baseURL": "http://127.0.0.1:8787/v1",
9
- "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
- "headers": {
11
- "X-AbteeX-Tenant": "{env:ABTEEX_TENANT_ID}",
12
- "X-AbteeX-Workspace-Capsule": "{env:SOVEREIGNCODE_CAPSULE_ID}"
13
- }
14
- },
15
- "models": {
16
- "lumynax/auto": {
17
- "name": "LumynaX Auto Sovereign Route"
18
- },
19
- "lumynax/code": {
20
- "name": "LumynaX Code Route"
21
- },
22
- "lumynax/reasoning": {
23
- "name": "LumynaX Reasoning Route"
24
- }
25
- }
26
- }
27
- }
28
- }
 
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "provider": {
4
+ "abteex-marama": {
5
+ "npm": "@ai-sdk/openai-compatible",
6
+ "name": "AbteeX MaramaRoute",
7
+ "options": {
8
+ "baseURL": "http://127.0.0.1:8787/v1",
9
+ "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
+ "headers": {
11
+ "X-AbteeX-Tenant": "{env:ABTEEX_TENANT_ID}",
12
+ "X-AbteeX-Workspace-Capsule": "{env:SOVEREIGNCODE_CAPSULE_ID}"
13
+ }
14
+ },
15
+ "models": {
16
+ "lumynax/auto": {
17
+ "name": "LumynaX Auto Sovereign Route"
18
+ },
19
+ "lumynax/code": {
20
+ "name": "LumynaX Code Route"
21
+ },
22
+ "lumynax/reasoning": {
23
+ "name": "LumynaX Reasoning Route"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
marama_route/examples/request.allowed-local-edit.json CHANGED
@@ -1,15 +1,15 @@
1
- {
2
- "actor": "developer",
3
- "purpose": "coding_assistance",
4
- "action": "read_context",
5
- "region": "NZ",
6
- "model_id": "AbteeXAILab/lumynax-infused-qwen3-8b-gguf",
7
- "data_classes": [
8
- "source_code"
9
- ],
10
- "tool_name": "workspace_reader",
11
- "writes_files": false,
12
- "exports_data": false,
13
- "trains_model": false,
14
- "human_approved": false
15
- }
 
1
+ {
2
+ "actor": "developer",
3
+ "purpose": "coding_assistance",
4
+ "action": "read_context",
5
+ "region": "NZ",
6
+ "model_id": "AbteeXAILab/lumynax-infused-qwen3-8b-gguf",
7
+ "data_classes": [
8
+ "source_code"
9
+ ],
10
+ "tool_name": "workspace_reader",
11
+ "writes_files": false,
12
+ "exports_data": false,
13
+ "trains_model": false,
14
+ "human_approved": false
15
+ }
marama_route/examples/request.code-restricted.json CHANGED
@@ -1,14 +1,14 @@
1
- {
2
- "prompt": "Refactor this private Python service and explain the diff.",
3
- "task_type": "code",
4
- "modalities": [
5
- "text"
6
- ],
7
- "jurisdiction": "NZ",
8
- "data_sensitivity": "restricted",
9
- "min_context_tokens": 4096,
10
- "requires_local": true,
11
- "requires_tools": false,
12
- "requires_json": true,
13
- "max_fallbacks": 3
14
- }
 
1
+ {
2
+ "prompt": "Refactor this private Python service and explain the diff.",
3
+ "task_type": "code",
4
+ "modalities": [
5
+ "text"
6
+ ],
7
+ "jurisdiction": "NZ",
8
+ "data_sensitivity": "restricted",
9
+ "min_context_tokens": 4096,
10
+ "requires_local": true,
11
+ "requires_tools": false,
12
+ "requires_json": true,
13
+ "max_fallbacks": 3
14
+ }
marama_route/examples/request.denied-training.json CHANGED
@@ -1,15 +1,15 @@
1
- {
2
- "actor": "developer",
3
- "purpose": "coding_assistance",
4
- "action": "train_adapter",
5
- "region": "NZ",
6
- "model_id": "local/lumynax",
7
- "data_classes": [
8
- "source_code"
9
- ],
10
- "tool_name": "trainer",
11
- "writes_files": true,
12
- "exports_data": false,
13
- "trains_model": true,
14
- "human_approved": true
15
- }
 
1
+ {
2
+ "actor": "developer",
3
+ "purpose": "coding_assistance",
4
+ "action": "train_adapter",
5
+ "region": "NZ",
6
+ "model_id": "local/lumynax",
7
+ "data_classes": [
8
+ "source_code"
9
+ ],
10
+ "tool_name": "trainer",
11
+ "writes_files": true,
12
+ "exports_data": false,
13
+ "trains_model": true,
14
+ "human_approved": true
15
+ }
marama_route/examples/request.openai-chat-code.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "model": "lumynax/code",
3
- "messages": [
4
- {
5
- "role": "system",
6
- "content": "You are a governed coding assistant for a New Zealand workspace."
7
- },
8
- {
9
- "role": "user",
10
- "content": "Refactor this private Python repository function and return a JSON diff plan."
11
- }
12
- ],
13
- "response_format": {
14
- "type": "json_object"
15
- },
16
- "tools": [
17
- {
18
- "type": "function",
19
- "function": {
20
- "name": "propose_patch",
21
- "description": "Propose a patch without writing it.",
22
- "parameters": {
23
- "type": "object",
24
- "properties": {
25
- "files": {
26
- "type": "array",
27
- "items": { "type": "string" }
28
- }
29
- }
30
- }
31
- }
32
- }
33
- ],
34
- "route": {
35
- "jurisdiction": "NZ",
36
- "data_sensitivity": "restricted",
37
- "task_type": "code",
38
- "requires_local": true,
39
- "requires_tools": true,
40
- "requires_json": true,
41
- "min_context_tokens": 4096,
42
- "max_fallbacks": 3
43
- }
44
- }
 
1
+ {
2
+ "model": "lumynax/code",
3
+ "messages": [
4
+ {
5
+ "role": "system",
6
+ "content": "You are a governed coding assistant for a New Zealand workspace."
7
+ },
8
+ {
9
+ "role": "user",
10
+ "content": "Refactor this private Python repository function and return a JSON diff plan."
11
+ }
12
+ ],
13
+ "response_format": {
14
+ "type": "json_object"
15
+ },
16
+ "tools": [
17
+ {
18
+ "type": "function",
19
+ "function": {
20
+ "name": "propose_patch",
21
+ "description": "Propose a patch without writing it.",
22
+ "parameters": {
23
+ "type": "object",
24
+ "properties": {
25
+ "files": {
26
+ "type": "array",
27
+ "items": { "type": "string" }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ],
34
+ "route": {
35
+ "jurisdiction": "NZ",
36
+ "data_sensitivity": "restricted",
37
+ "task_type": "code",
38
+ "requires_local": true,
39
+ "requires_tools": true,
40
+ "requires_json": true,
41
+ "min_context_tokens": 4096,
42
+ "max_fallbacks": 3
43
+ }
44
+ }
marama_route/examples/request.personal-memory-read.json CHANGED
@@ -1,19 +1,19 @@
1
- {
2
- "actor": "developer",
3
- "purpose": "personal_memory",
4
- "action": "read_context",
5
- "region": "NZ",
6
- "model_id": "local/lumynax",
7
- "data_classes": [
8
- "personal",
9
- "preferences"
10
- ],
11
- "tool_name": "personal_profile_reader",
12
- "writes_files": false,
13
- "exports_data": false,
14
- "trains_model": false,
15
- "human_approved": false,
16
- "personal_detail_level": "pseudonymous",
17
- "consent_scope": "personal_memory",
18
- "requested_retention_days": 7
19
- }
 
1
+ {
2
+ "actor": "developer",
3
+ "purpose": "personal_memory",
4
+ "action": "read_context",
5
+ "region": "NZ",
6
+ "model_id": "local/lumynax",
7
+ "data_classes": [
8
+ "personal",
9
+ "preferences"
10
+ ],
11
+ "tool_name": "personal_profile_reader",
12
+ "writes_files": false,
13
+ "exports_data": false,
14
+ "trains_model": false,
15
+ "human_approved": false,
16
+ "personal_detail_level": "pseudonymous",
17
+ "consent_scope": "personal_memory",
18
+ "requested_retention_days": 7
19
+ }
marama_route/gateway.py CHANGED
@@ -1,204 +1,204 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- import json
5
- import time
6
- from typing import Any
7
-
8
- from .registry import ModelEndpoint, RoutingRequest
9
- from .router import RouteDecision, SovereignModelRouter
10
-
11
-
12
- def routing_request_from_chat_payload(payload: dict[str, Any]) -> RoutingRequest:
13
- """Translate an OpenAI-compatible chat request into a routing request."""
14
-
15
- route_options = _mapping(
16
- payload.get("route")
17
- or payload.get("routing")
18
- or _mapping(payload.get("metadata")).get("marama_route"),
19
- )
20
- prompt, modalities = _prompt_and_modalities(payload)
21
- tools = payload.get("tools")
22
- response_format = _mapping(payload.get("response_format"))
23
- task_type = str(route_options.get("task_type") or _infer_task_type(prompt, modalities))
24
-
25
- return RoutingRequest.from_payload(
26
- {
27
- "prompt": prompt,
28
- "task_type": task_type,
29
- "modalities": sorted(modalities),
30
- "jurisdiction": route_options.get("jurisdiction", "NZ"),
31
- "data_sensitivity": route_options.get("data_sensitivity", "internal"),
32
- "min_context_tokens": route_options.get("min_context_tokens", 4096),
33
- "requires_local": route_options.get("requires_local", True),
34
- "requires_tools": bool(tools) or bool(route_options.get("requires_tools")),
35
- "requires_json": _requires_json(response_format, route_options),
36
- "license_allowlist": route_options.get("license_allowlist", ()),
37
- "max_fallbacks": route_options.get("max_fallbacks", 3),
38
- "metadata": {
39
- "requested_model": payload.get("model", "auto"),
40
- "source_protocol": "openai_chat_completions",
41
- },
42
- },
43
- )
44
-
45
-
46
- def build_models_response(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
47
- """Return an OpenAI-compatible `/v1/models` listing."""
48
-
49
- return {
50
- "object": "list",
51
- "data": [
52
- {
53
- "id": model.model_id,
54
- "object": "model",
55
- "created": 0,
56
- "owned_by": model.repo_id.split("/", maxsplit=1)[0],
57
- "metadata": {
58
- "repo_id": model.repo_id,
59
- "runtime": model.runtime,
60
- "modalities": list(model.modalities),
61
- "context_tokens": model.context_tokens,
62
- "residency": list(model.residency),
63
- "sovereignty_tier": model.sovereignty_tier,
64
- "supports_tools": model.supports_tools,
65
- "supports_json": model.supports_json,
66
- "tags": list(model.tags),
67
- },
68
- }
69
- for model in models
70
- ],
71
- }
72
-
73
-
74
- def build_chat_route_response(
75
- payload: dict[str, Any],
76
- decision: RouteDecision,
77
- ) -> dict[str, Any]:
78
- """Return a dry-run chat response with route metadata and no generated text."""
79
-
80
- selected = decision.selected_model
81
- created = int(time.time())
82
- request_hash = hashlib.sha256(
83
- json.dumps(payload, sort_keys=True, default=str).encode("utf-8"),
84
- ).hexdigest()
85
- model_id = selected.model_id if selected is not None else str(payload.get("model", ""))
86
-
87
- return {
88
- "id": f"marama-route-{request_hash[:16]}",
89
- "object": "chat.completion",
90
- "created": created,
91
- "model": model_id,
92
- "choices": [
93
- {
94
- "index": 0,
95
- "finish_reason": "route_only",
96
- "message": {
97
- "role": "assistant",
98
- "content": "",
99
- },
100
- },
101
- ],
102
- "usage": {
103
- "prompt_tokens": 0,
104
- "completion_tokens": 0,
105
- "total_tokens": 0,
106
- },
107
- "marama_route": {
108
- "dry_run": True,
109
- "selected_model": selected.to_dict() if selected is not None else None,
110
- "fallback_models": [model.to_dict() for model in decision.fallback_models],
111
- "rejected_count": len(decision.rejected),
112
- "reasons": list(decision.reasons),
113
- "scores": dict(decision.scores),
114
- "request_hash": request_hash,
115
- },
116
- }
117
-
118
-
119
- def route_chat_payload(
120
- payload: dict[str, Any],
121
- models: tuple[ModelEndpoint, ...],
122
- ) -> dict[str, Any]:
123
- request = routing_request_from_chat_payload(payload)
124
- decision = SovereignModelRouter(models).route(request)
125
- return {
126
- "routing_request": request.to_dict(),
127
- "route_decision": decision.to_dict(),
128
- "chat_completion_response": build_chat_route_response(payload, decision),
129
- }
130
-
131
-
132
- def _mapping(value: object) -> dict[str, Any]:
133
- return dict(value) if isinstance(value, dict) else {}
134
-
135
-
136
- def _requires_json(
137
- response_format: dict[str, Any],
138
- route_options: dict[str, Any],
139
- ) -> bool:
140
- if bool(route_options.get("requires_json")):
141
- return True
142
- response_type = str(response_format.get("type", "")).lower()
143
- return response_type in {"json_object", "json_schema"}
144
-
145
-
146
- def _prompt_and_modalities(payload: dict[str, Any]) -> tuple[str, set[str]]:
147
- modalities = {"text"}
148
- pieces: list[str] = []
149
- messages = payload.get("messages")
150
- if isinstance(messages, list):
151
- for message in messages:
152
- if not isinstance(message, dict):
153
- continue
154
- content = message.get("content")
155
- text, content_modalities = _content_text_and_modalities(content)
156
- pieces.append(text)
157
- modalities.update(content_modalities)
158
- elif isinstance(payload.get("prompt"), str):
159
- pieces.append(str(payload["prompt"]))
160
- return "\n".join(piece for piece in pieces if piece), modalities
161
-
162
-
163
- def _content_text_and_modalities(content: object) -> tuple[str, set[str]]:
164
- if isinstance(content, str):
165
- return content, {"text"}
166
- if not isinstance(content, list):
167
- return "", {"text"}
168
-
169
- pieces: list[str] = []
170
- modalities = {"text"}
171
- for part in content:
172
- if not isinstance(part, dict):
173
- continue
174
- part_type = str(part.get("type", "")).lower()
175
- if part_type in {"text", "input_text"}:
176
- pieces.append(str(part.get("text", "")))
177
- elif part_type in {"image", "image_url", "input_image"}:
178
- modalities.add("image")
179
- elif part_type in {"audio", "input_audio"}:
180
- modalities.add("audio")
181
- return "\n".join(piece for piece in pieces if piece), modalities
182
-
183
-
184
- def _infer_task_type(prompt: str, modalities: set[str]) -> str:
185
- prompt_lower = prompt.lower()
186
- if "image" in modalities or "vision" in modalities:
187
- return "multimodal"
188
- code_markers = (
189
- "refactor",
190
- "diff",
191
- "unit test",
192
- "python",
193
- "typescript",
194
- "javascript",
195
- "repository",
196
- "function",
197
- "class ",
198
- "stack trace",
199
- )
200
- if any(marker in prompt_lower for marker in code_markers):
201
- return "code"
202
- if "reason" in prompt_lower or "prove" in prompt_lower:
203
- return "reasoning"
204
- return "general"
 
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import time
6
+ from typing import Any
7
+
8
+ from .registry import ModelEndpoint, RoutingRequest
9
+ from .router import RouteDecision, SovereignModelRouter
10
+
11
+
12
+ def routing_request_from_chat_payload(payload: dict[str, Any]) -> RoutingRequest:
13
+ """Translate an OpenAI-compatible chat request into a routing request."""
14
+
15
+ route_options = _mapping(
16
+ payload.get("route")
17
+ or payload.get("routing")
18
+ or _mapping(payload.get("metadata")).get("marama_route"),
19
+ )
20
+ prompt, modalities = _prompt_and_modalities(payload)
21
+ tools = payload.get("tools")
22
+ response_format = _mapping(payload.get("response_format"))
23
+ task_type = str(route_options.get("task_type") or _infer_task_type(prompt, modalities))
24
+
25
+ return RoutingRequest.from_payload(
26
+ {
27
+ "prompt": prompt,
28
+ "task_type": task_type,
29
+ "modalities": sorted(modalities),
30
+ "jurisdiction": route_options.get("jurisdiction", "NZ"),
31
+ "data_sensitivity": route_options.get("data_sensitivity", "internal"),
32
+ "min_context_tokens": route_options.get("min_context_tokens", 4096),
33
+ "requires_local": route_options.get("requires_local", True),
34
+ "requires_tools": bool(tools) or bool(route_options.get("requires_tools")),
35
+ "requires_json": _requires_json(response_format, route_options),
36
+ "license_allowlist": route_options.get("license_allowlist", ()),
37
+ "max_fallbacks": route_options.get("max_fallbacks", 3),
38
+ "metadata": {
39
+ "requested_model": payload.get("model", "auto"),
40
+ "source_protocol": "openai_chat_completions",
41
+ },
42
+ },
43
+ )
44
+
45
+
46
+ def build_models_response(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
47
+ """Return an OpenAI-compatible `/v1/models` listing."""
48
+
49
+ return {
50
+ "object": "list",
51
+ "data": [
52
+ {
53
+ "id": model.model_id,
54
+ "object": "model",
55
+ "created": 0,
56
+ "owned_by": model.repo_id.split("/", maxsplit=1)[0],
57
+ "metadata": {
58
+ "repo_id": model.repo_id,
59
+ "runtime": model.runtime,
60
+ "modalities": list(model.modalities),
61
+ "context_tokens": model.context_tokens,
62
+ "residency": list(model.residency),
63
+ "sovereignty_tier": model.sovereignty_tier,
64
+ "supports_tools": model.supports_tools,
65
+ "supports_json": model.supports_json,
66
+ "tags": list(model.tags),
67
+ },
68
+ }
69
+ for model in models
70
+ ],
71
+ }
72
+
73
+
74
+ def build_chat_route_response(
75
+ payload: dict[str, Any],
76
+ decision: RouteDecision,
77
+ ) -> dict[str, Any]:
78
+ """Return a dry-run chat response with route metadata and no generated text."""
79
+
80
+ selected = decision.selected_model
81
+ created = int(time.time())
82
+ request_hash = hashlib.sha256(
83
+ json.dumps(payload, sort_keys=True, default=str).encode("utf-8"),
84
+ ).hexdigest()
85
+ model_id = selected.model_id if selected is not None else str(payload.get("model", ""))
86
+
87
+ return {
88
+ "id": f"marama-route-{request_hash[:16]}",
89
+ "object": "chat.completion",
90
+ "created": created,
91
+ "model": model_id,
92
+ "choices": [
93
+ {
94
+ "index": 0,
95
+ "finish_reason": "route_only",
96
+ "message": {
97
+ "role": "assistant",
98
+ "content": "",
99
+ },
100
+ },
101
+ ],
102
+ "usage": {
103
+ "prompt_tokens": 0,
104
+ "completion_tokens": 0,
105
+ "total_tokens": 0,
106
+ },
107
+ "marama_route": {
108
+ "dry_run": True,
109
+ "selected_model": selected.to_dict() if selected is not None else None,
110
+ "fallback_models": [model.to_dict() for model in decision.fallback_models],
111
+ "rejected_count": len(decision.rejected),
112
+ "reasons": list(decision.reasons),
113
+ "scores": dict(decision.scores),
114
+ "request_hash": request_hash,
115
+ },
116
+ }
117
+
118
+
119
+ def route_chat_payload(
120
+ payload: dict[str, Any],
121
+ models: tuple[ModelEndpoint, ...],
122
+ ) -> dict[str, Any]:
123
+ request = routing_request_from_chat_payload(payload)
124
+ decision = SovereignModelRouter(models).route(request)
125
+ return {
126
+ "routing_request": request.to_dict(),
127
+ "route_decision": decision.to_dict(),
128
+ "chat_completion_response": build_chat_route_response(payload, decision),
129
+ }
130
+
131
+
132
+ def _mapping(value: object) -> dict[str, Any]:
133
+ return dict(value) if isinstance(value, dict) else {}
134
+
135
+
136
+ def _requires_json(
137
+ response_format: dict[str, Any],
138
+ route_options: dict[str, Any],
139
+ ) -> bool:
140
+ if bool(route_options.get("requires_json")):
141
+ return True
142
+ response_type = str(response_format.get("type", "")).lower()
143
+ return response_type in {"json_object", "json_schema"}
144
+
145
+
146
+ def _prompt_and_modalities(payload: dict[str, Any]) -> tuple[str, set[str]]:
147
+ modalities = {"text"}
148
+ pieces: list[str] = []
149
+ messages = payload.get("messages")
150
+ if isinstance(messages, list):
151
+ for message in messages:
152
+ if not isinstance(message, dict):
153
+ continue
154
+ content = message.get("content")
155
+ text, content_modalities = _content_text_and_modalities(content)
156
+ pieces.append(text)
157
+ modalities.update(content_modalities)
158
+ elif isinstance(payload.get("prompt"), str):
159
+ pieces.append(str(payload["prompt"]))
160
+ return "\n".join(piece for piece in pieces if piece), modalities
161
+
162
+
163
+ def _content_text_and_modalities(content: object) -> tuple[str, set[str]]:
164
+ if isinstance(content, str):
165
+ return content, {"text"}
166
+ if not isinstance(content, list):
167
+ return "", {"text"}
168
+
169
+ pieces: list[str] = []
170
+ modalities = {"text"}
171
+ for part in content:
172
+ if not isinstance(part, dict):
173
+ continue
174
+ part_type = str(part.get("type", "")).lower()
175
+ if part_type in {"text", "input_text"}:
176
+ pieces.append(str(part.get("text", "")))
177
+ elif part_type in {"image", "image_url", "input_image"}:
178
+ modalities.add("image")
179
+ elif part_type in {"audio", "input_audio"}:
180
+ modalities.add("audio")
181
+ return "\n".join(piece for piece in pieces if piece), modalities
182
+
183
+
184
+ def _infer_task_type(prompt: str, modalities: set[str]) -> str:
185
+ prompt_lower = prompt.lower()
186
+ if "image" in modalities or "vision" in modalities:
187
+ return "multimodal"
188
+ code_markers = (
189
+ "refactor",
190
+ "diff",
191
+ "unit test",
192
+ "python",
193
+ "typescript",
194
+ "javascript",
195
+ "repository",
196
+ "function",
197
+ "class ",
198
+ "stack trace",
199
+ )
200
+ if any(marker in prompt_lower for marker in code_markers):
201
+ return "code"
202
+ if "reason" in prompt_lower or "prove" in prompt_lower:
203
+ return "reasoning"
204
+ return "general"
marama_route/integrations/opencode-compatible-provider.md CHANGED
@@ -1,96 +1,96 @@
1
- # OpenCode-Compatible Provider Integration
2
-
3
- ## Goal
4
-
5
- Make AbteeX SovereignCode usable from OpenCode and similar coding agents without
6
- requiring those tools to understand Data Capsules directly.
7
-
8
- The integration shape is:
9
-
10
- ```text
11
- OpenCode
12
- -> OpenAI-compatible provider config
13
- -> MaramaRoute gateway `/v1`
14
- -> SovereignCode policy and tool broker
15
- -> LumynaX model runtime
16
- ```
17
-
18
- ## Current Compatibility Target
19
-
20
- OpenCode supports custom OpenAI-compatible providers through
21
- `@ai-sdk/openai-compatible` and a provider `baseURL`. OpenRouter exposes an
22
- OpenAI-like chat endpoint at `/api/v1/chat/completions`, with normalized request
23
- and response payloads. MaramaRoute should therefore expose:
24
-
25
- - `GET /v1/models`
26
- - `POST /v1/chat/completions`
27
- - `POST /v1/route`
28
- - `GET /v1/route/{decision_id}`
29
-
30
- References checked on 2026-05-17:
31
-
32
- - https://opencode.ai/docs/providers
33
- - https://openrouter.ai/docs/api-reference/overview/
34
- - https://openrouter.ai/docs/api-reference/chat-completion
35
-
36
- ## OpenCode Provider Config
37
-
38
- Use `examples/opencode.marama-route.json` as the project-local provider file.
39
-
40
- The important fields are:
41
-
42
- | Field | Value |
43
- | --- | --- |
44
- | `provider.abteex-marama.npm` | `@ai-sdk/openai-compatible` |
45
- | `provider.abteex-marama.options.baseURL` | Local or hosted MaramaRoute `/v1` URL |
46
- | `provider.abteex-marama.options.apiKey` | Environment backed key |
47
- | `provider.abteex-marama.models` | LumynaX model aliases exposed by MaramaRoute |
48
-
49
- ## SovereignCode Responsibilities
50
-
51
- OpenCode sends a normal chat request. SovereignCode and MaramaRoute add:
52
-
53
- - capsule resolution from workspace policy files
54
- - purpose and personal-detail checks before prompt assembly
55
- - model routing based on residency, modality, task, and sensitivity
56
- - visible approval gates before file writes, shell commands, network export, or commit
57
- - audit records for policy decisions and route decisions
58
-
59
- ## Workspace Files
60
-
61
- A governed workspace should carry:
62
-
63
- ```text
64
- .sovereigncode/
65
- capsule.json
66
- tenant-policy.yaml
67
- approvals/
68
- audit/
69
- opencode.json
70
- ```
71
-
72
- The agent can start with `capsule.json` and `opencode.json`. The full tool
73
- broker can add approvals and audit persistence in the next build stage.
74
-
75
- ## Minimum Viable Flow
76
-
77
- 1. User opens a project in OpenCode.
78
- 2. OpenCode uses the `abteex-marama` provider.
79
- 3. MaramaRoute dry-runs the chat payload and selects a LumynaX model.
80
- 4. SovereignCode checks the workspace Data Capsule before exposing context.
81
- 5. The coding agent proposes a plan.
82
- 6. File writes require a visible diff and an audit record.
83
- 7. Shell, network, commit, and publish actions require explicit approval.
84
-
85
- ## Similar Clients
86
-
87
- Any client that can point at an OpenAI-compatible endpoint should use the same
88
- gateway:
89
-
90
- | Client type | Expected integration |
91
- | --- | --- |
92
- | OpenCode | `opencode.json` custom provider |
93
- | Continue-style IDE assistant | OpenAI-compatible base URL and model ids |
94
- | Aider-style terminal assistant | OpenAI-compatible base URL and key |
95
- | Internal agent runner | Direct `/v1/route` and `/v1/chat/completions` calls |
96
- | Browser console | Same API behind tenant auth |
 
1
+ # OpenCode-Compatible Provider Integration
2
+
3
+ ## Goal
4
+
5
+ Make AbteeX SovereignCode usable from OpenCode and similar coding agents without
6
+ requiring those tools to understand Data Capsules directly.
7
+
8
+ The integration shape is:
9
+
10
+ ```text
11
+ OpenCode
12
+ -> OpenAI-compatible provider config
13
+ -> MaramaRoute gateway `/v1`
14
+ -> SovereignCode policy and tool broker
15
+ -> LumynaX model runtime
16
+ ```
17
+
18
+ ## Current Compatibility Target
19
+
20
+ OpenCode supports custom OpenAI-compatible providers through
21
+ `@ai-sdk/openai-compatible` and a provider `baseURL`. OpenRouter exposes an
22
+ OpenAI-like chat endpoint at `/api/v1/chat/completions`, with normalized request
23
+ and response payloads. MaramaRoute should therefore expose:
24
+
25
+ - `GET /v1/models`
26
+ - `POST /v1/chat/completions`
27
+ - `POST /v1/route`
28
+ - `GET /v1/route/{decision_id}`
29
+
30
+ References checked on 2026-05-17:
31
+
32
+ - https://opencode.ai/docs/providers
33
+ - https://openrouter.ai/docs/api-reference/overview/
34
+ - https://openrouter.ai/docs/api-reference/chat-completion
35
+
36
+ ## OpenCode Provider Config
37
+
38
+ Use `examples/opencode.marama-route.json` as the project-local provider file.
39
+
40
+ The important fields are:
41
+
42
+ | Field | Value |
43
+ | --- | --- |
44
+ | `provider.abteex-marama.npm` | `@ai-sdk/openai-compatible` |
45
+ | `provider.abteex-marama.options.baseURL` | Local or hosted MaramaRoute `/v1` URL |
46
+ | `provider.abteex-marama.options.apiKey` | Environment backed key |
47
+ | `provider.abteex-marama.models` | LumynaX model aliases exposed by MaramaRoute |
48
+
49
+ ## SovereignCode Responsibilities
50
+
51
+ OpenCode sends a normal chat request. SovereignCode and MaramaRoute add:
52
+
53
+ - capsule resolution from workspace policy files
54
+ - purpose and personal-detail checks before prompt assembly
55
+ - model routing based on residency, modality, task, and sensitivity
56
+ - visible approval gates before file writes, shell commands, network export, or commit
57
+ - audit records for policy decisions and route decisions
58
+
59
+ ## Workspace Files
60
+
61
+ A governed workspace should carry:
62
+
63
+ ```text
64
+ .sovereigncode/
65
+ capsule.json
66
+ tenant-policy.yaml
67
+ approvals/
68
+ audit/
69
+ opencode.json
70
+ ```
71
+
72
+ The agent can start with `capsule.json` and `opencode.json`. The full tool
73
+ broker can add approvals and audit persistence in the next build stage.
74
+
75
+ ## Minimum Viable Flow
76
+
77
+ 1. User opens a project in OpenCode.
78
+ 2. OpenCode uses the `abteex-marama` provider.
79
+ 3. MaramaRoute dry-runs the chat payload and selects a LumynaX model.
80
+ 4. SovereignCode checks the workspace Data Capsule before exposing context.
81
+ 5. The coding agent proposes a plan.
82
+ 6. File writes require a visible diff and an audit record.
83
+ 7. Shell, network, commit, and publish actions require explicit approval.
84
+
85
+ ## Similar Clients
86
+
87
+ Any client that can point at an OpenAI-compatible endpoint should use the same
88
+ gateway:
89
+
90
+ | Client type | Expected integration |
91
+ | --- | --- |
92
+ | OpenCode | `opencode.json` custom provider |
93
+ | Continue-style IDE assistant | OpenAI-compatible base URL and model ids |
94
+ | Aider-style terminal assistant | OpenAI-compatible base URL and key |
95
+ | Internal agent runner | Direct `/v1/route` and `/v1/chat/completions` calls |
96
+ | Browser console | Same API behind tenant auth |
marama_route/integrations/opencode-provider.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "$schema": "https://opencode.ai/config.json",
3
- "provider": {
4
- "abteex-marama": {
5
- "npm": "@ai-sdk/openai-compatible",
6
- "name": "AbteeX MaramaRoute",
7
- "options": {
8
- "baseURL": "http://127.0.0.1:8787/v1",
9
- "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
- "headers": {
11
- "X-AbteeX-Route-Jurisdiction": "NZ",
12
- "X-AbteeX-Route-Sensitivity": "restricted"
13
- }
14
- },
15
- "models": {
16
- "lumynax/auto": {
17
- "name": "LumynaX Auto"
18
- },
19
- "lumynax/code": {
20
- "name": "LumynaX Code"
21
- },
22
- "lumynax/local": {
23
- "name": "LumynaX Local"
24
- }
25
- }
26
- }
27
- }
28
- }
 
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "provider": {
4
+ "abteex-marama": {
5
+ "npm": "@ai-sdk/openai-compatible",
6
+ "name": "AbteeX MaramaRoute",
7
+ "options": {
8
+ "baseURL": "http://127.0.0.1:8787/v1",
9
+ "apiKey": "{env:ABTEEX_MARAMA_API_KEY}",
10
+ "headers": {
11
+ "X-AbteeX-Route-Jurisdiction": "NZ",
12
+ "X-AbteeX-Route-Sensitivity": "restricted"
13
+ }
14
+ },
15
+ "models": {
16
+ "lumynax/auto": {
17
+ "name": "LumynaX Auto"
18
+ },
19
+ "lumynax/code": {
20
+ "name": "LumynaX Code"
21
+ },
22
+ "lumynax/local": {
23
+ "name": "LumynaX Local"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
marama_route/platform.py CHANGED
@@ -1,359 +1,359 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- import json
5
- from collections import Counter
6
- from typing import Any
7
-
8
- from .gateway import build_models_response, route_chat_payload
9
- from .registry import ModelEndpoint, RoutingRequest
10
- from .router import SovereignModelRouter
11
-
12
- DEFAULT_ROUTE_SCENARIOS: tuple[dict[str, Any], ...] = (
13
- {
14
- "name": "Restricted NZ code",
15
- "prompt": "Refactor a private New Zealand Python service and return a JSON diff plan.",
16
- "task_type": "code",
17
- "modalities": ["text"],
18
- "jurisdiction": "NZ",
19
- "data_sensitivity": "restricted",
20
- "min_context_tokens": 4096,
21
- "requires_local": True,
22
- "requires_json": True,
23
- "requires_tools": False,
24
- "max_fallbacks": 3,
25
- },
26
- {
27
- "name": "Personal memory",
28
- "prompt": "Summarise local operator preferences without exposing raw personal notes.",
29
- "task_type": "general",
30
- "modalities": ["text"],
31
- "jurisdiction": "NZ",
32
- "data_sensitivity": "personal",
33
- "min_context_tokens": 4096,
34
- "requires_local": True,
35
- "requires_json": False,
36
- "requires_tools": False,
37
- "max_fallbacks": 3,
38
- },
39
- {
40
- "name": "Vision document",
41
- "prompt": "Read a scanned table image and extract structured rows.",
42
- "task_type": "multimodal",
43
- "modalities": ["text", "image"],
44
- "jurisdiction": "NZ",
45
- "data_sensitivity": "internal",
46
- "min_context_tokens": 4096,
47
- "requires_local": False,
48
- "requires_json": True,
49
- "requires_tools": False,
50
- "max_fallbacks": 3,
51
- },
52
- {
53
- "name": "Reasoning brief",
54
- "prompt": "Reason through a procurement risk register and produce a concise decision memo.",
55
- "task_type": "reasoning",
56
- "modalities": ["text"],
57
- "jurisdiction": "NZ",
58
- "data_sensitivity": "internal",
59
- "min_context_tokens": 8192,
60
- "requires_local": True,
61
- "requires_json": False,
62
- "requires_tools": False,
63
- "max_fallbacks": 3,
64
- },
65
- )
66
-
67
-
68
- def build_registry_analytics(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
69
- runtimes = Counter(model.runtime for model in models)
70
- families = Counter(model.family for model in models)
71
- modalities = Counter(modality for model in models for modality in model.modalities)
72
- tiers = Counter(str(model.sovereignty_tier) for model in models)
73
- resident_nz = sum(1 for model in models if "NZ" in model.residency)
74
- json_ready = sum(1 for model in models if model.supports_json)
75
- tool_ready = sum(1 for model in models if model.supports_tools)
76
- local_runtimes = sum(1 for model in models if _is_local_runtime(model.runtime))
77
- context_values = [model.context_tokens for model in models]
78
- return {
79
- "model_count": len(models),
80
- "resident_nz": resident_nz,
81
- "local_runtimes": local_runtimes,
82
- "json_ready": json_ready,
83
- "tool_ready": tool_ready,
84
- "max_context_tokens": max(context_values) if context_values else 0,
85
- "avg_context_tokens": round(sum(context_values) / len(context_values), 2) if context_values else 0,
86
- "runtimes": dict(sorted(runtimes.items())),
87
- "families": dict(sorted(families.items())),
88
- "modalities": dict(sorted(modalities.items())),
89
- "sovereignty_tiers": dict(sorted(tiers.items())),
90
- "top_models": [model_summary(model) for model in _top_models(models, limit=8)],
91
- }
92
-
93
-
94
- def catalog_models(
95
- models: tuple[ModelEndpoint, ...],
96
- filters: dict[str, Any] | None = None,
97
- ) -> dict[str, Any]:
98
- filters = filters or {}
99
- search = str(filters.get("search") or "").strip().lower()
100
- runtime = str(filters.get("runtime") or "").strip().lower()
101
- family = str(filters.get("family") or "").strip().lower()
102
- modality = str(filters.get("modality") or "").strip().lower()
103
- task_type = str(filters.get("task_type") or "").strip().lower()
104
- jurisdiction = str(filters.get("jurisdiction") or "").strip().upper()
105
- min_context = int(filters.get("min_context_tokens") or 0)
106
- limit = int(filters.get("limit") or 50)
107
- requires_json = bool(filters.get("requires_json", False))
108
- requires_tools = bool(filters.get("requires_tools", False))
109
- requires_local = bool(filters.get("requires_local", False))
110
-
111
- filtered: list[ModelEndpoint] = []
112
- for model in models:
113
- haystack = " ".join(
114
- (
115
- model.model_id,
116
- model.repo_id,
117
- model.family,
118
- model.runtime,
119
- " ".join(model.tags),
120
- ),
121
- ).lower()
122
- if search and search not in haystack:
123
- continue
124
- if runtime and model.runtime.lower() != runtime:
125
- continue
126
- if family and model.family.lower() != family:
127
- continue
128
- if modality and modality not in {item.lower() for item in model.modalities}:
129
- continue
130
- if task_type and not _matches_task(model, task_type):
131
- continue
132
- if jurisdiction and jurisdiction not in model.residency:
133
- continue
134
- if min_context and model.context_tokens < min_context:
135
- continue
136
- if requires_json and not model.supports_json:
137
- continue
138
- if requires_tools and not model.supports_tools:
139
- continue
140
- if requires_local and not _is_local_runtime(model.runtime):
141
- continue
142
- filtered.append(model)
143
-
144
- ranked = sorted(filtered, key=_catalog_sort_key, reverse=True)
145
- return {
146
- "ok": True,
147
- "count": len(ranked),
148
- "filters": filters,
149
- "models": [model_summary(model) for model in ranked[:limit]],
150
- }
151
-
152
-
153
- def compare_models(
154
- models: tuple[ModelEndpoint, ...],
155
- model_ids: list[str],
156
- request_payload: dict[str, Any] | None = None,
157
- ) -> dict[str, Any]:
158
- index = {model.model_id: model for model in models}
159
- selected = [index[model_id] for model_id in model_ids if model_id in index]
160
- missing = [model_id for model_id in model_ids if model_id not in index]
161
- request = RoutingRequest.from_payload(request_payload or DEFAULT_ROUTE_SCENARIOS[0])
162
- route_scores = SovereignModelRouter(tuple(selected)).route(request).scores if selected else {}
163
- rows = []
164
- for model in selected:
165
- row = model_summary(model)
166
- row["route_score"] = route_scores.get(model.model_id)
167
- row["operator_score"] = _operator_score(model)
168
- rows.append(row)
169
- winner = max(rows, key=lambda item: (item.get("route_score") or -1, item["operator_score"]), default=None)
170
- return {
171
- "ok": bool(rows),
172
- "missing": missing,
173
- "request": request.to_dict(),
174
- "winner": winner,
175
- "models": rows,
176
- }
177
-
178
-
179
- def route_scenario_matrix(
180
- models: tuple[ModelEndpoint, ...],
181
- scenarios: list[dict[str, Any]] | None = None,
182
- ) -> dict[str, Any]:
183
- router = SovereignModelRouter(models)
184
- rows = []
185
- for scenario in scenarios or [dict(item) for item in DEFAULT_ROUTE_SCENARIOS]:
186
- request = RoutingRequest.from_payload(scenario)
187
- decision = router.route(request)
188
- selected = decision.selected_model
189
- rows.append(
190
- {
191
- "name": scenario.get("name", request.task_type),
192
- "ok": selected is not None,
193
- "task_type": request.task_type,
194
- "sensitivity": request.data_sensitivity,
195
- "selected_model": selected.model_id if selected else None,
196
- "runtime": selected.runtime if selected else None,
197
- "fallback_count": len(decision.fallback_models),
198
- "rejected_count": len(decision.rejected),
199
- "reasons": list(decision.reasons),
200
- },
201
- )
202
- return {"ok": all(row["ok"] for row in rows), "scenarios": rows}
203
-
204
-
205
- def build_opencode_provider_config(
206
- models: tuple[ModelEndpoint, ...],
207
- *,
208
- base_url: str = "http://127.0.0.1:8787/v1",
209
- provider_id: str = "abteex-marama",
210
- ) -> dict[str, Any]:
211
- route = SovereignModelRouter(models).route(RoutingRequest.from_payload(DEFAULT_ROUTE_SCENARIOS[0]))
212
- default_model = route.selected_model or (_top_models(models, limit=1)[0] if models else None)
213
- catalog = _top_models(models, limit=14)
214
- model_entries = {
215
- model.model_id: {
216
- "name": model.model_id,
217
- "context": model.context_tokens,
218
- "modalities": list(model.modalities),
219
- "residency": list(model.residency),
220
- "runtime": model.runtime,
221
- }
222
- for model in catalog
223
- }
224
- return {
225
- "$schema": "https://opencode.ai/config.json",
226
- "provider": {
227
- provider_id: {
228
- "name": "AbteeX MaramaRoute",
229
- "npm": "@ai-sdk/openai-compatible",
230
- "options": {
231
- "baseURL": base_url,
232
- "apiKey": "${ABTEEX_MARAMA_API_KEY:-local-dev}",
233
- },
234
- "models": model_entries,
235
- },
236
- },
237
- "model": f"{provider_id}/{default_model.model_id}" if default_model else "",
238
- "small_model": f"{provider_id}/{catalog[-1].model_id}" if catalog else "",
239
- }
240
-
241
-
242
- def route_receipt(payload: dict[str, Any], route_result: dict[str, Any]) -> dict[str, Any]:
243
- selected = route_result.get("route_decision", {}).get("selected_model")
244
- receipt_payload = {
245
- "request": payload,
246
- "selected_model_id": selected.get("model_id") if isinstance(selected, dict) else None,
247
- "rejected_count": len(route_result.get("route_decision", {}).get("rejected", [])),
248
- }
249
- digest = hashlib.sha256(
250
- json.dumps(receipt_payload, sort_keys=True, default=str).encode("utf-8"),
251
- ).hexdigest()
252
- return {
253
- "receipt_id": f"marama-{digest[:16]}",
254
- "request_hash": digest,
255
- "selected_model": receipt_payload["selected_model_id"],
256
- "prompt_retention": "not_stored_by_default",
257
- "audit_fields": [
258
- "request_hash",
259
- "selected_model",
260
- "fallback_models",
261
- "rejected_count",
262
- "residency",
263
- "runtime",
264
- ],
265
- }
266
-
267
-
268
- def route_or_chat_payload(payload: dict[str, Any], models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
269
- if "messages" in payload:
270
- result = route_chat_payload(payload, models)
271
- selected = result["route_decision"]["selected_model"]
272
- result = {"ok": selected is not None, "mode": "openai_chat_dry_run", **result}
273
- else:
274
- request = RoutingRequest.from_payload(payload)
275
- decision = SovereignModelRouter(models).route(request)
276
- result = {
277
- "ok": decision.selected_model is not None,
278
- "mode": "route",
279
- "routing_request": request.to_dict(),
280
- "route_decision": decision.to_dict(),
281
- }
282
- result["receipt"] = route_receipt(payload, result)
283
- return result
284
-
285
-
286
- def build_models_api(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
287
- response = build_models_response(models)
288
- response["analytics"] = build_registry_analytics(models)
289
- return response
290
-
291
-
292
- def model_summary(model: ModelEndpoint) -> dict[str, Any]:
293
- return {
294
- "model_id": model.model_id,
295
- "repo_id": model.repo_id,
296
- "family": model.family,
297
- "runtime": model.runtime,
298
- "modalities": list(model.modalities),
299
- "context_tokens": model.context_tokens,
300
- "residency": list(model.residency),
301
- "license_id": model.license_id,
302
- "active_params_b": model.active_params_b,
303
- "total_params_b": model.total_params_b,
304
- "quality_rank": model.quality_rank,
305
- "cost_rank": model.cost_rank,
306
- "sovereignty_tier": model.sovereignty_tier,
307
- "supports_json": model.supports_json,
308
- "supports_tools": model.supports_tools,
309
- "tags": list(model.tags),
310
- "operator_score": _operator_score(model),
311
- }
312
-
313
-
314
- def scenario_presets() -> list[dict[str, Any]]:
315
- return [dict(item) for item in DEFAULT_ROUTE_SCENARIOS]
316
-
317
-
318
- def _top_models(models: tuple[ModelEndpoint, ...], *, limit: int) -> list[ModelEndpoint]:
319
- return sorted(models, key=_catalog_sort_key, reverse=True)[:limit]
320
-
321
-
322
- def _catalog_sort_key(model: ModelEndpoint) -> tuple[float, int, str]:
323
- return (_operator_score(model), model.context_tokens, model.model_id)
324
-
325
-
326
- def _operator_score(model: ModelEndpoint) -> float:
327
- score = 0.0
328
- if "NZ" in model.residency:
329
- score += 25
330
- if _is_local_runtime(model.runtime):
331
- score += 15
332
- score += model.sovereignty_tier * 10
333
- score += max(0, 10 - model.quality_rank) * 3
334
- score -= model.cost_rank
335
- if model.supports_json:
336
- score += 5
337
- if model.supports_tools:
338
- score += 5
339
- if model.context_tokens >= 32768:
340
- score += 6
341
- elif model.context_tokens >= 8192:
342
- score += 3
343
- return round(score, 2)
344
-
345
-
346
- def _matches_task(model: ModelEndpoint, task_type: str) -> bool:
347
- tags = set(model.tags)
348
- if task_type in tags or task_type in model.family.lower() or task_type in model.model_id.lower():
349
- return True
350
- if task_type == "code":
351
- return "coder" in tags or "coder" in model.model_id.lower()
352
- if task_type == "multimodal":
353
- return "image" in model.modalities or "multimodal" in tags
354
- return False
355
-
356
-
357
- def _is_local_runtime(runtime: str) -> bool:
358
- value = runtime.lower()
359
- return value in {"llama_cpp", "gguf", "transformers", "sentence_transformers"} or "local" in value
 
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ from collections import Counter
6
+ from typing import Any
7
+
8
+ from .gateway import build_models_response, route_chat_payload
9
+ from .registry import ModelEndpoint, RoutingRequest
10
+ from .router import SovereignModelRouter
11
+
12
+ DEFAULT_ROUTE_SCENARIOS: tuple[dict[str, Any], ...] = (
13
+ {
14
+ "name": "Restricted NZ code",
15
+ "prompt": "Refactor a private New Zealand Python service and return a JSON diff plan.",
16
+ "task_type": "code",
17
+ "modalities": ["text"],
18
+ "jurisdiction": "NZ",
19
+ "data_sensitivity": "restricted",
20
+ "min_context_tokens": 4096,
21
+ "requires_local": True,
22
+ "requires_json": True,
23
+ "requires_tools": False,
24
+ "max_fallbacks": 3,
25
+ },
26
+ {
27
+ "name": "Personal memory",
28
+ "prompt": "Summarise local operator preferences without exposing raw personal notes.",
29
+ "task_type": "general",
30
+ "modalities": ["text"],
31
+ "jurisdiction": "NZ",
32
+ "data_sensitivity": "personal",
33
+ "min_context_tokens": 4096,
34
+ "requires_local": True,
35
+ "requires_json": False,
36
+ "requires_tools": False,
37
+ "max_fallbacks": 3,
38
+ },
39
+ {
40
+ "name": "Vision document",
41
+ "prompt": "Read a scanned table image and extract structured rows.",
42
+ "task_type": "multimodal",
43
+ "modalities": ["text", "image"],
44
+ "jurisdiction": "NZ",
45
+ "data_sensitivity": "internal",
46
+ "min_context_tokens": 4096,
47
+ "requires_local": False,
48
+ "requires_json": True,
49
+ "requires_tools": False,
50
+ "max_fallbacks": 3,
51
+ },
52
+ {
53
+ "name": "Reasoning brief",
54
+ "prompt": "Reason through a procurement risk register and produce a concise decision memo.",
55
+ "task_type": "reasoning",
56
+ "modalities": ["text"],
57
+ "jurisdiction": "NZ",
58
+ "data_sensitivity": "internal",
59
+ "min_context_tokens": 8192,
60
+ "requires_local": True,
61
+ "requires_json": False,
62
+ "requires_tools": False,
63
+ "max_fallbacks": 3,
64
+ },
65
+ )
66
+
67
+
68
+ def build_registry_analytics(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
69
+ runtimes = Counter(model.runtime for model in models)
70
+ families = Counter(model.family for model in models)
71
+ modalities = Counter(modality for model in models for modality in model.modalities)
72
+ tiers = Counter(str(model.sovereignty_tier) for model in models)
73
+ resident_nz = sum(1 for model in models if "NZ" in model.residency)
74
+ json_ready = sum(1 for model in models if model.supports_json)
75
+ tool_ready = sum(1 for model in models if model.supports_tools)
76
+ local_runtimes = sum(1 for model in models if _is_local_runtime(model.runtime))
77
+ context_values = [model.context_tokens for model in models]
78
+ return {
79
+ "model_count": len(models),
80
+ "resident_nz": resident_nz,
81
+ "local_runtimes": local_runtimes,
82
+ "json_ready": json_ready,
83
+ "tool_ready": tool_ready,
84
+ "max_context_tokens": max(context_values) if context_values else 0,
85
+ "avg_context_tokens": round(sum(context_values) / len(context_values), 2) if context_values else 0,
86
+ "runtimes": dict(sorted(runtimes.items())),
87
+ "families": dict(sorted(families.items())),
88
+ "modalities": dict(sorted(modalities.items())),
89
+ "sovereignty_tiers": dict(sorted(tiers.items())),
90
+ "top_models": [model_summary(model) for model in _top_models(models, limit=8)],
91
+ }
92
+
93
+
94
+ def catalog_models(
95
+ models: tuple[ModelEndpoint, ...],
96
+ filters: dict[str, Any] | None = None,
97
+ ) -> dict[str, Any]:
98
+ filters = filters or {}
99
+ search = str(filters.get("search") or "").strip().lower()
100
+ runtime = str(filters.get("runtime") or "").strip().lower()
101
+ family = str(filters.get("family") or "").strip().lower()
102
+ modality = str(filters.get("modality") or "").strip().lower()
103
+ task_type = str(filters.get("task_type") or "").strip().lower()
104
+ jurisdiction = str(filters.get("jurisdiction") or "").strip().upper()
105
+ min_context = int(filters.get("min_context_tokens") or 0)
106
+ limit = int(filters.get("limit") or 50)
107
+ requires_json = bool(filters.get("requires_json", False))
108
+ requires_tools = bool(filters.get("requires_tools", False))
109
+ requires_local = bool(filters.get("requires_local", False))
110
+
111
+ filtered: list[ModelEndpoint] = []
112
+ for model in models:
113
+ haystack = " ".join(
114
+ (
115
+ model.model_id,
116
+ model.repo_id,
117
+ model.family,
118
+ model.runtime,
119
+ " ".join(model.tags),
120
+ ),
121
+ ).lower()
122
+ if search and search not in haystack:
123
+ continue
124
+ if runtime and model.runtime.lower() != runtime:
125
+ continue
126
+ if family and model.family.lower() != family:
127
+ continue
128
+ if modality and modality not in {item.lower() for item in model.modalities}:
129
+ continue
130
+ if task_type and not _matches_task(model, task_type):
131
+ continue
132
+ if jurisdiction and jurisdiction not in model.residency:
133
+ continue
134
+ if min_context and model.context_tokens < min_context:
135
+ continue
136
+ if requires_json and not model.supports_json:
137
+ continue
138
+ if requires_tools and not model.supports_tools:
139
+ continue
140
+ if requires_local and not _is_local_runtime(model.runtime):
141
+ continue
142
+ filtered.append(model)
143
+
144
+ ranked = sorted(filtered, key=_catalog_sort_key, reverse=True)
145
+ return {
146
+ "ok": True,
147
+ "count": len(ranked),
148
+ "filters": filters,
149
+ "models": [model_summary(model) for model in ranked[:limit]],
150
+ }
151
+
152
+
153
+ def compare_models(
154
+ models: tuple[ModelEndpoint, ...],
155
+ model_ids: list[str],
156
+ request_payload: dict[str, Any] | None = None,
157
+ ) -> dict[str, Any]:
158
+ index = {model.model_id: model for model in models}
159
+ selected = [index[model_id] for model_id in model_ids if model_id in index]
160
+ missing = [model_id for model_id in model_ids if model_id not in index]
161
+ request = RoutingRequest.from_payload(request_payload or DEFAULT_ROUTE_SCENARIOS[0])
162
+ route_scores = SovereignModelRouter(tuple(selected)).route(request).scores if selected else {}
163
+ rows = []
164
+ for model in selected:
165
+ row = model_summary(model)
166
+ row["route_score"] = route_scores.get(model.model_id)
167
+ row["operator_score"] = _operator_score(model)
168
+ rows.append(row)
169
+ winner = max(rows, key=lambda item: (item.get("route_score") or -1, item["operator_score"]), default=None)
170
+ return {
171
+ "ok": bool(rows),
172
+ "missing": missing,
173
+ "request": request.to_dict(),
174
+ "winner": winner,
175
+ "models": rows,
176
+ }
177
+
178
+
179
+ def route_scenario_matrix(
180
+ models: tuple[ModelEndpoint, ...],
181
+ scenarios: list[dict[str, Any]] | None = None,
182
+ ) -> dict[str, Any]:
183
+ router = SovereignModelRouter(models)
184
+ rows = []
185
+ for scenario in scenarios or [dict(item) for item in DEFAULT_ROUTE_SCENARIOS]:
186
+ request = RoutingRequest.from_payload(scenario)
187
+ decision = router.route(request)
188
+ selected = decision.selected_model
189
+ rows.append(
190
+ {
191
+ "name": scenario.get("name", request.task_type),
192
+ "ok": selected is not None,
193
+ "task_type": request.task_type,
194
+ "sensitivity": request.data_sensitivity,
195
+ "selected_model": selected.model_id if selected else None,
196
+ "runtime": selected.runtime if selected else None,
197
+ "fallback_count": len(decision.fallback_models),
198
+ "rejected_count": len(decision.rejected),
199
+ "reasons": list(decision.reasons),
200
+ },
201
+ )
202
+ return {"ok": all(row["ok"] for row in rows), "scenarios": rows}
203
+
204
+
205
+ def build_opencode_provider_config(
206
+ models: tuple[ModelEndpoint, ...],
207
+ *,
208
+ base_url: str = "http://127.0.0.1:8787/v1",
209
+ provider_id: str = "abteex-marama",
210
+ ) -> dict[str, Any]:
211
+ route = SovereignModelRouter(models).route(RoutingRequest.from_payload(DEFAULT_ROUTE_SCENARIOS[0]))
212
+ default_model = route.selected_model or (_top_models(models, limit=1)[0] if models else None)
213
+ catalog = _top_models(models, limit=14)
214
+ model_entries = {
215
+ model.model_id: {
216
+ "name": model.model_id,
217
+ "context": model.context_tokens,
218
+ "modalities": list(model.modalities),
219
+ "residency": list(model.residency),
220
+ "runtime": model.runtime,
221
+ }
222
+ for model in catalog
223
+ }
224
+ return {
225
+ "$schema": "https://opencode.ai/config.json",
226
+ "provider": {
227
+ provider_id: {
228
+ "name": "AbteeX MaramaRoute",
229
+ "npm": "@ai-sdk/openai-compatible",
230
+ "options": {
231
+ "baseURL": base_url,
232
+ "apiKey": "${ABTEEX_MARAMA_API_KEY:-local-dev}",
233
+ },
234
+ "models": model_entries,
235
+ },
236
+ },
237
+ "model": f"{provider_id}/{default_model.model_id}" if default_model else "",
238
+ "small_model": f"{provider_id}/{catalog[-1].model_id}" if catalog else "",
239
+ }
240
+
241
+
242
+ def route_receipt(payload: dict[str, Any], route_result: dict[str, Any]) -> dict[str, Any]:
243
+ selected = route_result.get("route_decision", {}).get("selected_model")
244
+ receipt_payload = {
245
+ "request": payload,
246
+ "selected_model_id": selected.get("model_id") if isinstance(selected, dict) else None,
247
+ "rejected_count": len(route_result.get("route_decision", {}).get("rejected", [])),
248
+ }
249
+ digest = hashlib.sha256(
250
+ json.dumps(receipt_payload, sort_keys=True, default=str).encode("utf-8"),
251
+ ).hexdigest()
252
+ return {
253
+ "receipt_id": f"marama-{digest[:16]}",
254
+ "request_hash": digest,
255
+ "selected_model": receipt_payload["selected_model_id"],
256
+ "prompt_retention": "not_stored_by_default",
257
+ "audit_fields": [
258
+ "request_hash",
259
+ "selected_model",
260
+ "fallback_models",
261
+ "rejected_count",
262
+ "residency",
263
+ "runtime",
264
+ ],
265
+ }
266
+
267
+
268
+ def route_or_chat_payload(payload: dict[str, Any], models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
269
+ if "messages" in payload:
270
+ result = route_chat_payload(payload, models)
271
+ selected = result["route_decision"]["selected_model"]
272
+ result = {"ok": selected is not None, "mode": "openai_chat_dry_run", **result}
273
+ else:
274
+ request = RoutingRequest.from_payload(payload)
275
+ decision = SovereignModelRouter(models).route(request)
276
+ result = {
277
+ "ok": decision.selected_model is not None,
278
+ "mode": "route",
279
+ "routing_request": request.to_dict(),
280
+ "route_decision": decision.to_dict(),
281
+ }
282
+ result["receipt"] = route_receipt(payload, result)
283
+ return result
284
+
285
+
286
+ def build_models_api(models: tuple[ModelEndpoint, ...]) -> dict[str, Any]:
287
+ response = build_models_response(models)
288
+ response["analytics"] = build_registry_analytics(models)
289
+ return response
290
+
291
+
292
+ def model_summary(model: ModelEndpoint) -> dict[str, Any]:
293
+ return {
294
+ "model_id": model.model_id,
295
+ "repo_id": model.repo_id,
296
+ "family": model.family,
297
+ "runtime": model.runtime,
298
+ "modalities": list(model.modalities),
299
+ "context_tokens": model.context_tokens,
300
+ "residency": list(model.residency),
301
+ "license_id": model.license_id,
302
+ "active_params_b": model.active_params_b,
303
+ "total_params_b": model.total_params_b,
304
+ "quality_rank": model.quality_rank,
305
+ "cost_rank": model.cost_rank,
306
+ "sovereignty_tier": model.sovereignty_tier,
307
+ "supports_json": model.supports_json,
308
+ "supports_tools": model.supports_tools,
309
+ "tags": list(model.tags),
310
+ "operator_score": _operator_score(model),
311
+ }
312
+
313
+
314
+ def scenario_presets() -> list[dict[str, Any]]:
315
+ return [dict(item) for item in DEFAULT_ROUTE_SCENARIOS]
316
+
317
+
318
+ def _top_models(models: tuple[ModelEndpoint, ...], *, limit: int) -> list[ModelEndpoint]:
319
+ return sorted(models, key=_catalog_sort_key, reverse=True)[:limit]
320
+
321
+
322
+ def _catalog_sort_key(model: ModelEndpoint) -> tuple[float, int, str]:
323
+ return (_operator_score(model), model.context_tokens, model.model_id)
324
+
325
+
326
+ def _operator_score(model: ModelEndpoint) -> float:
327
+ score = 0.0
328
+ if "NZ" in model.residency:
329
+ score += 25
330
+ if _is_local_runtime(model.runtime):
331
+ score += 15
332
+ score += model.sovereignty_tier * 10
333
+ score += max(0, 10 - model.quality_rank) * 3
334
+ score -= model.cost_rank
335
+ if model.supports_json:
336
+ score += 5
337
+ if model.supports_tools:
338
+ score += 5
339
+ if model.context_tokens >= 32768:
340
+ score += 6
341
+ elif model.context_tokens >= 8192:
342
+ score += 3
343
+ return round(score, 2)
344
+
345
+
346
+ def _matches_task(model: ModelEndpoint, task_type: str) -> bool:
347
+ tags = set(model.tags)
348
+ if task_type in tags or task_type in model.family.lower() or task_type in model.model_id.lower():
349
+ return True
350
+ if task_type == "code":
351
+ return "coder" in tags or "coder" in model.model_id.lower()
352
+ if task_type == "multimodal":
353
+ return "image" in model.modalities or "multimodal" in tags
354
+ return False
355
+
356
+
357
+ def _is_local_runtime(runtime: str) -> bool:
358
+ value = runtime.lower()
359
+ return value in {"llama_cpp", "gguf", "transformers", "sentence_transformers"} or "local" in value
marama_route/registry.py CHANGED
@@ -1,150 +1,150 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from dataclasses import dataclass, field
5
- from pathlib import Path
6
- from typing import Any
7
-
8
-
9
- def _text_tuple(value: object, *, default: tuple[str, ...] = ()) -> tuple[str, ...]:
10
- if value in (None, ""):
11
- return default
12
- if isinstance(value, str):
13
- return (value,)
14
- if isinstance(value, (list, tuple, set)):
15
- return tuple(str(item).strip() for item in value if str(item).strip())
16
- return (str(value).strip(),)
17
-
18
-
19
- @dataclass(frozen=True, slots=True)
20
- class ModelEndpoint:
21
- model_id: str
22
- repo_id: str
23
- family: str
24
- runtime: str
25
- modalities: tuple[str, ...] = ("text",)
26
- context_tokens: int = 4096
27
- jurisdiction: str = "NZ"
28
- residency: tuple[str, ...] = ("NZ",)
29
- license_id: str = "see_model_card"
30
- quantization: str = "see_manifest"
31
- primary_artifact: str = ""
32
- active_params_b: float | None = None
33
- total_params_b: float | None = None
34
- quality_rank: int = 5
35
- cost_rank: int = 5
36
- sovereignty_tier: int = 2
37
- supports_tools: bool = False
38
- supports_json: bool = False
39
- tags: tuple[str, ...] = ()
40
- metadata: dict[str, Any] = field(default_factory=dict)
41
-
42
- @classmethod
43
- def from_payload(cls, payload: dict[str, Any]) -> ModelEndpoint:
44
- return cls(
45
- model_id=str(payload.get("model_id") or payload.get("repo_id") or ""),
46
- repo_id=str(payload.get("repo_id") or payload.get("model_id") or ""),
47
- family=str(payload.get("family") or "lumynax"),
48
- runtime=str(payload.get("runtime") or "llama_cpp"),
49
- modalities=_text_tuple(payload.get("modalities"), default=("text",)),
50
- context_tokens=int(payload.get("context_tokens") or 4096),
51
- jurisdiction=str(payload.get("jurisdiction") or "NZ").upper(),
52
- residency=tuple(str(item).upper() for item in _text_tuple(payload.get("residency"), default=("NZ",))),
53
- license_id=str(payload.get("license_id") or "see_model_card"),
54
- quantization=str(payload.get("quantization") or "see_manifest"),
55
- primary_artifact=str(payload.get("primary_artifact") or ""),
56
- active_params_b=(
57
- float(payload["active_params_b"]) if payload.get("active_params_b") is not None else None
58
- ),
59
- total_params_b=(
60
- float(payload["total_params_b"]) if payload.get("total_params_b") is not None else None
61
- ),
62
- quality_rank=int(payload.get("quality_rank") or 5),
63
- cost_rank=int(payload.get("cost_rank") or 5),
64
- sovereignty_tier=int(payload.get("sovereignty_tier") or 2),
65
- supports_tools=bool(payload.get("supports_tools", False)),
66
- supports_json=bool(payload.get("supports_json", False)),
67
- tags=tuple(item.lower() for item in _text_tuple(payload.get("tags"))),
68
- metadata=dict(payload.get("metadata") or {}),
69
- )
70
-
71
- def to_dict(self) -> dict[str, Any]:
72
- return {
73
- "model_id": self.model_id,
74
- "repo_id": self.repo_id,
75
- "family": self.family,
76
- "runtime": self.runtime,
77
- "modalities": list(self.modalities),
78
- "context_tokens": self.context_tokens,
79
- "jurisdiction": self.jurisdiction,
80
- "residency": list(self.residency),
81
- "license_id": self.license_id,
82
- "quantization": self.quantization,
83
- "primary_artifact": self.primary_artifact,
84
- "active_params_b": self.active_params_b,
85
- "total_params_b": self.total_params_b,
86
- "quality_rank": self.quality_rank,
87
- "cost_rank": self.cost_rank,
88
- "sovereignty_tier": self.sovereignty_tier,
89
- "supports_tools": self.supports_tools,
90
- "supports_json": self.supports_json,
91
- "tags": list(self.tags),
92
- "metadata": dict(self.metadata),
93
- }
94
-
95
-
96
- @dataclass(frozen=True, slots=True)
97
- class RoutingRequest:
98
- prompt: str
99
- task_type: str = "general"
100
- modalities: tuple[str, ...] = ("text",)
101
- jurisdiction: str = "NZ"
102
- data_sensitivity: str = "internal"
103
- min_context_tokens: int = 4096
104
- requires_local: bool = True
105
- requires_tools: bool = False
106
- requires_json: bool = False
107
- license_allowlist: tuple[str, ...] = ()
108
- max_fallbacks: int = 3
109
- metadata: dict[str, Any] = field(default_factory=dict)
110
-
111
- @classmethod
112
- def from_payload(cls, payload: dict[str, Any]) -> RoutingRequest:
113
- return cls(
114
- prompt=str(payload.get("prompt") or ""),
115
- task_type=str(payload.get("task_type") or "general").lower(),
116
- modalities=tuple(item.lower() for item in _text_tuple(payload.get("modalities"), default=("text",))),
117
- jurisdiction=str(payload.get("jurisdiction") or "NZ").upper(),
118
- data_sensitivity=str(payload.get("data_sensitivity") or "internal").lower(),
119
- min_context_tokens=int(payload.get("min_context_tokens") or 4096),
120
- requires_local=bool(payload.get("requires_local", True)),
121
- requires_tools=bool(payload.get("requires_tools", False)),
122
- requires_json=bool(payload.get("requires_json", False)),
123
- license_allowlist=tuple(item.lower() for item in _text_tuple(payload.get("license_allowlist"))),
124
- max_fallbacks=int(payload.get("max_fallbacks") or 3),
125
- metadata=dict(payload.get("metadata") or {}),
126
- )
127
-
128
- def to_dict(self) -> dict[str, Any]:
129
- return {
130
- "prompt": self.prompt,
131
- "task_type": self.task_type,
132
- "modalities": list(self.modalities),
133
- "jurisdiction": self.jurisdiction,
134
- "data_sensitivity": self.data_sensitivity,
135
- "min_context_tokens": self.min_context_tokens,
136
- "requires_local": self.requires_local,
137
- "requires_tools": self.requires_tools,
138
- "requires_json": self.requires_json,
139
- "license_allowlist": list(self.license_allowlist),
140
- "max_fallbacks": self.max_fallbacks,
141
- "metadata": dict(self.metadata),
142
- }
143
-
144
-
145
- def load_model_registry(path: Path) -> tuple[ModelEndpoint, ...]:
146
- payload = json.loads(path.read_text(encoding="utf-8-sig"))
147
- raw_models = payload.get("models") if isinstance(payload, dict) else payload
148
- if not isinstance(raw_models, list):
149
- raise ValueError(f"Expected model list in {path}")
150
- return tuple(ModelEndpoint.from_payload(item) for item in raw_models if isinstance(item, dict))
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ def _text_tuple(value: object, *, default: tuple[str, ...] = ()) -> tuple[str, ...]:
10
+ if value in (None, ""):
11
+ return default
12
+ if isinstance(value, str):
13
+ return (value,)
14
+ if isinstance(value, (list, tuple, set)):
15
+ return tuple(str(item).strip() for item in value if str(item).strip())
16
+ return (str(value).strip(),)
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class ModelEndpoint:
21
+ model_id: str
22
+ repo_id: str
23
+ family: str
24
+ runtime: str
25
+ modalities: tuple[str, ...] = ("text",)
26
+ context_tokens: int = 4096
27
+ jurisdiction: str = "NZ"
28
+ residency: tuple[str, ...] = ("NZ",)
29
+ license_id: str = "see_model_card"
30
+ quantization: str = "see_manifest"
31
+ primary_artifact: str = ""
32
+ active_params_b: float | None = None
33
+ total_params_b: float | None = None
34
+ quality_rank: int = 5
35
+ cost_rank: int = 5
36
+ sovereignty_tier: int = 2
37
+ supports_tools: bool = False
38
+ supports_json: bool = False
39
+ tags: tuple[str, ...] = ()
40
+ metadata: dict[str, Any] = field(default_factory=dict)
41
+
42
+ @classmethod
43
+ def from_payload(cls, payload: dict[str, Any]) -> ModelEndpoint:
44
+ return cls(
45
+ model_id=str(payload.get("model_id") or payload.get("repo_id") or ""),
46
+ repo_id=str(payload.get("repo_id") or payload.get("model_id") or ""),
47
+ family=str(payload.get("family") or "lumynax"),
48
+ runtime=str(payload.get("runtime") or "llama_cpp"),
49
+ modalities=_text_tuple(payload.get("modalities"), default=("text",)),
50
+ context_tokens=int(payload.get("context_tokens") or 4096),
51
+ jurisdiction=str(payload.get("jurisdiction") or "NZ").upper(),
52
+ residency=tuple(str(item).upper() for item in _text_tuple(payload.get("residency"), default=("NZ",))),
53
+ license_id=str(payload.get("license_id") or "see_model_card"),
54
+ quantization=str(payload.get("quantization") or "see_manifest"),
55
+ primary_artifact=str(payload.get("primary_artifact") or ""),
56
+ active_params_b=(
57
+ float(payload["active_params_b"]) if payload.get("active_params_b") is not None else None
58
+ ),
59
+ total_params_b=(
60
+ float(payload["total_params_b"]) if payload.get("total_params_b") is not None else None
61
+ ),
62
+ quality_rank=int(payload.get("quality_rank") or 5),
63
+ cost_rank=int(payload.get("cost_rank") or 5),
64
+ sovereignty_tier=int(payload.get("sovereignty_tier") or 2),
65
+ supports_tools=bool(payload.get("supports_tools", False)),
66
+ supports_json=bool(payload.get("supports_json", False)),
67
+ tags=tuple(item.lower() for item in _text_tuple(payload.get("tags"))),
68
+ metadata=dict(payload.get("metadata") or {}),
69
+ )
70
+
71
+ def to_dict(self) -> dict[str, Any]:
72
+ return {
73
+ "model_id": self.model_id,
74
+ "repo_id": self.repo_id,
75
+ "family": self.family,
76
+ "runtime": self.runtime,
77
+ "modalities": list(self.modalities),
78
+ "context_tokens": self.context_tokens,
79
+ "jurisdiction": self.jurisdiction,
80
+ "residency": list(self.residency),
81
+ "license_id": self.license_id,
82
+ "quantization": self.quantization,
83
+ "primary_artifact": self.primary_artifact,
84
+ "active_params_b": self.active_params_b,
85
+ "total_params_b": self.total_params_b,
86
+ "quality_rank": self.quality_rank,
87
+ "cost_rank": self.cost_rank,
88
+ "sovereignty_tier": self.sovereignty_tier,
89
+ "supports_tools": self.supports_tools,
90
+ "supports_json": self.supports_json,
91
+ "tags": list(self.tags),
92
+ "metadata": dict(self.metadata),
93
+ }
94
+
95
+
96
+ @dataclass(frozen=True, slots=True)
97
+ class RoutingRequest:
98
+ prompt: str
99
+ task_type: str = "general"
100
+ modalities: tuple[str, ...] = ("text",)
101
+ jurisdiction: str = "NZ"
102
+ data_sensitivity: str = "internal"
103
+ min_context_tokens: int = 4096
104
+ requires_local: bool = True
105
+ requires_tools: bool = False
106
+ requires_json: bool = False
107
+ license_allowlist: tuple[str, ...] = ()
108
+ max_fallbacks: int = 3
109
+ metadata: dict[str, Any] = field(default_factory=dict)
110
+
111
+ @classmethod
112
+ def from_payload(cls, payload: dict[str, Any]) -> RoutingRequest:
113
+ return cls(
114
+ prompt=str(payload.get("prompt") or ""),
115
+ task_type=str(payload.get("task_type") or "general").lower(),
116
+ modalities=tuple(item.lower() for item in _text_tuple(payload.get("modalities"), default=("text",))),
117
+ jurisdiction=str(payload.get("jurisdiction") or "NZ").upper(),
118
+ data_sensitivity=str(payload.get("data_sensitivity") or "internal").lower(),
119
+ min_context_tokens=int(payload.get("min_context_tokens") or 4096),
120
+ requires_local=bool(payload.get("requires_local", True)),
121
+ requires_tools=bool(payload.get("requires_tools", False)),
122
+ requires_json=bool(payload.get("requires_json", False)),
123
+ license_allowlist=tuple(item.lower() for item in _text_tuple(payload.get("license_allowlist"))),
124
+ max_fallbacks=int(payload.get("max_fallbacks") or 3),
125
+ metadata=dict(payload.get("metadata") or {}),
126
+ )
127
+
128
+ def to_dict(self) -> dict[str, Any]:
129
+ return {
130
+ "prompt": self.prompt,
131
+ "task_type": self.task_type,
132
+ "modalities": list(self.modalities),
133
+ "jurisdiction": self.jurisdiction,
134
+ "data_sensitivity": self.data_sensitivity,
135
+ "min_context_tokens": self.min_context_tokens,
136
+ "requires_local": self.requires_local,
137
+ "requires_tools": self.requires_tools,
138
+ "requires_json": self.requires_json,
139
+ "license_allowlist": list(self.license_allowlist),
140
+ "max_fallbacks": self.max_fallbacks,
141
+ "metadata": dict(self.metadata),
142
+ }
143
+
144
+
145
+ def load_model_registry(path: Path) -> tuple[ModelEndpoint, ...]:
146
+ payload = json.loads(path.read_text(encoding="utf-8-sig"))
147
+ raw_models = payload.get("models") if isinstance(payload, dict) else payload
148
+ if not isinstance(raw_models, list):
149
+ raise ValueError(f"Expected model list in {path}")
150
+ return tuple(ModelEndpoint.from_payload(item) for item in raw_models if isinstance(item, dict))
marama_route/router.py CHANGED
@@ -1,113 +1,113 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import Any
5
-
6
- from .registry import ModelEndpoint, RoutingRequest
7
-
8
- HIGH_SENSITIVITY = frozenset({"personal", "restricted", "health", "iwi", "taonga"})
9
-
10
-
11
- @dataclass(frozen=True, slots=True)
12
- class RouteDecision:
13
- selected_model: ModelEndpoint | None
14
- fallback_models: tuple[ModelEndpoint, ...]
15
- rejected: tuple[dict[str, str], ...]
16
- reasons: tuple[str, ...]
17
- scores: dict[str, float]
18
-
19
- def to_dict(self) -> dict[str, Any]:
20
- return {
21
- "selected_model": self.selected_model.to_dict() if self.selected_model else None,
22
- "fallback_models": [model.to_dict() for model in self.fallback_models],
23
- "rejected": list(self.rejected),
24
- "reasons": list(self.reasons),
25
- "scores": dict(self.scores),
26
- }
27
-
28
-
29
- class SovereignModelRouter:
30
- def __init__(self, models: tuple[ModelEndpoint, ...]) -> None:
31
- self.models = models
32
-
33
- def route(self, request: RoutingRequest) -> RouteDecision:
34
- accepted: list[ModelEndpoint] = []
35
- rejected: list[dict[str, str]] = []
36
- requested_modalities = set(request.modalities)
37
- license_allowlist = set(request.license_allowlist)
38
-
39
- for model in self.models:
40
- model_modalities = set(item.lower() for item in model.modalities)
41
- if not requested_modalities.issubset(model_modalities):
42
- rejected.append({"model_id": model.model_id, "reason": "modality_mismatch"})
43
- continue
44
- if model.context_tokens < request.min_context_tokens:
45
- rejected.append({"model_id": model.model_id, "reason": "context_too_small"})
46
- continue
47
- if request.requires_tools and not model.supports_tools:
48
- rejected.append({"model_id": model.model_id, "reason": "tools_required"})
49
- continue
50
- if request.requires_json and not model.supports_json:
51
- rejected.append({"model_id": model.model_id, "reason": "json_required"})
52
- continue
53
- if license_allowlist and model.license_id.lower() not in license_allowlist:
54
- rejected.append({"model_id": model.model_id, "reason": "license_not_allowed"})
55
- continue
56
- if request.requires_local and request.jurisdiction not in model.residency:
57
- rejected.append({"model_id": model.model_id, "reason": "residency_mismatch"})
58
- continue
59
- if request.data_sensitivity in HIGH_SENSITIVITY and model.sovereignty_tier < 2:
60
- rejected.append({"model_id": model.model_id, "reason": "sovereignty_tier_too_low"})
61
- continue
62
- accepted.append(model)
63
-
64
- scores = {model.model_id: self._score(model, request) for model in accepted}
65
- ranked = tuple(sorted(accepted, key=lambda model: (scores[model.model_id], model.model_id), reverse=True))
66
- selected = ranked[0] if ranked else None
67
- fallbacks = ranked[1 : 1 + request.max_fallbacks]
68
- reasons = self._reasons(selected, request)
69
- return RouteDecision(
70
- selected_model=selected,
71
- fallback_models=fallbacks,
72
- rejected=tuple(rejected),
73
- reasons=reasons,
74
- scores=scores,
75
- )
76
-
77
- def _score(self, model: ModelEndpoint, request: RoutingRequest) -> float:
78
- score = 0.0
79
- tags = set(model.tags)
80
- prompt_lower = request.prompt.lower()
81
- if request.jurisdiction in model.residency:
82
- score += 8.0
83
- if request.task_type in tags or request.task_type in model.family.lower():
84
- score += 7.0
85
- if request.task_type == "code" and ("coder" in model.model_id or "coder" in tags):
86
- score += 10.0
87
- if request.task_type == "reasoning" and ("reasoning" in model.model_id or "reasoning" in tags):
88
- score += 9.0
89
- if "iwi" in prompt_lower or "data sovereignty" in prompt_lower:
90
- score += 3.0 * model.sovereignty_tier
91
- if "gguf" in model.runtime.lower() or model.runtime == "llama_cpp":
92
- score += 2.5
93
- if model.supports_json and request.requires_json:
94
- score += 3.0
95
- if model.supports_tools and request.requires_tools:
96
- score += 3.0
97
- score += max(0, 10 - model.quality_rank) * 1.7
98
- score -= model.cost_rank * 0.25
99
- if model.active_params_b is not None and model.active_params_b <= 8:
100
- score += 0.5
101
- return round(score, 4)
102
-
103
- def _reasons(self, selected: ModelEndpoint | None, request: RoutingRequest) -> tuple[str, ...]:
104
- if selected is None:
105
- return ("no model satisfied sovereignty and capability constraints",)
106
- reasons = [
107
- f"selected `{selected.model_id}` for task_type `{request.task_type}`",
108
- f"residency `{request.jurisdiction}` satisfied",
109
- f"runtime `{selected.runtime}`",
110
- ]
111
- if request.data_sensitivity in HIGH_SENSITIVITY:
112
- reasons.append("high-sensitivity routing kept inside sovereign tier constraints")
113
- return tuple(reasons)
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from .registry import ModelEndpoint, RoutingRequest
7
+
8
+ HIGH_SENSITIVITY = frozenset({"personal", "restricted", "health", "iwi", "taonga"})
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class RouteDecision:
13
+ selected_model: ModelEndpoint | None
14
+ fallback_models: tuple[ModelEndpoint, ...]
15
+ rejected: tuple[dict[str, str], ...]
16
+ reasons: tuple[str, ...]
17
+ scores: dict[str, float]
18
+
19
+ def to_dict(self) -> dict[str, Any]:
20
+ return {
21
+ "selected_model": self.selected_model.to_dict() if self.selected_model else None,
22
+ "fallback_models": [model.to_dict() for model in self.fallback_models],
23
+ "rejected": list(self.rejected),
24
+ "reasons": list(self.reasons),
25
+ "scores": dict(self.scores),
26
+ }
27
+
28
+
29
+ class SovereignModelRouter:
30
+ def __init__(self, models: tuple[ModelEndpoint, ...]) -> None:
31
+ self.models = models
32
+
33
+ def route(self, request: RoutingRequest) -> RouteDecision:
34
+ accepted: list[ModelEndpoint] = []
35
+ rejected: list[dict[str, str]] = []
36
+ requested_modalities = set(request.modalities)
37
+ license_allowlist = set(request.license_allowlist)
38
+
39
+ for model in self.models:
40
+ model_modalities = set(item.lower() for item in model.modalities)
41
+ if not requested_modalities.issubset(model_modalities):
42
+ rejected.append({"model_id": model.model_id, "reason": "modality_mismatch"})
43
+ continue
44
+ if model.context_tokens < request.min_context_tokens:
45
+ rejected.append({"model_id": model.model_id, "reason": "context_too_small"})
46
+ continue
47
+ if request.requires_tools and not model.supports_tools:
48
+ rejected.append({"model_id": model.model_id, "reason": "tools_required"})
49
+ continue
50
+ if request.requires_json and not model.supports_json:
51
+ rejected.append({"model_id": model.model_id, "reason": "json_required"})
52
+ continue
53
+ if license_allowlist and model.license_id.lower() not in license_allowlist:
54
+ rejected.append({"model_id": model.model_id, "reason": "license_not_allowed"})
55
+ continue
56
+ if request.requires_local and request.jurisdiction not in model.residency:
57
+ rejected.append({"model_id": model.model_id, "reason": "residency_mismatch"})
58
+ continue
59
+ if request.data_sensitivity in HIGH_SENSITIVITY and model.sovereignty_tier < 2:
60
+ rejected.append({"model_id": model.model_id, "reason": "sovereignty_tier_too_low"})
61
+ continue
62
+ accepted.append(model)
63
+
64
+ scores = {model.model_id: self._score(model, request) for model in accepted}
65
+ ranked = tuple(sorted(accepted, key=lambda model: (scores[model.model_id], model.model_id), reverse=True))
66
+ selected = ranked[0] if ranked else None
67
+ fallbacks = ranked[1 : 1 + request.max_fallbacks]
68
+ reasons = self._reasons(selected, request)
69
+ return RouteDecision(
70
+ selected_model=selected,
71
+ fallback_models=fallbacks,
72
+ rejected=tuple(rejected),
73
+ reasons=reasons,
74
+ scores=scores,
75
+ )
76
+
77
+ def _score(self, model: ModelEndpoint, request: RoutingRequest) -> float:
78
+ score = 0.0
79
+ tags = set(model.tags)
80
+ prompt_lower = request.prompt.lower()
81
+ if request.jurisdiction in model.residency:
82
+ score += 8.0
83
+ if request.task_type in tags or request.task_type in model.family.lower():
84
+ score += 7.0
85
+ if request.task_type == "code" and ("coder" in model.model_id or "coder" in tags):
86
+ score += 10.0
87
+ if request.task_type == "reasoning" and ("reasoning" in model.model_id or "reasoning" in tags):
88
+ score += 9.0
89
+ if "iwi" in prompt_lower or "data sovereignty" in prompt_lower:
90
+ score += 3.0 * model.sovereignty_tier
91
+ if "gguf" in model.runtime.lower() or model.runtime == "llama_cpp":
92
+ score += 2.5
93
+ if model.supports_json and request.requires_json:
94
+ score += 3.0
95
+ if model.supports_tools and request.requires_tools:
96
+ score += 3.0
97
+ score += max(0, 10 - model.quality_rank) * 1.7
98
+ score -= model.cost_rank * 0.25
99
+ if model.active_params_b is not None and model.active_params_b <= 8:
100
+ score += 0.5
101
+ return round(score, 4)
102
+
103
+ def _reasons(self, selected: ModelEndpoint | None, request: RoutingRequest) -> tuple[str, ...]:
104
+ if selected is None:
105
+ return ("no model satisfied sovereignty and capability constraints",)
106
+ reasons = [
107
+ f"selected `{selected.model_id}` for task_type `{request.task_type}`",
108
+ f"residency `{request.jurisdiction}` satisfied",
109
+ f"runtime `{selected.runtime}`",
110
+ ]
111
+ if request.data_sensitivity in HIGH_SENSITIVITY:
112
+ reasons.append("high-sensitivity routing kept inside sovereign tier constraints")
113
+ return tuple(reasons)
marama_route/schemas/data_capsule.schema.json CHANGED
@@ -1,88 +1,88 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://abteex.com/schemas/sovereigncode/data-capsule.schema.json",
4
- "title": "AbteeX SovereignCode Data Capsule",
5
- "type": "object",
6
- "required": ["capsule_id", "subject_id", "jurisdiction", "sensitivity"],
7
- "additionalProperties": true,
8
- "properties": {
9
- "capsule_id": {
10
- "type": "string",
11
- "minLength": 1
12
- },
13
- "subject_id": {
14
- "type": "string",
15
- "minLength": 1
16
- },
17
- "jurisdiction": {
18
- "type": "string",
19
- "default": "NZ"
20
- },
21
- "sensitivity": {
22
- "type": "string",
23
- "enum": ["public", "internal", "restricted", "personal", "health", "iwi", "taonga"]
24
- },
25
- "allowed_purposes": {
26
- "type": "array",
27
- "items": { "type": "string" },
28
- "default": ["inference", "coding_assistance"]
29
- },
30
- "denied_purposes": {
31
- "type": "array",
32
- "items": { "type": "string" },
33
- "default": []
34
- },
35
- "resident_regions": {
36
- "type": "array",
37
- "items": { "type": "string" },
38
- "default": ["NZ"]
39
- },
40
- "data_classes": {
41
- "type": "array",
42
- "items": { "type": "string" },
43
- "default": ["source_code"]
44
- },
45
- "retention_days": {
46
- "type": "integer",
47
- "minimum": 0,
48
- "default": 30
49
- },
50
- "export_allowed": {
51
- "type": "boolean",
52
- "default": false
53
- },
54
- "training_allowed": {
55
- "type": "boolean",
56
- "default": false
57
- },
58
- "personal_detail_level": {
59
- "type": "string",
60
- "enum": ["none", "anonymous", "pseudonymous", "identifiable", "sensitive_identifiable"],
61
- "default": "none"
62
- },
63
- "consent_scopes": {
64
- "type": "array",
65
- "items": { "type": "string" },
66
- "default": []
67
- },
68
- "data_subject_rights": {
69
- "type": "array",
70
- "items": { "type": "string" },
71
- "default": ["access", "correction", "deletion_request", "processing_objection"]
72
- },
73
- "revoked": {
74
- "type": "boolean",
75
- "default": false
76
- },
77
- "schema_context": {
78
- "type": "string",
79
- "default": "https://schema.org"
80
- },
81
- "consent_record": {
82
- "type": "string"
83
- },
84
- "metadata": {
85
- "type": "object"
86
- }
87
- }
88
- }
 
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://abteex.com/schemas/sovereigncode/data-capsule.schema.json",
4
+ "title": "AbteeX SovereignCode Data Capsule",
5
+ "type": "object",
6
+ "required": ["capsule_id", "subject_id", "jurisdiction", "sensitivity"],
7
+ "additionalProperties": true,
8
+ "properties": {
9
+ "capsule_id": {
10
+ "type": "string",
11
+ "minLength": 1
12
+ },
13
+ "subject_id": {
14
+ "type": "string",
15
+ "minLength": 1
16
+ },
17
+ "jurisdiction": {
18
+ "type": "string",
19
+ "default": "NZ"
20
+ },
21
+ "sensitivity": {
22
+ "type": "string",
23
+ "enum": ["public", "internal", "restricted", "personal", "health", "iwi", "taonga"]
24
+ },
25
+ "allowed_purposes": {
26
+ "type": "array",
27
+ "items": { "type": "string" },
28
+ "default": ["inference", "coding_assistance"]
29
+ },
30
+ "denied_purposes": {
31
+ "type": "array",
32
+ "items": { "type": "string" },
33
+ "default": []
34
+ },
35
+ "resident_regions": {
36
+ "type": "array",
37
+ "items": { "type": "string" },
38
+ "default": ["NZ"]
39
+ },
40
+ "data_classes": {
41
+ "type": "array",
42
+ "items": { "type": "string" },
43
+ "default": ["source_code"]
44
+ },
45
+ "retention_days": {
46
+ "type": "integer",
47
+ "minimum": 0,
48
+ "default": 30
49
+ },
50
+ "export_allowed": {
51
+ "type": "boolean",
52
+ "default": false
53
+ },
54
+ "training_allowed": {
55
+ "type": "boolean",
56
+ "default": false
57
+ },
58
+ "personal_detail_level": {
59
+ "type": "string",
60
+ "enum": ["none", "anonymous", "pseudonymous", "identifiable", "sensitive_identifiable"],
61
+ "default": "none"
62
+ },
63
+ "consent_scopes": {
64
+ "type": "array",
65
+ "items": { "type": "string" },
66
+ "default": []
67
+ },
68
+ "data_subject_rights": {
69
+ "type": "array",
70
+ "items": { "type": "string" },
71
+ "default": ["access", "correction", "deletion_request", "processing_objection"]
72
+ },
73
+ "revoked": {
74
+ "type": "boolean",
75
+ "default": false
76
+ },
77
+ "schema_context": {
78
+ "type": "string",
79
+ "default": "https://schema.org"
80
+ },
81
+ "consent_record": {
82
+ "type": "string"
83
+ },
84
+ "metadata": {
85
+ "type": "object"
86
+ }
87
+ }
88
+ }
marama_route/schemas/openai_chat_route_request.schema.json CHANGED
@@ -1,95 +1,95 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://abteex.com/schemas/marama-route/openai-chat-route-request.schema.json",
4
- "title": "MaramaRoute OpenAI-Compatible Chat Route Request",
5
- "type": "object",
6
- "required": ["messages"],
7
- "additionalProperties": true,
8
- "properties": {
9
- "model": {
10
- "type": "string",
11
- "default": "lumynax/auto"
12
- },
13
- "messages": {
14
- "type": "array",
15
- "items": {
16
- "type": "object",
17
- "required": ["role", "content"],
18
- "properties": {
19
- "role": {
20
- "type": "string"
21
- },
22
- "content": {}
23
- },
24
- "additionalProperties": true
25
- }
26
- },
27
- "tools": {
28
- "type": "array"
29
- },
30
- "response_format": {
31
- "type": "object"
32
- },
33
- "route": {
34
- "$ref": "#/$defs/routeOptions"
35
- },
36
- "routing": {
37
- "$ref": "#/$defs/routeOptions"
38
- },
39
- "metadata": {
40
- "type": "object",
41
- "properties": {
42
- "marama_route": {
43
- "$ref": "#/$defs/routeOptions"
44
- }
45
- },
46
- "additionalProperties": true
47
- }
48
- },
49
- "$defs": {
50
- "routeOptions": {
51
- "type": "object",
52
- "additionalProperties": true,
53
- "properties": {
54
- "jurisdiction": {
55
- "type": "string",
56
- "default": "NZ"
57
- },
58
- "data_sensitivity": {
59
- "type": "string",
60
- "default": "internal"
61
- },
62
- "task_type": {
63
- "type": "string",
64
- "enum": ["general", "code", "reasoning", "multimodal", "embedding"]
65
- },
66
- "min_context_tokens": {
67
- "type": "integer",
68
- "minimum": 1,
69
- "default": 4096
70
- },
71
- "requires_local": {
72
- "type": "boolean",
73
- "default": true
74
- },
75
- "requires_tools": {
76
- "type": "boolean",
77
- "default": false
78
- },
79
- "requires_json": {
80
- "type": "boolean",
81
- "default": false
82
- },
83
- "license_allowlist": {
84
- "type": "array",
85
- "items": { "type": "string" }
86
- },
87
- "max_fallbacks": {
88
- "type": "integer",
89
- "minimum": 0,
90
- "default": 3
91
- }
92
- }
93
- }
94
- }
95
- }
 
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://abteex.com/schemas/marama-route/openai-chat-route-request.schema.json",
4
+ "title": "MaramaRoute OpenAI-Compatible Chat Route Request",
5
+ "type": "object",
6
+ "required": ["messages"],
7
+ "additionalProperties": true,
8
+ "properties": {
9
+ "model": {
10
+ "type": "string",
11
+ "default": "lumynax/auto"
12
+ },
13
+ "messages": {
14
+ "type": "array",
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["role", "content"],
18
+ "properties": {
19
+ "role": {
20
+ "type": "string"
21
+ },
22
+ "content": {}
23
+ },
24
+ "additionalProperties": true
25
+ }
26
+ },
27
+ "tools": {
28
+ "type": "array"
29
+ },
30
+ "response_format": {
31
+ "type": "object"
32
+ },
33
+ "route": {
34
+ "$ref": "#/$defs/routeOptions"
35
+ },
36
+ "routing": {
37
+ "$ref": "#/$defs/routeOptions"
38
+ },
39
+ "metadata": {
40
+ "type": "object",
41
+ "properties": {
42
+ "marama_route": {
43
+ "$ref": "#/$defs/routeOptions"
44
+ }
45
+ },
46
+ "additionalProperties": true
47
+ }
48
+ },
49
+ "$defs": {
50
+ "routeOptions": {
51
+ "type": "object",
52
+ "additionalProperties": true,
53
+ "properties": {
54
+ "jurisdiction": {
55
+ "type": "string",
56
+ "default": "NZ"
57
+ },
58
+ "data_sensitivity": {
59
+ "type": "string",
60
+ "default": "internal"
61
+ },
62
+ "task_type": {
63
+ "type": "string",
64
+ "enum": ["general", "code", "reasoning", "multimodal", "embedding"]
65
+ },
66
+ "min_context_tokens": {
67
+ "type": "integer",
68
+ "minimum": 1,
69
+ "default": 4096
70
+ },
71
+ "requires_local": {
72
+ "type": "boolean",
73
+ "default": true
74
+ },
75
+ "requires_tools": {
76
+ "type": "boolean",
77
+ "default": false
78
+ },
79
+ "requires_json": {
80
+ "type": "boolean",
81
+ "default": false
82
+ },
83
+ "license_allowlist": {
84
+ "type": "array",
85
+ "items": { "type": "string" }
86
+ },
87
+ "max_fallbacks": {
88
+ "type": "integer",
89
+ "minimum": 0,
90
+ "default": 3
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
marama_route/server.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import tempfile
6
+ import urllib.error
7
+ import urllib.request
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ try: # repo package
12
+ from tinyluminax.products._ui_server import serve_dashboard
13
+ except ModuleNotFoundError: # standalone HF package
14
+ from ._ui_server import serve_dashboard
15
+
16
+ from .gateway import route_chat_payload
17
+ from .platform import build_models_api, route_or_chat_payload, route_receipt
18
+ from .registry import load_model_registry
19
+ from .ui import (
20
+ PRODUCT_NAME,
21
+ build_dashboard_state,
22
+ build_expanded_dashboard_html,
23
+ default_openai_chat_request_path,
24
+ default_registry_path,
25
+ handle_api_request,
26
+ load_json_mapping,
27
+ )
28
+
29
+ PACKAGE_ROOT = Path(__file__).resolve().parent
30
+ PACKAGE_PARENT = PACKAGE_ROOT.parent
31
+
32
+ DEFAULT_GATEWAY_CONFIG: dict[str, Any] = {
33
+ "mode": "route_only",
34
+ "prompt_retention": "not_stored_by_default",
35
+ "default_timeout_seconds": 120,
36
+ "backends": {},
37
+ }
38
+
39
+
40
+ def default_gateway_config_path() -> Path:
41
+ candidates = [
42
+ Path.cwd() / "products" / "lumynax-marama-route" / "configs" / "gateway.local.json",
43
+ Path.cwd() / "configs" / "gateway.local.json",
44
+ PACKAGE_ROOT / "configs" / "gateway.local.json",
45
+ PACKAGE_PARENT / "configs" / "gateway.local.json",
46
+ ]
47
+ for path in candidates:
48
+ if path.exists():
49
+ return path
50
+ return candidates[0]
51
+
52
+
53
+ def default_route_request_path() -> Path:
54
+ candidates = [
55
+ Path.cwd() / "products" / "lumynax-marama-route" / "examples" / "request.code-restricted.json",
56
+ Path.cwd() / "examples" / "request.code-restricted.json",
57
+ PACKAGE_ROOT / "examples" / "request.code-restricted.json",
58
+ PACKAGE_PARENT / "examples" / "request.code-restricted.json",
59
+ ]
60
+ for path in candidates:
61
+ if path.exists():
62
+ return path
63
+ return candidates[0]
64
+
65
+
66
+ def load_gateway_config(path: Path | None = None) -> dict[str, Any]:
67
+ config = dict(DEFAULT_GATEWAY_CONFIG)
68
+ config["backends"] = dict(DEFAULT_GATEWAY_CONFIG["backends"])
69
+ resolved = path or default_gateway_config_path()
70
+ if resolved.exists():
71
+ payload = json.loads(resolved.read_text(encoding="utf-8-sig"))
72
+ if not isinstance(payload, dict):
73
+ raise ValueError(f"Expected gateway config object in {resolved}")
74
+ config.update(payload)
75
+ config["backends"] = dict(payload.get("backends") or {})
76
+ config["config_path"] = str(resolved)
77
+ return config
78
+
79
+
80
+ def handle_gateway_request(
81
+ method: str,
82
+ path: str,
83
+ payload: dict[str, Any] | None,
84
+ registry_path: Path,
85
+ config_path: Path | None = None,
86
+ ) -> tuple[int, dict[str, Any]]:
87
+ models = load_model_registry(registry_path)
88
+ config = load_gateway_config(config_path)
89
+
90
+ if path.startswith("/api/"):
91
+ return handle_api_request(method, path, payload, registry_path)
92
+ if method == "GET" and path in {"/health", "/v1/health"}:
93
+ return 200, {
94
+ "ok": True,
95
+ "product": PRODUCT_NAME,
96
+ "mode": config["mode"],
97
+ "model_count": len(models),
98
+ "configured_backends": len(config.get("backends") or {}),
99
+ "prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
100
+ }
101
+ if method == "GET" and path == "/v1/models":
102
+ return 200, build_models_api(models)
103
+ if method == "POST" and path == "/v1/route" and payload is not None:
104
+ result = route_or_chat_payload(payload, models)
105
+ return (200 if result["ok"] else 422), result
106
+ if method == "POST" and path == "/v1/chat/completions" and payload is not None:
107
+ return chat_completion_gateway(payload, models, config)
108
+ return 404, {"ok": False, "error": "not_found"}
109
+
110
+
111
+ def chat_completion_gateway(
112
+ payload: dict[str, Any],
113
+ models: tuple[Any, ...],
114
+ config: dict[str, Any],
115
+ ) -> tuple[int, dict[str, Any]]:
116
+ route_result = route_chat_payload(payload, models)
117
+ decision = route_result["route_decision"]
118
+ selected = decision.get("selected_model")
119
+ if not isinstance(selected, dict):
120
+ return 422, {"ok": False, "error": "no_eligible_model", **route_result}
121
+
122
+ receipt = route_receipt(payload, route_result)
123
+ dry_run = bool(
124
+ payload.get("dry_run")
125
+ or payload.get("marama_route_dry_run")
126
+ or config.get("mode", "route_only") == "route_only"
127
+ )
128
+ if dry_run:
129
+ response = dict(route_result["chat_completion_response"])
130
+ response["marama_route"] = dict(response["marama_route"])
131
+ response["marama_route"].update(
132
+ {
133
+ "backend_mode": "route_only",
134
+ "receipt": receipt,
135
+ "prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
136
+ },
137
+ )
138
+ return 200, response
139
+
140
+ backend = _backend_for_model(selected["model_id"], config)
141
+ if backend is None:
142
+ return 424, {
143
+ "ok": False,
144
+ "error": "backend_not_configured",
145
+ "message": "Routing succeeded, but no live backend is configured for the selected model.",
146
+ "selected_model": selected["model_id"],
147
+ "required_config": {
148
+ "mode": "live",
149
+ "backends": {
150
+ selected["model_id"]: {
151
+ "type": "openai_compatible",
152
+ "base_url": "http://127.0.0.1:8000/v1",
153
+ "api_key_env": "OPTIONAL_ENV_NAME",
154
+ },
155
+ },
156
+ },
157
+ "receipt": receipt,
158
+ **route_result,
159
+ }
160
+ return _proxy_openai_chat_completion(payload, selected, backend, config, route_result, receipt)
161
+
162
+
163
+ def smoke_gateway(
164
+ *,
165
+ registry_path: Path | None = None,
166
+ config_path: Path | None = None,
167
+ ) -> dict[str, Any]:
168
+ resolved_registry = registry_path or default_registry_path()
169
+ resolved_config = config_path or _temporary_route_only_config()
170
+ route_payload = load_json_mapping(default_route_request_path())
171
+ chat_payload = load_json_mapping(default_openai_chat_request_path())
172
+ chat_payload["dry_run"] = True
173
+
174
+ health_status, health = handle_gateway_request("GET", "/health", None, resolved_registry, resolved_config)
175
+ models_status, models = handle_gateway_request("GET", "/v1/models", None, resolved_registry, resolved_config)
176
+ route_status, route = handle_gateway_request("POST", "/v1/route", route_payload, resolved_registry, resolved_config)
177
+ chat_status, chat = handle_gateway_request(
178
+ "POST",
179
+ "/v1/chat/completions",
180
+ chat_payload,
181
+ resolved_registry,
182
+ resolved_config,
183
+ )
184
+
185
+ if health_status != 200 or models_status != 200 or route_status != 200 or chat_status != 200:
186
+ raise RuntimeError("MaramaRoute gateway smoke failed")
187
+ if chat.get("object") != "chat.completion" or chat["marama_route"]["selected_model"] is None:
188
+ raise RuntimeError("MaramaRoute gateway did not return a routed chat response")
189
+ return {
190
+ "ok": True,
191
+ "product": PRODUCT_NAME,
192
+ "mode": health["mode"],
193
+ "model_count": health["model_count"],
194
+ "route_selected_model": route["route_decision"]["selected_model"]["model_id"],
195
+ "chat_selected_model": chat["marama_route"]["selected_model"]["model_id"],
196
+ "configured_backends": health["configured_backends"],
197
+ }
198
+
199
+
200
+ def serve_gateway(
201
+ *,
202
+ registry_path: Path | None = None,
203
+ config_path: Path | None = None,
204
+ host: str = "127.0.0.1",
205
+ port: int = 8787,
206
+ open_browser: bool = False,
207
+ smoke: bool = False,
208
+ ) -> int:
209
+ resolved_registry = registry_path or default_registry_path()
210
+ if smoke:
211
+ print(json.dumps(smoke_gateway(registry_path=resolved_registry, config_path=config_path), indent=2, sort_keys=True))
212
+ return 0
213
+
214
+ html = build_expanded_dashboard_html(build_dashboard_state(resolved_registry))
215
+ return serve_dashboard(
216
+ product_name=f"{PRODUCT_NAME} Gateway",
217
+ html=html,
218
+ api_handler=lambda method, path, request_payload: handle_gateway_request(
219
+ method,
220
+ path,
221
+ request_payload,
222
+ resolved_registry,
223
+ config_path,
224
+ ),
225
+ host=host,
226
+ port=port,
227
+ open_browser=open_browser,
228
+ api_path_prefixes=("/api/", "/v1/"),
229
+ api_exact_paths=("/health",),
230
+ )
231
+
232
+
233
+ def _backend_for_model(model_id: str, config: dict[str, Any]) -> dict[str, Any] | None:
234
+ backends = config.get("backends")
235
+ if not isinstance(backends, dict):
236
+ return None
237
+ backend = backends.get(model_id) or backends.get("*")
238
+ return dict(backend) if isinstance(backend, dict) else None
239
+
240
+
241
+ def _proxy_openai_chat_completion(
242
+ payload: dict[str, Any],
243
+ selected: dict[str, Any],
244
+ backend: dict[str, Any],
245
+ config: dict[str, Any],
246
+ route_result: dict[str, Any],
247
+ receipt: dict[str, Any],
248
+ ) -> tuple[int, dict[str, Any]]:
249
+ if str(backend.get("type") or "openai_compatible") != "openai_compatible":
250
+ return 424, {"ok": False, "error": "unsupported_backend_type", "backend": backend}
251
+
252
+ base_url = str(backend.get("base_url") or "").rstrip("/")
253
+ if not base_url:
254
+ return 424, {"ok": False, "error": "backend_base_url_missing", "selected_model": selected["model_id"]}
255
+ endpoint = f"{base_url}/chat/completions"
256
+ upstream_payload = dict(payload)
257
+ upstream_payload["model"] = str(backend.get("model") or selected["model_id"])
258
+ for key in ("route", "routing", "dry_run", "marama_route_dry_run"):
259
+ upstream_payload.pop(key, None)
260
+
261
+ headers = {"Content-Type": "application/json"}
262
+ api_key_env = str(backend.get("api_key_env") or "")
263
+ if api_key_env and os.getenv(api_key_env):
264
+ headers["Authorization"] = f"Bearer {os.environ[api_key_env]}"
265
+ headers.update({str(key): str(value) for key, value in dict(backend.get("headers") or {}).items()})
266
+
267
+ timeout = float(backend.get("timeout_seconds") or config.get("default_timeout_seconds") or 120)
268
+ request = urllib.request.Request(
269
+ endpoint,
270
+ data=json.dumps(upstream_payload).encode("utf-8"),
271
+ headers=headers,
272
+ method="POST",
273
+ )
274
+ try:
275
+ with urllib.request.urlopen(request, timeout=timeout) as response: # noqa: S310 - operator-configured local/remote backend
276
+ body = response.read().decode("utf-8")
277
+ payload_out = json.loads(body)
278
+ if not isinstance(payload_out, dict):
279
+ raise ValueError("upstream response was not a JSON object")
280
+ payload_out["marama_route"] = {
281
+ "dry_run": False,
282
+ "selected_model": selected,
283
+ "fallback_models": route_result["route_decision"]["fallback_models"],
284
+ "rejected_count": len(route_result["route_decision"]["rejected"]),
285
+ "receipt": receipt,
286
+ "backend_base_url": base_url,
287
+ "prompt_retention": config.get("prompt_retention", "not_stored_by_default"),
288
+ }
289
+ return int(response.status), payload_out
290
+ except urllib.error.HTTPError as exc:
291
+ return exc.code, {
292
+ "ok": False,
293
+ "error": "backend_http_error",
294
+ "status": exc.code,
295
+ "body": exc.read().decode("utf-8", errors="replace"),
296
+ "receipt": receipt,
297
+ **route_result,
298
+ }
299
+ except Exception as exc:
300
+ return 502, {
301
+ "ok": False,
302
+ "error": "backend_unavailable",
303
+ "message": str(exc),
304
+ "receipt": receipt,
305
+ **route_result,
306
+ }
307
+
308
+
309
+ def _temporary_route_only_config() -> Path:
310
+ path = Path(tempfile.gettempdir()) / "marama-route-smoke.gateway.json"
311
+ path.write_text(json.dumps(DEFAULT_GATEWAY_CONFIG, indent=2, sort_keys=True), encoding="utf-8")
312
+ return path
policy-packs/nz-personal-sovereignty.yaml CHANGED
@@ -1,59 +1,59 @@
1
- policy_id: abx-sovereigncode-nz-personal-sovereignty-v0
2
- jurisdiction: NZ
3
- purpose: governed personal, workspace, and community-context coding assistance
4
- default_residency:
5
- - NZ
6
- allowed_personal_detail_levels:
7
- - none
8
- - anonymous
9
- - pseudonymous
10
- - identifiable
11
- - sensitive_identifiable
12
- default_personal_detail_level: pseudonymous
13
- consent_scopes:
14
- coding_assistance:
15
- allowed_actions:
16
- - read_context
17
- - propose_patch
18
- - generate_tests
19
- blocked_actions_without_approval:
20
- - write_file
21
- - execute_shell
22
- - commit
23
- - publish
24
- - network_export
25
- personal_memory:
26
- allowed_actions:
27
- - read_preference
28
- - summarise_profile
29
- blocked_actions_without_approval:
30
- - export_profile
31
- - train_adapter
32
- - share_with_third_party
33
- retention_defaults:
34
- restricted_code_days: 14
35
- personal_trace_days: 7
36
- audit_record_days: 365
37
- data_subject_rights:
38
- - access
39
- - correction
40
- - deletion_request
41
- - processing_objection
42
- obligations:
43
- - write_immutable_audit_record
44
- - minimise_personal_detail_in_prompt
45
- - keep_personal_trace_inside_capsule_retention
46
- - show_diff_before_write_or_commit
47
- - route_only_to_resident_runtime
48
- - require_human_review_for_external_effects
49
- model_rules:
50
- high_impact_requires_lumynax_or_local: true
51
- restricted_requires_nz_residency: true
52
- public_may_route_to_approved_global: true
53
- export_rules:
54
- default_export_allowed: false
55
- require_export_manifest: true
56
- require_named_recipient: true
57
- training_rules:
58
- default_training_allowed: false
59
- require_explicit_capsule_training_allowed: true
 
1
+ policy_id: abx-sovereigncode-nz-personal-sovereignty-v0
2
+ jurisdiction: NZ
3
+ purpose: governed personal, workspace, and community-context coding assistance
4
+ default_residency:
5
+ - NZ
6
+ allowed_personal_detail_levels:
7
+ - none
8
+ - anonymous
9
+ - pseudonymous
10
+ - identifiable
11
+ - sensitive_identifiable
12
+ default_personal_detail_level: pseudonymous
13
+ consent_scopes:
14
+ coding_assistance:
15
+ allowed_actions:
16
+ - read_context
17
+ - propose_patch
18
+ - generate_tests
19
+ blocked_actions_without_approval:
20
+ - write_file
21
+ - execute_shell
22
+ - commit
23
+ - publish
24
+ - network_export
25
+ personal_memory:
26
+ allowed_actions:
27
+ - read_preference
28
+ - summarise_profile
29
+ blocked_actions_without_approval:
30
+ - export_profile
31
+ - train_adapter
32
+ - share_with_third_party
33
+ retention_defaults:
34
+ restricted_code_days: 14
35
+ personal_trace_days: 7
36
+ audit_record_days: 365
37
+ data_subject_rights:
38
+ - access
39
+ - correction
40
+ - deletion_request
41
+ - processing_objection
42
+ obligations:
43
+ - write_immutable_audit_record
44
+ - minimise_personal_detail_in_prompt
45
+ - keep_personal_trace_inside_capsule_retention
46
+ - show_diff_before_write_or_commit
47
+ - route_only_to_resident_runtime
48
+ - require_human_review_for_external_effects
49
+ model_rules:
50
+ high_impact_requires_lumynax_or_local: true
51
+ restricted_requires_nz_residency: true
52
+ public_may_route_to_approved_global: true
53
+ export_rules:
54
+ default_export_allowed: false
55
+ require_export_manifest: true
56
+ require_named_recipient: true
57
+ training_rules:
58
+ default_training_allowed: false
59
+ require_explicit_capsule_training_allowed: true
product_blueprint.md CHANGED
@@ -1,73 +1,73 @@
1
- # AbteeX SovereignCode Product Blueprint
2
-
3
- ## One-Sentence Product
4
-
5
- SovereignCode is a local-first coding agent for New Zealand teams that need code
6
- assistance, model routing, personal-data controls, and audit-ready tool use in
7
- one governed workflow.
8
-
9
- ## Core User Jobs
10
-
11
- | User | Job | SovereignCode Response |
12
- | --- | --- | --- |
13
- | Individual developer | Use an AI coding assistant without exposing private files or personal preferences. | Local capsule, pseudonymous personal profile, resident model route, no training by default. |
14
- | Startup or SME | Refactor and test private code while keeping customer data out of generic SaaS logs. | Workspace capsule, local route, diff review, audit hash. |
15
- | Council or public-sector team | Use AI on operational code and documents with retention and residency controls. | Tenant policy pack, NZ residency, approval gates, signed audit export. |
16
- | Iwi or community data steward | Keep community-held context under explicit purpose and consent boundaries. | High-impact sensitivity, local/LumynaX-only model rule, export denial by default. |
17
- | Internal platform owner | Give developers one coding assistant with central policy. | OpenAI-compatible provider, CLI planner, future SSO and policy server. |
18
-
19
- ## Product Pillars
20
-
21
- 1. Capsule-first context: every workspace, profile, dataset, and prompt context
22
- resolves to a Data Capsule before agent work starts.
23
- 2. Personal sovereignty: personal detail is classified before prompt assembly,
24
- and consent scopes gate how profile context can be used.
25
- 3. Governed autonomy: read, plan, patch, test, shell, network, commit, and
26
- publish actions are separate tool grants.
27
- 4. Open integration: OpenCode and similar clients connect through MaramaRoute's
28
- OpenAI-compatible gateway.
29
- 5. Audit without hoarding: records retain decision hashes, obligations, model
30
- identity, and reasons while prompt retention stays constrained.
31
-
32
- ## Minimum Product Loop
33
-
34
- ```text
35
- developer asks for a coding task
36
- -> resolve `.sovereigncode/capsule.json`
37
- -> evaluate SovereignRequest
38
- -> build MaramaRoute request
39
- -> select resident LumynaX model
40
- -> produce plan
41
- -> request approval for writes or shell
42
- -> apply patch
43
- -> run tests
44
- -> store audit record
45
- ```
46
-
47
- ## Product Modules To Build Next
48
-
49
- | Module | MVP Definition | Implementation Notes |
50
- | --- | --- | --- |
51
- | Workspace indexer | Reads repo files, ignores secrets/build outputs, tags data classes. | Start with `rg --files`, `.gitignore`, and capsule include/exclude rules. |
52
- | Tool broker | Wraps file write, shell, git, package install, HTTP, and model calls. | Reuse policy decisions and emit one audit record per effectful tool call. |
53
- | Terminal UI | Shows plan, selected model, obligations, diff, and test output. | Keep compatible with OpenCode-style terminal use. |
54
- | Personal profile store | Keeps user preferences and memory under a personal capsule. | Local encrypted file first, tenant vault later. |
55
- | Audit ledger | Append-only local JSONL with hash chain. | Export signed bundles for enterprise customers. |
56
- | Tenant policy server | Central policy packs, model allowlists, API keys, quotas. | Only needed after local MVP works. |
57
-
58
- ## Default Plans
59
-
60
- | Plan | Buyer | Included |
61
- | --- | --- | --- |
62
- | Local Developer | individual NZ developer | local capsule, local audit, MaramaRoute provider config |
63
- | Team Sovereign | startup or SME | shared policy pack, route registry, team audit export |
64
- | Regulated Workspace | council, health-adjacent, community data project | stronger approval gates, retention controls, signed audit, SSO-ready policy server |
65
-
66
- ## First Non-Negotiables
67
-
68
- - Never train on a capsule unless `training_allowed` is explicitly true.
69
- - Never export restricted or personal context unless `export_allowed` is true
70
- and the request carries human approval.
71
- - Never route high-impact data to a non-local or non-LumynaX-governed model.
72
- - Never apply file writes without a visible diff obligation.
73
- - Never hide selected model identity from the audit record.
 
1
+ # AbteeX SovereignCode Product Blueprint
2
+
3
+ ## One-Sentence Product
4
+
5
+ SovereignCode is a local-first coding agent for New Zealand teams that need code
6
+ assistance, model routing, personal-data controls, and audit-ready tool use in
7
+ one governed workflow.
8
+
9
+ ## Core User Jobs
10
+
11
+ | User | Job | SovereignCode Response |
12
+ | --- | --- | --- |
13
+ | Individual developer | Use an AI coding assistant without exposing private files or personal preferences. | Local capsule, pseudonymous personal profile, resident model route, no training by default. |
14
+ | Startup or SME | Refactor and test private code while keeping customer data out of generic SaaS logs. | Workspace capsule, local route, diff review, audit hash. |
15
+ | Council or public-sector team | Use AI on operational code and documents with retention and residency controls. | Tenant policy pack, NZ residency, approval gates, signed audit export. |
16
+ | Iwi or community data steward | Keep community-held context under explicit purpose and consent boundaries. | High-impact sensitivity, local/LumynaX-only model rule, export denial by default. |
17
+ | Internal platform owner | Give developers one coding assistant with central policy. | OpenAI-compatible provider, CLI planner, future SSO and policy server. |
18
+
19
+ ## Product Pillars
20
+
21
+ 1. Capsule-first context: every workspace, profile, dataset, and prompt context
22
+ resolves to a Data Capsule before agent work starts.
23
+ 2. Personal sovereignty: personal detail is classified before prompt assembly,
24
+ and consent scopes gate how profile context can be used.
25
+ 3. Governed autonomy: read, plan, patch, test, shell, network, commit, and
26
+ publish actions are separate tool grants.
27
+ 4. Open integration: OpenCode and similar clients connect through MaramaRoute's
28
+ OpenAI-compatible gateway.
29
+ 5. Audit without hoarding: records retain decision hashes, obligations, model
30
+ identity, and reasons while prompt retention stays constrained.
31
+
32
+ ## Minimum Product Loop
33
+
34
+ ```text
35
+ developer asks for a coding task
36
+ -> resolve `.sovereigncode/capsule.json`
37
+ -> evaluate SovereignRequest
38
+ -> build MaramaRoute request
39
+ -> select resident LumynaX model
40
+ -> produce plan
41
+ -> request approval for writes or shell
42
+ -> apply patch
43
+ -> run tests
44
+ -> store audit record
45
+ ```
46
+
47
+ ## Product Modules To Build Next
48
+
49
+ | Module | MVP Definition | Implementation Notes |
50
+ | --- | --- | --- |
51
+ | Workspace indexer | Reads repo files, ignores secrets/build outputs, tags data classes. | Start with `rg --files`, `.gitignore`, and capsule include/exclude rules. |
52
+ | Tool broker | Wraps file write, shell, git, package install, HTTP, and model calls. | Reuse policy decisions and emit one audit record per effectful tool call. |
53
+ | Terminal UI | Shows plan, selected model, obligations, diff, and test output. | Keep compatible with OpenCode-style terminal use. |
54
+ | Personal profile store | Keeps user preferences and memory under a personal capsule. | Local encrypted file first, tenant vault later. |
55
+ | Audit ledger | Append-only local JSONL with hash chain. | Export signed bundles for enterprise customers. |
56
+ | Tenant policy server | Central policy packs, model allowlists, API keys, quotas. | Only needed after local MVP works. |
57
+
58
+ ## Default Plans
59
+
60
+ | Plan | Buyer | Included |
61
+ | --- | --- | --- |
62
+ | Local Developer | individual NZ developer | local capsule, local audit, MaramaRoute provider config |
63
+ | Team Sovereign | startup or SME | shared policy pack, route registry, team audit export |
64
+ | Regulated Workspace | council, health-adjacent, community data project | stronger approval gates, retention controls, signed audit, SSO-ready policy server |
65
+
66
+ ## First Non-Negotiables
67
+
68
+ - Never train on a capsule unless `training_allowed` is explicitly true.
69
+ - Never export restricted or personal context unless `export_allowed` is true
70
+ and the request carries human approval.
71
+ - Never route high-impact data to a non-local or non-LumynaX-governed model.
72
+ - Never apply file writes without a visible diff obligation.
73
+ - Never hide selected model identity from the audit record.
product_manifest.json CHANGED
@@ -3,7 +3,7 @@
3
  "slug": "abx-sovereigncode",
4
  "publisher": "AbteeX AI Labs",
5
  "family": "LumynaX sovereign products",
6
- "stage": "product_scaffold",
7
  "positioning": "OpenCode-style local coding agent with Data Capsule sovereignty controls",
8
  "target_region": "NZ",
9
  "target_users": [
@@ -25,7 +25,9 @@
25
  "tool_gate_check",
26
  "capsule_summary",
27
  "opencode_workspace_export",
28
- "opencode_compatible_provider"
 
 
29
  ],
30
  "runtime_entrypoints": [
31
  "python -m tinyluminax.products.sovereigncode.cli evaluate",
@@ -33,11 +35,17 @@
33
  "python -m tinyluminax.products.sovereigncode.cli policy-matrix",
34
  "python -m tinyluminax.products.sovereigncode.cli tool-check",
35
  "python -m tinyluminax.products.sovereigncode.cli opencode-config",
36
- "python -m tinyluminax.products.sovereigncode.cli ui"
 
 
37
  ],
38
  "integration_surfaces": [
39
  "OpenAI-compatible /v1/chat/completions via MaramaRoute",
40
  "OpenAI-compatible /v1/models via MaramaRoute",
 
 
 
 
41
  "OpenCode custom provider config",
42
  "local CLI policy evaluator",
43
  "governed coding-turn planner",
 
3
  "slug": "abx-sovereigncode",
4
  "publisher": "AbteeX AI Labs",
5
  "family": "LumynaX sovereign products",
6
+ "stage": "local_runtime",
7
  "positioning": "OpenCode-style local coding agent with Data Capsule sovereignty controls",
8
  "target_region": "NZ",
9
  "target_users": [
 
25
  "tool_gate_check",
26
  "capsule_summary",
27
  "opencode_workspace_export",
28
+ "opencode_compatible_provider",
29
+ "policy_api_service",
30
+ "persistent_audit_ledger"
31
  ],
32
  "runtime_entrypoints": [
33
  "python -m tinyluminax.products.sovereigncode.cli evaluate",
 
35
  "python -m tinyluminax.products.sovereigncode.cli policy-matrix",
36
  "python -m tinyluminax.products.sovereigncode.cli tool-check",
37
  "python -m tinyluminax.products.sovereigncode.cli opencode-config",
38
+ "python -m tinyluminax.products.sovereigncode.cli ui",
39
+ "python -m tinyluminax.products.sovereigncode.cli serve",
40
+ "python -m tinyluminax.products.sovereigncode.cli audit"
41
  ],
42
  "integration_surfaces": [
43
  "OpenAI-compatible /v1/chat/completions via MaramaRoute",
44
  "OpenAI-compatible /v1/models via MaramaRoute",
45
+ "SovereignCode GET /health and GET /v1/audit",
46
+ "SovereignCode POST /v1/evaluate",
47
+ "SovereignCode POST /v1/plan-turn",
48
+ "SovereignCode POST /v1/tool-check",
49
  "OpenCode custom provider config",
50
  "local CLI policy evaluator",
51
  "governed coding-turn planner",
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "abteex-sovereigncode"
7
- version = "0.2.0"
8
  description = "AbteeX SovereignCode: local-first coding agent with Data Capsule sovereignty controls."
9
  readme = "README.md"
10
  requires-python = ">=3.11"
 
4
 
5
  [project]
6
  name = "abteex-sovereigncode"
7
+ version = "0.3.0"
8
  description = "AbteeX SovereignCode: local-first coding agent with Data Capsule sovereignty controls."
9
  readme = "README.md"
10
  requires-python = ">=3.11"
quickstart.py CHANGED
@@ -28,6 +28,10 @@ if __name__ == "__main__":
28
  "ui",
29
  "--smoke",
30
  ],
 
 
 
 
31
  [
32
  "policy-matrix",
33
  "--capsule",
 
28
  "ui",
29
  "--smoke",
30
  ],
31
+ [
32
+ "serve",
33
+ "--smoke",
34
+ ],
35
  [
36
  "policy-matrix",
37
  "--capsule",
schemas/data_capsule.schema.json CHANGED
@@ -1,88 +1,88 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://abteex.com/schemas/sovereigncode/data-capsule.schema.json",
4
- "title": "AbteeX SovereignCode Data Capsule",
5
- "type": "object",
6
- "required": ["capsule_id", "subject_id", "jurisdiction", "sensitivity"],
7
- "additionalProperties": true,
8
- "properties": {
9
- "capsule_id": {
10
- "type": "string",
11
- "minLength": 1
12
- },
13
- "subject_id": {
14
- "type": "string",
15
- "minLength": 1
16
- },
17
- "jurisdiction": {
18
- "type": "string",
19
- "default": "NZ"
20
- },
21
- "sensitivity": {
22
- "type": "string",
23
- "enum": ["public", "internal", "restricted", "personal", "health", "iwi", "taonga"]
24
- },
25
- "allowed_purposes": {
26
- "type": "array",
27
- "items": { "type": "string" },
28
- "default": ["inference", "coding_assistance"]
29
- },
30
- "denied_purposes": {
31
- "type": "array",
32
- "items": { "type": "string" },
33
- "default": []
34
- },
35
- "resident_regions": {
36
- "type": "array",
37
- "items": { "type": "string" },
38
- "default": ["NZ"]
39
- },
40
- "data_classes": {
41
- "type": "array",
42
- "items": { "type": "string" },
43
- "default": ["source_code"]
44
- },
45
- "retention_days": {
46
- "type": "integer",
47
- "minimum": 0,
48
- "default": 30
49
- },
50
- "export_allowed": {
51
- "type": "boolean",
52
- "default": false
53
- },
54
- "training_allowed": {
55
- "type": "boolean",
56
- "default": false
57
- },
58
- "personal_detail_level": {
59
- "type": "string",
60
- "enum": ["none", "anonymous", "pseudonymous", "identifiable", "sensitive_identifiable"],
61
- "default": "none"
62
- },
63
- "consent_scopes": {
64
- "type": "array",
65
- "items": { "type": "string" },
66
- "default": []
67
- },
68
- "data_subject_rights": {
69
- "type": "array",
70
- "items": { "type": "string" },
71
- "default": ["access", "correction", "deletion_request", "processing_objection"]
72
- },
73
- "revoked": {
74
- "type": "boolean",
75
- "default": false
76
- },
77
- "schema_context": {
78
- "type": "string",
79
- "default": "https://schema.org"
80
- },
81
- "consent_record": {
82
- "type": "string"
83
- },
84
- "metadata": {
85
- "type": "object"
86
- }
87
- }
88
- }
 
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://abteex.com/schemas/sovereigncode/data-capsule.schema.json",
4
+ "title": "AbteeX SovereignCode Data Capsule",
5
+ "type": "object",
6
+ "required": ["capsule_id", "subject_id", "jurisdiction", "sensitivity"],
7
+ "additionalProperties": true,
8
+ "properties": {
9
+ "capsule_id": {
10
+ "type": "string",
11
+ "minLength": 1
12
+ },
13
+ "subject_id": {
14
+ "type": "string",
15
+ "minLength": 1
16
+ },
17
+ "jurisdiction": {
18
+ "type": "string",
19
+ "default": "NZ"
20
+ },
21
+ "sensitivity": {
22
+ "type": "string",
23
+ "enum": ["public", "internal", "restricted", "personal", "health", "iwi", "taonga"]
24
+ },
25
+ "allowed_purposes": {
26
+ "type": "array",
27
+ "items": { "type": "string" },
28
+ "default": ["inference", "coding_assistance"]
29
+ },
30
+ "denied_purposes": {
31
+ "type": "array",
32
+ "items": { "type": "string" },
33
+ "default": []
34
+ },
35
+ "resident_regions": {
36
+ "type": "array",
37
+ "items": { "type": "string" },
38
+ "default": ["NZ"]
39
+ },
40
+ "data_classes": {
41
+ "type": "array",
42
+ "items": { "type": "string" },
43
+ "default": ["source_code"]
44
+ },
45
+ "retention_days": {
46
+ "type": "integer",
47
+ "minimum": 0,
48
+ "default": 30
49
+ },
50
+ "export_allowed": {
51
+ "type": "boolean",
52
+ "default": false
53
+ },
54
+ "training_allowed": {
55
+ "type": "boolean",
56
+ "default": false
57
+ },
58
+ "personal_detail_level": {
59
+ "type": "string",
60
+ "enum": ["none", "anonymous", "pseudonymous", "identifiable", "sensitive_identifiable"],
61
+ "default": "none"
62
+ },
63
+ "consent_scopes": {
64
+ "type": "array",
65
+ "items": { "type": "string" },
66
+ "default": []
67
+ },
68
+ "data_subject_rights": {
69
+ "type": "array",
70
+ "items": { "type": "string" },
71
+ "default": ["access", "correction", "deletion_request", "processing_objection"]
72
+ },
73
+ "revoked": {
74
+ "type": "boolean",
75
+ "default": false
76
+ },
77
+ "schema_context": {
78
+ "type": "string",
79
+ "default": "https://schema.org"
80
+ },
81
+ "consent_record": {
82
+ "type": "string"
83
+ },
84
+ "metadata": {
85
+ "type": "object"
86
+ }
87
+ }
88
+ }
schemas/openai_chat_route_request.schema.json CHANGED
@@ -1,95 +1,95 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://abteex.com/schemas/marama-route/openai-chat-route-request.schema.json",
4
- "title": "MaramaRoute OpenAI-Compatible Chat Route Request",
5
- "type": "object",
6
- "required": ["messages"],
7
- "additionalProperties": true,
8
- "properties": {
9
- "model": {
10
- "type": "string",
11
- "default": "lumynax/auto"
12
- },
13
- "messages": {
14
- "type": "array",
15
- "items": {
16
- "type": "object",
17
- "required": ["role", "content"],
18
- "properties": {
19
- "role": {
20
- "type": "string"
21
- },
22
- "content": {}
23
- },
24
- "additionalProperties": true
25
- }
26
- },
27
- "tools": {
28
- "type": "array"
29
- },
30
- "response_format": {
31
- "type": "object"
32
- },
33
- "route": {
34
- "$ref": "#/$defs/routeOptions"
35
- },
36
- "routing": {
37
- "$ref": "#/$defs/routeOptions"
38
- },
39
- "metadata": {
40
- "type": "object",
41
- "properties": {
42
- "marama_route": {
43
- "$ref": "#/$defs/routeOptions"
44
- }
45
- },
46
- "additionalProperties": true
47
- }
48
- },
49
- "$defs": {
50
- "routeOptions": {
51
- "type": "object",
52
- "additionalProperties": true,
53
- "properties": {
54
- "jurisdiction": {
55
- "type": "string",
56
- "default": "NZ"
57
- },
58
- "data_sensitivity": {
59
- "type": "string",
60
- "default": "internal"
61
- },
62
- "task_type": {
63
- "type": "string",
64
- "enum": ["general", "code", "reasoning", "multimodal", "embedding"]
65
- },
66
- "min_context_tokens": {
67
- "type": "integer",
68
- "minimum": 1,
69
- "default": 4096
70
- },
71
- "requires_local": {
72
- "type": "boolean",
73
- "default": true
74
- },
75
- "requires_tools": {
76
- "type": "boolean",
77
- "default": false
78
- },
79
- "requires_json": {
80
- "type": "boolean",
81
- "default": false
82
- },
83
- "license_allowlist": {
84
- "type": "array",
85
- "items": { "type": "string" }
86
- },
87
- "max_fallbacks": {
88
- "type": "integer",
89
- "minimum": 0,
90
- "default": 3
91
- }
92
- }
93
- }
94
- }
95
- }
 
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://abteex.com/schemas/marama-route/openai-chat-route-request.schema.json",
4
+ "title": "MaramaRoute OpenAI-Compatible Chat Route Request",
5
+ "type": "object",
6
+ "required": ["messages"],
7
+ "additionalProperties": true,
8
+ "properties": {
9
+ "model": {
10
+ "type": "string",
11
+ "default": "lumynax/auto"
12
+ },
13
+ "messages": {
14
+ "type": "array",
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["role", "content"],
18
+ "properties": {
19
+ "role": {
20
+ "type": "string"
21
+ },
22
+ "content": {}
23
+ },
24
+ "additionalProperties": true
25
+ }
26
+ },
27
+ "tools": {
28
+ "type": "array"
29
+ },
30
+ "response_format": {
31
+ "type": "object"
32
+ },
33
+ "route": {
34
+ "$ref": "#/$defs/routeOptions"
35
+ },
36
+ "routing": {
37
+ "$ref": "#/$defs/routeOptions"
38
+ },
39
+ "metadata": {
40
+ "type": "object",
41
+ "properties": {
42
+ "marama_route": {
43
+ "$ref": "#/$defs/routeOptions"
44
+ }
45
+ },
46
+ "additionalProperties": true
47
+ }
48
+ },
49
+ "$defs": {
50
+ "routeOptions": {
51
+ "type": "object",
52
+ "additionalProperties": true,
53
+ "properties": {
54
+ "jurisdiction": {
55
+ "type": "string",
56
+ "default": "NZ"
57
+ },
58
+ "data_sensitivity": {
59
+ "type": "string",
60
+ "default": "internal"
61
+ },
62
+ "task_type": {
63
+ "type": "string",
64
+ "enum": ["general", "code", "reasoning", "multimodal", "embedding"]
65
+ },
66
+ "min_context_tokens": {
67
+ "type": "integer",
68
+ "minimum": 1,
69
+ "default": 4096
70
+ },
71
+ "requires_local": {
72
+ "type": "boolean",
73
+ "default": true
74
+ },
75
+ "requires_tools": {
76
+ "type": "boolean",
77
+ "default": false
78
+ },
79
+ "requires_json": {
80
+ "type": "boolean",
81
+ "default": false
82
+ },
83
+ "license_allowlist": {
84
+ "type": "array",
85
+ "items": { "type": "string" }
86
+ },
87
+ "max_fallbacks": {
88
+ "type": "integer",
89
+ "minimum": 0,
90
+ "default": 3
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }