Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All notable changes to OpenCode Model Control are recorded here. The project follows [Semantic Versioning](https://semver.org/).

## 0.4.0

- Treat empty SDK-default endpoints as unspecified rather than invalid, without letting a missing public URL certify a custom gateway.
- Accept OpenCode provider-owned authentication transports on Paid routes when the exact model binding matches. Do not inspect transport source or fall back to another billing connection.
- Add connection snapshots and settings schema 4. Existing Paid installs migrate to `verified-pricing`; selecting the new Paid control adopts `configured-connections`.
- Free routing still requires verified free prices. Configured Paid access can use a host route when public estimates are unavailable.
- Report tokens separately from OpenCode-recorded cost. Missing cost stays unreported. Quota may be not reported. Historical usage is not relabelled after a login change.
- Show configured connection billing declarations, evidence sources, exact role bindings, and separate captured usage by billing kind and currency. Connection edits require current revisions; drafts survive conflicts.
- Reload connection evidence in planner and MCP routes. Bound attribution work and retention, preserve assistant-message history and price snapshots, and flush on host disposal.
- Verify upgrades from the exact public 0.3.0 package under both Free and legacy Paid policy, including private backups and guarded update/recovery.
- Managed integration surface version 3; an explicit Connect/update and OpenCode restart is required for plugin behavior changes.

## 0.3.0

- Replace historical free-model authorization with exact provider/model/API pricing evidence from credential-free public Models.dev metadata, including all supported supplied billing dimensions. Refresh every 15 minutes while active; expire evidence after 24 hours and block missing, conflicting, malformed, or expired pricing at dispatch.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Documentation-only changes should still run `npm run verify` when practical. Sta
4. Update documentation when behavior, support, security, or benchmark claims change.
5. Run `npm run verify` and report any check you could not run.

Routing changes must keep unknown/expired pricing blocked and preserve the verified-free default. Saving Paid is the explicit authorization for known-paid routing; with auto-include on, it also authorizes future eligible known-paid models without a per-model click. Preserve explicit disables and never write inferred enrollment as saved intent. Public metadata cannot expand host-effective capabilities. Preserve existing ranking and defer authentication integrations unless separately approved. Connector changes must preserve unrelated OpenCode configuration, fail closed on ownership conflicts, and include isolated install/disconnect tests.
Routing changes must preserve the verified-free default. Free and migrated `verified-pricing` Paid still block unknown/expired public prices. After a user saves the new Paid control (`configured-connections`), a configured host route may be eligible without a public estimate; malformed endpoints and changed bindings stay blocked. Preserve explicit disables and never write inferred enrollment as saved intent. Public metadata cannot expand host-effective capabilities or certify a custom endpoint. Preserve existing ranking and defer authentication integrations unless separately approved. Connector changes must preserve unrelated OpenCode configuration, fail closed on ownership conflicts, and include isolated install/disconnect tests.

Do not include credentials, private prompts, user transcripts, proprietary source code, benchmark data you cannot redistribute, or code copied from closed-source routers. Contributions must be clean-room work or compatible third-party material with its provenance and license recorded.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The control panel runs on `127.0.0.1`. OpenCode remains responsible for provider

The running app is authoritative for model names, availability, pricing evidence, and role eligibility.

> This source documents **0.3.0**; `@latest` installs the version currently published on [npm](https://www.npmjs.com/package/opencode-model-control). Check the [release index](https://github.com/BitL8-ByteShort/opencode-model-control/releases) for availability and the [support matrix](docs/support-matrix.md) for verified compatibility.
> This source documents **0.4.0**; `@latest` installs the version currently published on [npm](https://www.npmjs.com/package/opencode-model-control). Check the [release index](https://github.com/BitL8-ByteShort/opencode-model-control/releases) for availability and the [support matrix](docs/support-matrix.md) for verified compatibility. 0.4.0 is implemented in this tree; public publication is a separate authorized gate.

## What it does

Expand Down Expand Up @@ -110,7 +110,7 @@ The connector writes absolute Node and package CLI paths, so a source checkout d

The [GitHub release index](https://github.com/BitL8-ByteShort/opencode-model-control/releases) lists published versioned tarballs and checksums. Download the exact release asset, verify its SHA-256 against that release's checksum, then install the local file with `npm install --global /absolute/path/to/downloaded-package.tgz`. Historical package digests are recorded in the [historical package ledger](https://github.com/BitL8-ByteShort/opencode-model-control/blob/v0.2.1/packages/README.md). The [release checklist](docs/releasing.md) contains the maintainer-only 0.3.0 publication and verification procedure.

## What “Update available models” means (0.3.0)
## What “Update available models” means (0.4.0)

The button asks the installed OpenCode CLI for its effective model list with plugin-aware discovery and `--refresh`. This reflects OpenCode's resolved provider configuration, including its provider and model filters.

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The manual runtime access check is never automatic. It requires explicit provide

Catalog refresh sends a credential-free request only to the fixed `https://models.dev/api.json` metadata endpoint, with JSON accept and conditional ETag/Last-Modified headers. Redirects are rejected; request time and response size are bounded (8 seconds, 32 MiB). No metadata-provided URL is fetched and no prompt, attachment, usage, selected-model list, local config, or provider credential is sent. Public metadata servers still receive ordinary request/network metadata. The private conditional cache carries an exact source digest and retrieval/expiry times; failed retrieval never extends the 24-hour pricing lifetime. Refresh checks run every 15 minutes while active and on stale startup or manual request.

Pricing is matched by the exact provider/full model key and API identity (model ID, npm adapter, and normalized endpoint). A similarly named model, a `-free` suffix, arbitrary CLI zeros, and bundled historical evidence cannot authorize free routing. Model Control fetches the fixed public `https://models.dev/api.json` endpoint without credentials; URLs inside metadata are never fetched. Complete, finite, nonnegative input/output rates are required. Every supported supplied billing dimension counts: reasoning, cache read/write, audio input/output, context tiers, legacy over-200k rates, and experimental modes. With complete valid evidence, any positive rate means paid; all supplied rates must be valid and exactly zero for free. Missing, malformed, unsupported, or conflicting evidence is unknown and blocked. Complete positive CLI evidence can establish `reported-paid` when independent evidence does not contradict it; CLI zero cannot establish free.
Pricing is matched by the exact provider/full model key and API identity (model ID, npm adapter, and normalized endpoint). Raw empty, absent, and null URLs are unspecified SDK defaults, not a wildcard for custom endpoints. A similarly named model, a `-free` suffix, arbitrary CLI zeros, and bundled historical evidence cannot authorize free routing. Model Control fetches the fixed public `https://models.dev/api.json` endpoint without credentials; URLs inside metadata are never fetched. Complete, finite, nonnegative input/output rates are required. Every supported supplied billing dimension counts: reasoning, cache read/write, audio input/output, context tiers, legacy over-200k rates, and experimental modes. With complete valid evidence, any positive rate means paid; all supplied rates must be valid and exactly zero for free. Missing, malformed, unsupported, or conflicting evidence is unknown. Unknown prices cannot authorize Free or migrated verified-pricing Paid. After the user saves configured-connection Paid access, a configured host route may be used without a public estimate. Complete positive CLI evidence can establish `reported-paid` when independent evidence does not contradict it; CLI zero cannot establish free, and CLI cost cannot override a public-price route mismatch. Provider-owned authentication transports are not inspected or logged.

Pricing evidence expires after **24 hours**, checked at route time even without another refresh. Successful HTTP 200 or cached 304 revalidation renews public-source freshness; a failed attempt does not. Cached evidence remains usable only until its existing expiry. Public-source digests and timestamps describe retrieved metadata, not a billing guarantee or model-quality score.

Expand Down
204 changes: 204 additions & 0 deletions benchmarks/schemas/connection-store.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://opencode-model-control.local/schemas/connection-store.schema.json",
"title": "OpenCode Model Control Connection Snapshot",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"revision",
"scopeId",
"connections"
],
"properties": {
"schemaVersion": {
"const": 1
},
"revision": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"scopeId": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"description": "Private installation scope UUID. Not a path, account name, or credential fingerprint."
},
"connections": {
"type": "array",
"items": {
"$ref": "#/$defs/connection"
}
}
},
"$defs": {
"hex32": {
"type": "string",
"pattern": "^[a-f0-9]{32}$"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"quota": {
"type": "object",
"additionalProperties": false,
"required": [
"source",
"unit",
"limit",
"used",
"remaining",
"resetsAt",
"observedAt",
"expiresAt"
],
"properties": {
"source": {
"enum": [
"host",
"provider-adapter"
]
},
"unit": {
"enum": [
"tokens",
"requests",
"credits",
"percent"
]
},
"limit": {
"type": [
"number",
"null"
],
"minimum": 0
},
"used": {
"type": [
"number",
"null"
],
"minimum": 0
},
"remaining": {
"type": [
"number",
"null"
],
"minimum": 0
},
"resetsAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"observedAt": {
"$ref": "#/$defs/timestamp"
},
"expiresAt": {
"$ref": "#/$defs/timestamp"
}
}
},
"connection": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"providerId",
"bindingRevision",
"authKind",
"billing",
"transportVisibility",
"inventoryObservedAt",
"entitlement",
"quota"
],
"properties": {
"id": {
"$ref": "#/$defs/hex32"
},
"providerId": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
},
"bindingRevision": {
"$ref": "#/$defs/hex32"
},
"authKind": {
"enum": [
"oauth",
"api-key",
"none",
"unknown"
]
},
"billing": {
"type": "object",
"additionalProperties": false,
"required": [
"kind",
"source",
"observedAt"
],
"properties": {
"kind": {
"enum": [
"subscription",
"metered-api",
"prepaid",
"local",
"free",
"unknown"
]
},
"source": {
"enum": [
"host",
"provider-adapter",
"user-declared",
"unknown"
]
},
"observedAt": {
"type": [
"string",
"null"
],
"format": "date-time"
}
}
},
"transportVisibility": {
"enum": [
"declared-endpoint",
"host-managed"
]
},
"inventoryObservedAt": {
"$ref": "#/$defs/timestamp"
},
"entitlement": {
"enum": [
"reported-active",
"reported-revoked",
"not-reported"
]
},
"quota": {
"anyOf": [
{
"type": "null"
},
{
"$ref": "#/$defs/quota"
}
]
}
}
}
}
}
Loading