Publish expanded LumynaX product platform package
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +29 -3
- SMOKE_TESTS.md +2 -0
- architecture.md +95 -95
- configs/default_policy.yaml +26 -26
- configs/gateway.local.json +13 -0
- configs/provider_aliases.yaml +30 -30
- configs/routing_policy.yaml +20 -20
- configs/service.local.json +14 -0
- examples/capsule.personal-sovereignty-profile.json +45 -45
- examples/capsule.restricted-nz-code.json +28 -28
- examples/opencode.marama-route.json +28 -28
- examples/request.allowed-local-edit.json +15 -15
- examples/request.code-restricted.json +14 -14
- examples/request.denied-training.json +15 -15
- examples/request.openai-chat-code.json +44 -44
- examples/request.personal-memory-read.json +19 -19
- integrations/opencode-compatible-provider.md +96 -96
- integrations/opencode-provider.json +28 -28
- marama_route/__init__.py +4 -0
- marama_route/_ui_server.py +8 -2
- marama_route/cli.py +25 -0
- marama_route/configs/default_policy.yaml +26 -26
- marama_route/configs/gateway.local.json +13 -0
- marama_route/configs/provider_aliases.yaml +30 -30
- marama_route/configs/routing_policy.yaml +20 -20
- marama_route/configs/service.local.json +14 -0
- marama_route/examples/capsule.personal-sovereignty-profile.json +45 -45
- marama_route/examples/capsule.restricted-nz-code.json +28 -28
- marama_route/examples/opencode.marama-route.json +28 -28
- marama_route/examples/request.allowed-local-edit.json +15 -15
- marama_route/examples/request.code-restricted.json +14 -14
- marama_route/examples/request.denied-training.json +15 -15
- marama_route/examples/request.openai-chat-code.json +44 -44
- marama_route/examples/request.personal-memory-read.json +19 -19
- marama_route/gateway.py +204 -204
- marama_route/integrations/opencode-compatible-provider.md +96 -96
- marama_route/integrations/opencode-provider.json +28 -28
- marama_route/platform.py +359 -359
- marama_route/registry.py +150 -150
- marama_route/router.py +113 -113
- marama_route/schemas/data_capsule.schema.json +88 -88
- marama_route/schemas/openai_chat_route_request.schema.json +95 -95
- marama_route/server.py +312 -0
- policy-packs/nz-personal-sovereignty.yaml +59 -59
- product_blueprint.md +73 -73
- product_manifest.json +11 -3
- pyproject.toml +1 -1
- quickstart.py +4 -0
- schemas/data_capsule.schema.json +88 -88
- 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:
|
| 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
|
| 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
|
| 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
|
| 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": "
|
| 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.
|
| 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 |
+
}
|