From d54ebe1763e94f9bc3c9e80fed0194d459a150ba Mon Sep 17 00:00:00 2001 From: Chris <51251284+BitL8-ByteShort@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:41:35 -0400 Subject: [PATCH 1/2] Fix plan access, model availability, partial discovery and Usage IDs Signed-off-by: Chris <51251284+BitL8-ByteShort@users.noreply.github.com> --- src/core/access-policy.js | 19 +++++++++++ src/core/catalog.js | 5 +-- src/core/eligibility.js | 3 +- src/core/pricing.js | 5 +-- src/server/opencode-cli.js | 4 ++- src/server/opencode-usage.js | 2 +- src/server/service.js | 13 +++++++- src/ui/model-control.js | 7 ++++ test/core/eligibility.test.js | 12 ++++++- test/core/pricing.test.js | 15 +++++++++ test/server/catalog-v2.test.js | 11 +++++++ test/server/connection-policy-v4.test.js | 41 +++++++++++++++++++++++- test/server/opencode-usage.test.js | 9 ++++++ test/ui/connections.test.js | 15 ++++++++- 14 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 src/core/access-policy.js diff --git a/src/core/access-policy.js b/src/core/access-policy.js new file mode 100644 index 0000000..4861d92 --- /dev/null +++ b/src/core/access-policy.js @@ -0,0 +1,19 @@ +// A plan-specific slot's zero token rates describe included usage, not the +// account's subscription/credit charges. This conservative gate does not infer +// billing, authentication, entitlement, or quota from a provider name. +export function isPlanProvider(modelId) { + const provider = typeof modelId === "string" + ? modelId.split("/", 1)[0].toLowerCase() + : ""; + return provider === "opencode-go" || provider === "kimi-for-coding" || + /(?:^|-)(?:coding|code|token|step)-plan(?:-|$)/.test(provider); +} + +export function guardPlanPricing(modelId, pricing) { + if (!isPlanProvider(modelId) || pricing?.class !== "free") return pricing; + return { + ...pricing, + class: "unknown", + reasons: [...new Set([...(pricing.reasons ?? []), "plan-access-unverified"])], + }; +} diff --git a/src/core/catalog.js b/src/core/catalog.js index 8a29999..4cf0b06 100644 --- a/src/core/catalog.js +++ b/src/core/catalog.js @@ -1,5 +1,6 @@ import { classifyPricingEvidence, unknownPricing, normalizeApiIdentity, capabilityDetails, digestJson } from "./pricing.js"; import { resolveEligibility } from "./eligibility.js"; +import { guardPlanPricing } from "./access-policy.js"; import { pricingSchema, capabilityDetailsSchema } from "./catalog-evidence.js"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; @@ -184,7 +185,7 @@ function normalizeModel(value) { let pricing, capabilities; try { - pricing = pricingSchema.parse(value.pricing ?? unknownPricing('legacy-no-source-freshness')); + pricing = pricingSchema.parse(guardPlanPricing(id, value.pricing ?? unknownPricing('legacy-no-source-freshness'))); capabilities = capabilityDetailsSchema.parse(value.capabilities ?? { effective: capabilityDetails({}, 'legacy', null), supplemental: null, }); @@ -296,7 +297,7 @@ export function isVerifiedFree(model) { } export function classifyModelPricing(model, options) { - return classifyPricingEvidence(model?.pricing, options); + return classifyPricingEvidence(guardPlanPricing(model?.id, model?.pricing), options); } export function modelSupports({ model, role, modalities, access }) { diff --git a/src/core/eligibility.js b/src/core/eligibility.js index d622f21..cbd620f 100644 --- a/src/core/eligibility.js +++ b/src/core/eligibility.js @@ -1,4 +1,5 @@ import { classifyPricingEvidence } from "./pricing.js"; +import { guardPlanPricing } from "./access-policy.js"; import { COST_POLICIES, COST_PREFERENCES, @@ -7,7 +8,7 @@ import { } from "./constants.js"; function classifyModelPricing(model, options) { - return classifyPricingEvidence(model?.pricing, options); + return classifyPricingEvidence(guardPlanPricing(model?.id, model?.pricing), options); } function modelEnabled(settings, modelId) { diff --git a/src/core/pricing.js b/src/core/pricing.js index 964ef72..0d04dd7 100644 --- a/src/core/pricing.js +++ b/src/core/pricing.js @@ -1,5 +1,6 @@ import { createHash } from "node:crypto"; import { isPlainObject } from "./utils.js"; +import { guardPlanPricing } from "./access-policy.js"; export const MODELS_DEV_URL = "https://models.dev/api.json"; export const PRICING_TTL_MS = 24 * 60 * 60 * 1000; @@ -282,7 +283,7 @@ export function normalizeModelsDev(raw, { fetchedAt, digest } = {}) { } models[`${providerId}/${key}`] = { api, - pricing, + pricing: guardPlanPricing(`${providerId}/${key}`, pricing), capabilities: capabilityDetails(model, "models.dev", fetchedAt), }; } @@ -312,7 +313,7 @@ export function resolveModelEvidence(live, snapshot) { const priceRouteMismatch = !identityConflict && !publicRatesApply(publicApi, api); return { - ...record.pricing, + ...guardPlanPricing(live.id, record.pricing), ...(identityConflict ? { class: "unknown", reasons: ["identity-conflict"] } : priceRouteMismatch diff --git a/src/server/opencode-cli.js b/src/server/opencode-cli.js index 1edaacc..563e1e7 100644 --- a/src/server/opencode-cli.js +++ b/src/server/opencode-cli.js @@ -120,7 +120,9 @@ export function parseOpenCodeVerboseCatalog( provider: splitModelId(id)[0], name: typeof value?.name === "string" ? value.name : splitModelId(id)[1], - status: value?.status === "active" ? "active" : "unavailable", + // OpenCode filters deprecated and disabled alpha models before listing. + // Listed beta/alpha records remain usable, subject to normal policy. + status: ["active", "beta", "alpha"].includes(value?.status) ? "active" : "unavailable", // OpenCode can normalize missing prices to zero. A zero reported here is // not sufficient evidence that an arbitrary provider model is free. priceClass: reportedPricing.class === "paid" ? "paid" : "unknown", diff --git a/src/server/opencode-usage.js b/src/server/opencode-usage.js index 4f97c9c..ff59dea 100644 --- a/src/server/opencode-usage.js +++ b/src/server/opencode-usage.js @@ -12,7 +12,7 @@ const DEFAULT_TIMEOUT_MS = 10_000; const MAX_OUTPUT_BYTES = 1024 * 1024; const MAX_MODEL_ROWS = 250; const PROVIDER_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u; -const MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:+/-]*$/u; +const MODEL_ID_PATTERN = /^[A-Za-z0-9@~][A-Za-z0-9._:+/@~-]*$/u; const CAVEATS = Object.freeze([ "Token counts are usage. OpenCode-recorded cost is not a provider bill or subscription charge.", "Missing cost or token fields stay unreported instead of becoming zero.", diff --git a/src/server/service.js b/src/server/service.js index cc2fe6f..4214ffc 100644 --- a/src/server/service.js +++ b/src/server/service.js @@ -409,8 +409,19 @@ export class ControlService { settingsPath: this.settingsPath, locked: true, }); + // An incomplete provider inventory cannot establish removals. Include + // cached discovered identities for omitted models, while fresh records + // still replace their API identity and invalidate actual route changes. + const observedProviderIds = new Set(models.map(model => model.provider ?? model.id.split("/")[0])); + const observedModelIds = new Set(models.map(model => model.id)); + const bindingModels = discovered.complete === true ? models : [ + ...previousCatalog.models.filter(model => model.discovered === true && + observedProviderIds.has(model.provider ?? model.id.split("/")[0]) && + !observedModelIds.has(model.id)), + ...models, + ]; const observedConnections = observeConnections({ - providers: providersFromLiveModels(models), + providers: providersFromLiveModels(bindingModels), previousConnections: previousConnections.connections, scopeId: previousConnections.scopeId, now: this.now(), diff --git a/src/ui/model-control.js b/src/ui/model-control.js index ed6b6e5..ab0ada6 100644 --- a/src/ui/model-control.js +++ b/src/ui/model-control.js @@ -1,3 +1,5 @@ +import { isPlanProvider } from "../core/access-policy.js"; + const AVAILABLE_STATES = new Set([ "available", "ready", @@ -52,6 +54,11 @@ export function isModelFree(model) { } export function modelCostClass(model) { + const priceClass = reportedModelCostClass(model); + return isPlanProvider(model?.id) && priceClass === "free" ? "unknown" : priceClass; +} + +function reportedModelCostClass(model) { if (["free", "paid", "unknown"].includes(model?.pricingClass)) { if ( model?.pricing?.expiresAt && diff --git a/test/core/eligibility.test.js b/test/core/eligibility.test.js index 1c600d3..7dbea91 100644 --- a/test/core/eligibility.test.js +++ b/test/core/eligibility.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import { loadModelCatalog, syntheticPricing } from "../fixtures/catalog.js"; -import { createDefaultSettings, eligibleModelsForRole } from "../../src/core/index.js"; +import { createDefaultSettings, eligibleModelsForRole, classifyModelPricing } from "../../src/core/index.js"; import { resolveEligibility } from "../../src/core/eligibility.js"; function paidUnknown(catalog, extras = {}) { @@ -29,6 +29,16 @@ function paidUnknown(catalog, extras = {}) { return model; } +test("saved zero-rate plan models remain blocked in Free before refresh or billing declaration", () => { + const catalog = loadModelCatalog(); + const model = { ...catalog.models[0], id: "zai-coding-plan/glm-5.3-flash" }; + const settings = createDefaultSettings(catalog); + assert.equal(classifyModelPricing(model), "unknown"); + assert.equal(resolveEligibility({ model, settings }).allowed, false); + assert.equal(resolveEligibility({ model, settings: { ...settings, + costPolicy: "known-cost", paidEligibility: "configured-connections" } }).allowed, true); +}); + test("eligibility matrix: free stays verified-free; configured paid allows unknown estimates", () => { const catalog = loadModelCatalog(); const free = catalog.models.find((model) => model.id === "opencode/big-pickle"); diff --git a/test/core/pricing.test.js b/test/core/pricing.test.js index 5608241..38081e4 100644 --- a/test/core/pricing.test.js +++ b/test/core/pricing.test.js @@ -141,6 +141,21 @@ test("raw absent, malformed and CLI normalized zeros cannot authorize free", () assert.equal(evidence(cost).class, "unknown"); assert.equal(evidence({ input: 0, output: 0 }).class, "free"); }); +test("zero token rates on plan-specific providers do not certify free access, including old public caches", () => { + for (const providerId of ["zai-coding-plan", "alibaba-coding-plan", "minimax-coding-plan", "xiaomi-token-plan-sgp", "opencode-go", "kimi-for-coding"]) { + const api = { id: "model", npm: "@ai-sdk/openai-compatible", url: "https://api.example/v1" }; + const id = `${providerId}/model`; + const snapshot = normalizeModelsDev({ + [providerId]: { id: providerId, npm: api.npm, api: api.url, + models: { model: { id: "model", cost: { input: 0, output: 0 } } } }, + }, { fetchedAt: at }); + assert.equal(resolveModelEvidence({ id, api }, snapshot).class, "unknown", providerId); + assert.ok(resolveModelEvidence({ id, api }, snapshot).reasons.includes("plan-access-unverified")); + // A pre-upgrade normalized cache or a 304 response must not retain old permission. + snapshot.models[id].pricing = { class: "free", rates: { input: 0, output: 0 }, reasons: [] }; + assert.equal(resolveModelEvidence({ id, api }, snapshot).class, "unknown", providerId); + } +}); test("every supplied billing dimension participates, including tiers, legacy and modes", () => { for (const dimension of [ "input", diff --git a/test/server/catalog-v2.test.js b/test/server/catalog-v2.test.js index 021481a..a619c93 100644 --- a/test/server/catalog-v2.test.js +++ b/test/server/catalog-v2.test.js @@ -169,6 +169,17 @@ test("new paid, unknown and explicit false capability reports remain distinguish "unknown", ); }); +test("host-listed beta and enabled alpha models retain availability and role eligibility", () => { + for (const status of ["beta", "alpha"]) { + const live = parseOpenCodeVerboseCatalog(`opencode/new-preview\n${JSON.stringify({ ...model("new-preview"), status })}`); + const catalog = validateCatalog(mergeDiscoveredCatalog(loadModelCatalog(), live, { + publicMetadata: publicData("new-preview"), + })); + const entry = catalog.models.find(m => m.id === "opencode/new-preview"); + assert.equal(entry.available, true, status); + assert.ok(eligibleModelsForRole({ catalog, settings: createDefaultSettings(catalog), role: "code-worker", access: "write" }).some(m => m.id === entry.id)); + } +}); test("legacy migration never invents freshness and CLI zeros do not renew bundled authorization", () => { const catalog = loadModelCatalog(); for (const entry of catalog.models) diff --git a/test/server/connection-policy-v4.test.js b/test/server/connection-policy-v4.test.js index 0a99be5..2c58809 100644 --- a/test/server/connection-policy-v4.test.js +++ b/test/server/connection-policy-v4.test.js @@ -7,13 +7,14 @@ import { ControlService } from "../../src/server/service.js"; import { publicFixture, liveModel } from "../fixtures/public-metadata.js"; import { readConnectionSnapshot, writeConnectionSnapshot } from "../../src/server/connection-store.js"; -async function fixture(t) { +async function fixture(t, options = {}) { const root = await mkdtemp(join(tmpdir(), "omc-connection-policy-")); const settingsPath = join(root, "settings.json"); const service = await new ControlService({ settingsPath, discovery: async () => ({ installed: true, complete: true, models: [liveModel("new/model")], error: null }), metadataFetch: async () => new Response(JSON.stringify(publicFixture([{ id: "new/model" }]))), + ...options, }).initialize(); t.after(async () => { await service.close(); await rm(root, { recursive: true, force: true }); }); return { service, settingsPath }; @@ -68,6 +69,44 @@ test("failed discovery preserves cached connection bindings without renewing obs assert.deepEqual(service.getState().connections, before); }); +test("partial provider discovery preserves unchanged mixed routes, billing and pinned assignments", async (t) => { + const models = ["a", "b"].map(key => liveModel(`gateway/${key}`, { + api: { id: key, npm: "@ai-sdk/openai-compatible", url: `https://route-${key}.invalid/v1`, urlValid: true }, + })); + const { service } = await fixture(t, { + discovery: async () => ({ installed: true, complete: true, models }), + metadataFetch: async () => new Response(JSON.stringify(publicFixture(models))), + }); + let state = service.getState(); + const connection = state.connections[0]; + state = await service.updateSettings({ ...state.settings, + costPolicy: "known-cost", paidEligibility: "configured-connections", + roleAssignments: { ...state.settings.roleAssignments, orchestrator: "gateway/a" }, + roleConnections: { ...state.settings.roleConnections, orchestrator: binding(connection) }, + billingDeclarations: { [connection.id]: { kind: "subscription", bindingRevision: connection.bindingRevision } }, + }, revisions(state)); + assert.equal(service.route({ task: "Explain this function", modality: "text" }).assignments[0].modelId, "gateway/a"); + service.discovery = async () => ({ installed: true, complete: false, models: [models[0]] }); + await service.refreshCatalog(); + state = service.getState(); + assert.equal(state.catalog.find(m => m.id === "gateway/b").available, true); + assert.equal(state.connections[0].bindingRevision, connection.bindingRevision); + assert.equal(state.connections[0].billing.kind, "subscription"); + assert.deepEqual(state.blockedRoles.orchestrator, []); + assert.equal(service.route({ task: "Explain this function", modality: "text" }).assignments[0].modelId, "gateway/a"); + + // Partial observations must still invalidate an actually changed endpoint. + service.discovery = async () => ({ installed: true, complete: false, + models: [{ ...models[0], api: { ...models[0].api, url: "https://changed.invalid/v1" } }], + }); + await service.refreshCatalog(); + state = service.getState(); + assert.notEqual(state.connections[0].bindingRevision, connection.bindingRevision); + assert.equal(state.connections[0].billing.kind, "unknown"); + assert.ok(state.blockedRoles.orchestrator.includes("connection-binding-changed")); + assert.throws(() => service.route({ task: "Explain this function", modality: "text" }), { code: "INVALID_ROLE_ASSIGNMENT" }); +}); + test("new explicit pins cannot bypass or remove their exact connection binding", async (t) => { const { service } = await fixture(t); let state = service.getState(); diff --git a/test/server/opencode-usage.test.js b/test/server/opencode-usage.test.js index 881e05b..a24db36 100644 --- a/test/server/opencode-usage.test.js +++ b/test/server/opencode-usage.test.js @@ -125,6 +125,15 @@ test("usage parser returns bounded provider-reported totals and model attributio ); }); +test("usage accepts the same nested and regional model identities as discovery", () => { + for (const id of ["@cf/qwen/qwen3-coder-30b-a3b-instruct", "claude-model@region", "~vendor/model-latest"]) { + const input = rows({ summary: { model_count: 1 }, models: [{ ...rows()[1], provider_id: "cloudflare-workers-ai", model_id: id }] }); + const result = parseOpenCodeUsageRows(JSON.stringify(input)); + assert.equal(result.byModel[0].id, `cloudflare-workers-ai/${id}`); + assert.equal(result.totals.tokens.total, 435); + } +}); + test("missing cost stays unreported and explicit zero remains zero", () => { const missing = parseOpenCodeUsageRows( JSON.stringify( diff --git a/test/ui/connections.test.js b/test/ui/connections.test.js index 35aa6e2..8884a2f 100644 --- a/test/ui/connections.test.js +++ b/test/ui/connections.test.js @@ -1,10 +1,23 @@ import test from "node:test"; import assert from "node:assert/strict"; -import {normalizeState, selectRoleModel, modelEligibilityReasons, effectiveBilling, toggleEnabledModel} from "../../src/ui/model-control.js"; +import {normalizeState, selectRoleModel, modelEligibilityReasons, effectiveBilling, toggleEnabledModel, modelCostClass, isModelCostAllowed} from "../../src/ui/model-control.js"; import {createEditor, editDraft, receiveSnapshot, finishSave, startSave} from "../../src/ui/editor-state.js"; import {attributedUsageGroups, attributionCoverageWarning} from "../../src/ui/usage-view.js"; const connection = {id: "private-id", providerId: "newvendor", bindingRevision: "r1", billing: {kind: "unknown", source: "unknown"}, entitlement: "not-reported"}; const raw = {settingsRevision: "s1", connectionRevision: "c1", connections: [connection], settings: {costPolicy: "known-cost", paidEligibility: "configured-connections"}, catalog: [{id:"newvendor/model", available:true, modalities:{input:["text"],output:["text"]}, roles:{reviewer:1}, access:["read"], pricingClass:"unknown"}]}; +test("old free labels for plan slots cannot enable Free routing in the panel", () => { + for (const prices of [ + {pricingClass:"free"}, + {pricingClass:undefined, pricing:{class:"unknown"}, free:true}, + {pricingClass:undefined, pricing:{class:"unknown"}, free:{verified:true, inputUsdPerMillion:0, outputUsdPerMillion:0}}, + ]) { + const model = {...raw.catalog[0], id:"minimax-coding-plan/MiniMax-M3", ...prices}; + assert.equal(modelCostClass(model), "unknown"); + assert.equal(isModelCostAllowed(model, {costPolicy:"free-only"}), false); + assert.equal(isModelCostAllowed(model, {costPolicy:"known-cost", paidEligibility:"configured-connections"}), true); + } + assert.equal(modelCostClass({...raw.catalog[0], id:"minimax-coding-plan/MiniMax-M3", pricingClass:"paid", free:true}), "paid"); +}); test("pins capture exact slot binding and retain old pins when the binding changes", () => { const state=normalizeState(raw); const selected=selectRoleModel(state.settings,state.catalog,"reviewer","newvendor/model"); From ad5c7409a82c371d5581b86007d51fdf0d935721 Mon Sep 17 00:00:00 2001 From: Chris <51251284+BitL8-ByteShort@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:41:35 -0400 Subject: [PATCH 2/2] Prepare 0.4.1 release and update compatible dependencies Signed-off-by: Chris <51251284+BitL8-ByteShort@users.noreply.github.com> --- .github/pull_request_template.md | 2 +- CHANGELOG.md | 8 + README.md | 16 +- docs/opencode-integration.md | 2 + ...8-connection-billing-and-usage-grok-4.6.md | 574 ++++++++++++++++++ docs/releasing.md | 18 +- docs/support-matrix.md | 18 +- package-lock.json | 312 +++++----- package.json | 22 +- packages/README.md | 14 +- 10 files changed, 787 insertions(+), 199 deletions(-) create mode 100644 docs/plans/2026-09-08-connection-billing-and-usage-grok-4.6.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2a39691..296dfa2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,7 +8,7 @@ ## Safety and compatibility -- [ ] Unknown pricing and unavailable models still fail closed in every cost mode. +- [ ] Free and legacy verified-price Paid still reject unknown pricing; configured Paid still checks connection identity, availability, and capabilities. - [ ] Paid routing remains an explicit user choice and cannot bypass capability gates. - [ ] No credentials, private prompts, customer data, absolute user paths, or generated local settings are included. - [ ] OpenCode config writes are explicit, receipt-owned, recoverable, and preserve unrelated settings. diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c76dd..49f1fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to OpenCode Model Control are recorded here. The project follows [Semantic Versioning](https://semver.org/). +## 0.4.1 + +- Keep zero token rates on plan-specific provider slots from authorizing Free access, including old saved catalogs and public metadata caches. Configured Paid access remains available without an API estimate. +- Preserve availability for beta and enabled alpha models listed by OpenCode. +- Keep unchanged connection billing and role bindings when incomplete discovery omits models within a provider. Fresh endpoint changes still invalidate bindings. +- Accept nested and regional Usage model IDs containing `@` and `~`. +- Update dependencies within their existing major versions and record the published 0.4.0 release evidence. + ## 0.4.0 - Treat empty SDK-default endpoints as unspecified rather than invalid, without letting a missing public URL certify a custom gateway. diff --git a/README.md b/README.md index 189df98..2f9e86c 100644 --- a/README.md +++ b/README.md @@ -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.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. +> This source documents **0.4.1**; `@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. ## What it does @@ -55,7 +55,7 @@ npm install --global opencode-model-control@latest opencode-model-control ``` -The first command installs the version tagged `latest` on npm and its runtime dependencies. Check `opencode-model-control --version` against the public release notes; an older published version may not include the 0.3.0 behavior described here. The second command starts the local panel and opens it in the default browser. +The first command installs the version tagged `latest` on npm and its runtime dependencies. Check `opencode-model-control --version` against the public release notes; an older published version may not include the fixes described here. The second command starts the local panel and opens it in the default browser. Then: @@ -108,11 +108,11 @@ The connector writes absolute Node and package CLI paths, so a source checkout d ### Direct GitHub release artifact -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.4.0 publication and verification procedure. +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`. Package digests and release evidence are recorded in the [package ledger](packages/README.md). The [release checklist](docs/releasing.md) contains the maintainer publication and verification procedure. -## What “Update available models” means (0.4.0) +## What “Update available models” means (0.4.1) -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. +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. Listed beta and enabled alpha models stay available. An incomplete result preserves omitted model routes and unchanged connection bindings; a freshly observed endpoint change still invalidates saved bindings. Startup refreshes stale metadata before initialization completes; a live service checks every **15 minutes**, and **Update available models** can request an immediate refresh. A shared refresh lease coalesces panel/MCP processes; a recent persisted attempt prevents duplicate periodic work. OpenCode discovery and the independent public metadata fetch run concurrently. Failed or incomplete discovery retains the last usable model records; complete discovery can mark an absent model unavailable while preserving its identity and saved choices. The panel distinguishes last attempt, last successful discovery, and last successful pricing retrieval. A failed refresh cannot renew pricing freshness. Refresh does not invoke provider inference or rewrite OpenCode config; OpenCode itself may normalize its standard `$schema` field. @@ -124,13 +124,15 @@ Catalog state is deliberately split into four concepts: - **Discovered:** OpenCode reported the model. - **Saved inclusion intent:** Policy, explicitly enabled, or explicitly disabled. Effective eligibility also requires current pricing, availability, and role capabilities. -- **Available:** the refreshed metadata reports it active. +- **Available:** OpenCode lists an active, beta, or enabled alpha model. - **Runtime access checked:** a manually confirmed bounded synthetic OpenCode run returned the expected sentinel. OpenCode may have retried a provider failure during that run. Refresh does not make this claim or incur a model charge, and a runtime-access pass is not benchmark evidence. ## Free-first, Paid-first, and pricing evidence 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 pricing evidence is unknown. It cannot authorize Free or legacy verified-price Paid routing; configured Paid routing instead requires an eligible host connection. Complete positive CLI evidence can establish `reported-paid` when independent evidence does not contradict it; CLI zero cannot establish free. +Zero token rates on identified plan-specific provider slots do not prove free access. These records remain Unknown for Free routing, including when loaded from old saved catalogs or public metadata caches. Provider names do not establish account billing, entitlement, authentication, or quota. Configured Paid access can still use an eligible host connection. + 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. **Automatically include new models** defaults on. A model with `selection: "policy"` (including an absent control) follows that setting and the saved Free/Paid policy. Free permits current verified-free evidence only. Configured Paid permits eligible host connections without requiring a public estimate. Saving that Paid mode with auto-include on authorizes future eligible configured models without a separate click for every new model. Turning auto-include off excludes policy-following models; explicit enables still apply. An explicit disable always wins. An enable or role pin cannot bypass availability, capabilities, connection binding, or the selected policy. Free and legacy verified-price Paid still require current pricing evidence. @@ -207,7 +209,7 @@ The isolation guard excludes user/project instructions, external plugins, MCP se ## Easy controls and Advanced tools -The normal 0.3.0 setup path is **Update**, choose a cost preference and inclusion policy, decide whether Omc-Router should become the default agent, **Save**, **Connect**, and restart OpenCode. After setup, saved policy changes apply live within the host-loaded inventory. The default-agent option adds `default_agent: "omc-router"` only when OpenCode has no existing default. A user-owned default is preserved, and disabling the option removes only a value previously added by this installation. +The normal setup path is **Update**, choose a cost preference and inclusion policy, decide whether Omc-Router should become the default agent, **Save**, **Connect**, and restart OpenCode. After setup, saved policy changes apply live within the host-loaded inventory. The default-agent option adds `default_agent: "omc-router"` only when OpenCode has no existing default. A user-owned default is preserved, and disabling the option removes only a value previously added by this installation. The collapsed **Advanced tools for developers** section is optional. It shows the exact managed config path, lets a developer open or reveal that existing file, and previews or exports generated integration JSON. It does not provide an unrestricted config writer. Manual changes to an owned entry make connection health report **Needs attention**, and Model Control will not overwrite the divergence. diff --git a/docs/opencode-integration.md b/docs/opencode-integration.md index fc6c237..47db253 100644 --- a/docs/opencode-integration.md +++ b/docs/opencode-integration.md @@ -34,6 +34,8 @@ For a custom provider that maps models to different endpoints or SDK adapters, a 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; they are not invalid and are not a wildcard for custom gateways. 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 the new Paid control (`configured-connections`), a configured host route may be used when estimates are unavailable. 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. +Zero token rates on identified plan-specific provider slots do not prove free access. These records remain Unknown for Free routing, including old saved catalogs and cached public metadata. This restriction does not infer account billing, entitlement, authentication, or quota. Configured Paid access remains available when the host connection is eligible. + OpenCode owns execution transport. A provider-owned authentication `fetch` may be accepted on Paid routes when the exact selected provider/model and observable connection binding match. Transport visibility is host-managed in that case; Model Control does not claim to have verified the network destination. Task or model route overrides, changed endpoints, and opaque transports under Free policy remain blocked. There is no automatic fallback from a subscription connection to metered API billing. Pricing evidence expires after **24 hours** for Free and legacy verified-pricing access, checked at route time even without another refresh. Configured-connection Paid can continue with a stale-estimate warning. 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, subscription quota, or model-quality score. Quota is **Not reported** unless a supported host adapter actually exposes it. Historical OpenCode usage is not classified from today's login. diff --git a/docs/plans/2026-09-08-connection-billing-and-usage-grok-4.6.md b/docs/plans/2026-09-08-connection-billing-and-usage-grok-4.6.md new file mode 100644 index 0000000..a30b20f --- /dev/null +++ b/docs/plans/2026-09-08-connection-billing-and-usage-grok-4.6.md @@ -0,0 +1,574 @@ +# Connection Billing, Subscription Routing, and Usage — Implementation Plan + +> **Executor: Grok 4.6.** This document is the complete handoff. Follow the checkbox tasks in order, inspect the repository before editing, and maintain an execution log with evidence. If the Superpowers skills are installed, use `superpowers:executing-plans`; otherwise use the equivalent checkpoints specified here. Steps use checkbox (`- [ ]`) syntax for tracking. No Codex-specific tool or subagent is required. + +**Goal:** Let users select compatible models through their configured subscription, metered API, prepaid, gateway, or local connections without confusing missing cost estimates with permission to use a model. Preserve strict verified-free routing and report usage without inventing charges or quota. + +**Architecture:** Separate model metadata, configured connections, access policy, and accounting. OpenCode remains the authority for the running provider inventory and execution transport. Public metadata supplies applicable prices and descriptive capabilities, never account entitlement. One shared eligibility function drives the panel, planner, MCP, and runtime. + +**Tech Stack:** JavaScript ES modules, Node.js 22.12.0 and 24.x, React/TypeScript, existing Node test runner, Playwright, OpenCode plugin and MCP companion. + +**Spec:** Sections 1–5 are the product and data contract. Sections 6–8 define implementation and acceptance. Where a host cannot expose information reliably, represent it as unknown and record the limitation; do not infer it from a provider name or normalized zero price. + +**Proposed release:** 0.4.0. This is a change to billing policy and persisted data, not a replacement of the immutable 0.3.0 release. Confirm the latest repository/package version before choosing the final next version. + +**Prepared:** 2026-09-08. Repository baseline observed at `b931cbc3218b028c5060aaa3eb7997d189dc9ea3` on `main`, package 0.3.0. Revalidate these facts when executing. + +## Global constraints + +- This artifact authorizes planning only. A future instruction to execute starts implementation; publishing requires the applicable release authorization at that time. +- Preserve existing disabled models, requested role assignments, automatic-inclusion preferences, unsaved panel edits, unrelated agents, sessions, and OpenCode configuration. +- Do not weaken Free mode to make a subscription work. A paid subscription is paid access even when OpenCode records zero token cost. +- Never silently switch from a subscription connection to metered API billing, including retries, repair/resume, and exhausted quota handling. +- Do not read, copy, log, hash, or export credentials to detect billing. Do not serialize transport functions, inspect their source, or monkeypatch production networking. +- Make no real paid-provider calls during automated acceptance. Use isolated loopback providers and synthetic credentials. Do not run title or compaction helpers against real providers. +- Keep automatic model discovery, capability enrichment, pricing freshness, settings revision checks, and existing permissions intact. Do not introduce model-name allowlists, benchmark campaigns, model-quality ranking changes, or a new authentication manager. +- The existing `CONTRIBUTING.md` requirement to block all unknown/expired pricing describes 0.3.0. This requested design deliberately changes that rule only for explicitly adopted configured-connection Paid access. Update the contribution/security documentation with the new contract; retain the old rule for Free and migrated legacy Paid. This is a product change, not permission to bypass dispatch identity checks. +- Commit with DCO sign-offs. Use protected-main PR review and the repository's branch policy. If no more specific policy exists, create a `codex/connection-billing-usage` branch. Do not edit protected main as the implementation workflow. +- Local source already contains uncommitted changes in `src/core/pricing.js` and `src/server/opencode-cli.js`. Preserve them before implementation and evaluate them explicitly; they are not verified fixes. Never reset or silently discard them. +- Put internal execution records under `docs/plans/`, matching this repository's existing convention and package exclusion. Do not ship credentials, local state, or test artifacts in npm packages. + +## 1. The problem and reproduced evidence + +The user connected `xai/grok-4.6` and saw Available alongside Unknown — Blocked, with Enable unavailable and no usable role assignment. The screenshot's pricing evidence was fresh and contained `identity-conflict`. Refreshing or selecting Policy did not resolve it. + +Two independent failures were reproduced before this plan. Neither reproduction establishes successful real subscription dispatch. + +### 1.1 Empty SDK-default endpoint is treated as an invalid identity + +The observed raw OpenCode model had: + +```js +{ + id: 'grok-4.6', + api: { id: 'grok-4.6', npm: '@ai-sdk/xai', url: '' } +} +``` + +The saved catalog normalized this to `url: null, urlValid: false`. The exact Models.dev record had `url: null, urlValid: true`. Pricing evidence therefore became Unknown with `identity-conflict`, even though rates were available. Correcting the fresh empty endpoint to an absent SDK-default endpoint in an in-memory diagnostic produced Paid classification. + +OpenCode 1.18.28's `fromModelsDevModel` can produce an empty URL when neither model nor provider supplies one. The xAI SDK then supplies its own default. See [provider implementation](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/provider/provider.ts) and [xAI authentication implementation](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/plugin/xai.ts). + +Fix the fresh normalization boundary. Do not change every cached `urlValid: false` to true: an existing invalid record may represent a real malformed endpoint. A successful new host discovery can replace it with new evidence. + +The existing uncommitted patch also loosens missing-public-URL matching and allows positive CLI costs to override an identity conflict. Those shortcuts are not an acceptable general fix. An unknown public endpoint must not certify arbitrary custom endpoints, and positive recorded costs do not prove matching route identity. + +### 1.2 Runtime rejects provider-owned authentication transports + +`optionsMatch()` in `src/opencode/plugin-runtime.js` rejects any `fetch` option. OpenCode's built-in subscription authentication loaders use custom fetch functions. OpenAI transport selection may also use a custom fetch for API-key operation, so fetch presence does not identify billing mode. + +A hook diagnostic accepted a matching fixture without `fetch` and rejected the same fixture with an inert fetch function as `OMC_DISPATCH_IDENTITY_CONFLICT`, making zero provider requests. See [OpenAI auth loader](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/plugin/openai/codex.ts) and [runtime hook call](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/session/llm/request.ts). + +Public provider responses strip functions; `chat.params` receives the live provider object. Consequently, comparing the serialized public object against the live object as if they contained identical transport evidence is invalid. Passing this hook test alone will not demonstrate that OAuth works in a real host. + +### 1.3 The product model conflates billing, pricing, and usability + +A public token-price catalog cannot determine which plan an account owns, the selected billing connection, or the remaining subscription allowance. Conversely, missing public pricing does not make an otherwise configured paid route unusable. + +This must work across providers. Kimi Code can use an API key with a membership coding endpoint, while Moonshot's ordinary API uses a separate endpoint and billing system. Therefore `API key => metered API` is incorrect. Similarly, `OAuth => subscription`, `cost: 0 => free`, and `major lab => entitled` are incorrect. See [Kimi Code FAQ](https://www.kimi.com/code/docs/en/kimi-code/faq.html). + +## 2. Product contract + +### 2.1 Treat a connection as the billing boundary + +A model describes capabilities. A connection describes the configured path through which OpenCode uses that model. Each route is an exact connection plus exact provider/model identity. + +Examples the design must support without hardcoded model entries: + +| Connection | Authentication may be | Billing may be | Required display | +| --- | --- | --- | --- | +| xAI subscription | OAuth | Subscription | Plan access; quota only when reported | +| xAI API | API key | Metered API | Applicable token rates or estimate unavailable | +| OpenAI Codex plan | OAuth | Subscription | Plan access; recorded tokens distinct from charges | +| Kimi Code | API key or supported login | Subscription | Coding-plan connection, not ordinary Moonshot API billing | +| Moonshot API | API key | Metered API | Exact regional endpoint and applicable API rates | +| Provider credit balance | API key or OAuth | Prepaid | Report balance only from an authorized supported source | +| Custom gateway | Any supported method | Any or unknown | Configured route usable under paid policy; public prices may not apply | +| Local provider | None or another method | Local or unknown | No assumed cloud bill; local infrastructure cost not calculated | +| Unfamiliar provider/model | Unknown | Unknown | Honest unknown fields; same generic eligibility rules | + +These are supported data shapes and test scenarios, not promises that every provider exposes all fields through OpenCode. + +A host with one configured provider slot and one active credential exposes one connection, even if the vendor sells multiple products. Show two simultaneous connections only when the running host actually exposes separate configured slots/routes. Do not duplicate a row into imaginary subscription and API alternatives. + +### 2.2 Separate access policy from estimate availability + +Retain the familiar Free/Paid control with this explicit contract: + +| Situation | Free policy | Paid policy with configured-connection access | +| --- | --- | --- | +| Verified fresh zero rates for the exact effective route | Eligible if all other checks pass | Eligible | +| Confirmed subscription connection | Blocked as paid access | Eligible; pricing estimate optional | +| Known metered/prepaid connection | Blocked when nonzero or unverified | Eligible; estimate optional | +| Unknown, missing, stale, or conflicting public price evidence | Blocked because free use is not verified | Eligible if the configured host route passes identity and capability checks | +| Custom endpoint differs from public pricing route | Public prices cannot authorize Free | Usable configured route; estimate unavailable for that route | +| Saved connection binding differs from current host binding | Blocked | Blocked pending explicit connection review | +| Disabled, missing from host, incompatible, or explicitly revoked | Blocked | Blocked | + +“Eligible” never promises provider entitlement or successful service. A provider may still reject an account, quota, or request. Preserve and explain that failure. Do not convert authentication errors into pricing errors or retry on a different billing connection. + +An expired subscription entitlement explicitly reported by a supported host adapter is an access block. Missing entitlement or quota information is not proof of revocation. Do not invent an entitlement check by making a chargeable request. + +### 2.3 Existing-user migration must preserve authorization + +Version 3 Paid policy only admitted models with verified paid/free pricing. Automatically broadening every existing user's Paid policy would newly permit unpriced connections without their prior authorization. + +Introduce `paidEligibility: 'verified-pricing' | 'configured-connections'`: + +- Migrate existing settings to `verified-pricing`, preserving previous access semantics and all explicit choices. +- Keep a fresh installation's default policy Free. +- The new Paid control describes the broadened behavior: “Allow configured paid connections, including subscriptions. Cost estimates may be unavailable.” Selecting and saving that option sets `configured-connections`. +- Existing Paid users see one concise migration notice and one normal settings action to adopt the new behavior. No repeated per-model confirmation dialog. +- Preserve the legacy restriction until the user changes it. Show its specific blocking reason instead of an unexplained Unknown — Blocked. +- Turning a model off is always allowed. A settings save must not reject a disable because pricing or availability changed. + +This distinction concerns saved policy intent, not the ability of a user declaration to certify a route as free. + +### 2.4 Capabilities stay independent + +Retain all discovered modalities, tools, reasoning, structured-output support, context and output limits, compatible roles, provenance, and successful refresh timestamps. Use tri-state support: reported true, reported false, and not reported. + +Effective host restrictions win over public descriptive enrichment. Missing benchmarks cannot block compatible roles. A subscription connection may expose a different model inventory or restrictions from the same provider's ordinary API; use that connection's actual inventory. + +### 2.5 Use clear panel language + +Show separate fields for connection, access, and pricing: + +- `Subscription · Available`, `Metered API · Available`, or `Billing not reported · Available under Paid policy`. +- `API estimate unavailable`, `Public rates do not match this endpoint`, or `Cached rates expired` as estimate warnings. +- `Disabled by you`, `Free policy requires verified free access`, `Connection changed — review required`, `Reload OpenCode to load this model`, and capability-specific messages as access blocks. +- `Declared by you` versus `Reported by OpenCode` versus `Reported by provider` on billing metadata. + +Do not present unknown API pricing as a routing block when configured-connection paid access allows the route. Show all capability details and preserve the responsive layout, filters, draft behavior, and explicit pins. + +## 3. Data contracts and trust boundaries + +### 3.1 Schemas and identities + +Target settings schema 4, catalog schema 3, new connection-store schema 1, and usage response schema 2. Keep existing route-plan/result versions unless their public structures actually change; if changing them, version their schemas and update every consumer in the same task. + +Connection identity must be a stable, nonsecret identifier derived from the installation's private scope ID and configured provider slot. It must not contain account names, tokens, raw directory paths, or credential fingerprints. The same slot retains its connection ID; its observable binding gets a new revision when endpoint, SDK, or reliably reported active authentication/billing identity changes. + +A binding revision cannot detect credential changes the host does not expose. Surface that limitation. Do not claim a guaranteed subscription-only connection when the available host metadata cannot establish it. + +Proposed plain-data contract: + +```ts +type BillingKind = 'subscription' | 'metered-api' | 'prepaid' | + 'local' | 'free' | 'unknown'; +type EvidenceSource = 'host' | 'provider-adapter' | 'user-declared' | 'unknown'; +type AuthKind = 'oauth' | 'api-key' | 'none' | 'unknown'; + +interface Connection { + id: string; + providerId: string; // actual configured host slot + bindingRevision: string; + authKind: AuthKind; // active method only if reliably exposed + billing: { + kind: BillingKind; + source: EvidenceSource; + observedAt: string | null; + }; + transportVisibility: 'declared-endpoint' | 'host-managed'; + inventoryObservedAt: string; + entitlement: 'reported-active' | 'reported-revoked' | 'not-reported'; + quota: QuotaObservation | null; +} + +interface QuotaObservation { + source: 'host' | 'provider-adapter'; + unit: 'tokens' | 'requests' | 'credits' | 'percent'; + limit: number | null; + used: number | null; + remaining: number | null; + resetsAt: string | null; + observedAt: string; + expiresAt: string; +} + +interface Eligibility { + allowed: boolean; + blockingReasons: string[]; + warnings: string[]; + pricingStatus: 'free' | 'paid' | 'unknown'; + connectionId: string | null; + bindingRevision: string | null; +} +``` + +Validate finite, nonnegative quota values and supported units; percentage values must be within 0–100. Do not derive `remaining` from incompatible windows or unrelated balances. Expired observations remain visibly stale and cannot imply current entitlement or exhaustion. + +Keep existing role-assignment strings for model IDs and add `roleConnections` keyed by the same four roles, with `{ connectionId, bindingRevision }` or null. Resolve these as one unit. Migrate a pin to a connection only when the relationship is unambiguous; otherwise preserve the requested model and show a connection-selection requirement. Never replace it with Automatic. + +### 3.2 Shared functions and ownership + +Create small modules with explicit responsibilities. Add `benchmarks/schemas/connection-store.schema.json` for the connection snapshot; keep quota validation within it. The usage API contract is tested alongside its TypeScript response types rather than introducing an unrelated benchmark schema: + +| File | Responsibility | +| --- | --- | +| `src/core/connections.js` | Validate/sanitize connection records and derive nonsecret binding inputs; no filesystem or networking | +| `src/core/eligibility.js` | Pure shared access decision with stable reason codes | +| `src/server/connection-store.js` | Private atomic connection snapshot persistence and revisions under the existing state lock | +| `src/core/usage-accounting.js` | Validate observations and compute only supported, provenance-labelled estimates | +| `src/server/usage-attribution-store.js` | Bounded private attribution records; atomic upserts, retention, coverage diagnostics | +| `src/opencode/connection-observer.js` | Convert actual host inventory/runtime observations into sanitized connection evidence | + +Use these module interfaces as the implementation seam (all persisted objects are validated before return): + +```ts +// core/connections.js +validateConnection(value: unknown): Connection; +// Throws a sanitized validation error; never echoes the invalid payload. + +// server/connection-store.js +readConnectionSnapshot({ settingsPath, locked = false }): Promise<{ + schemaVersion: 1; revision: string; connections: Connection[]; +}>; +writeConnectionSnapshot({ settingsPath, snapshot, locked = false }): Promise; +// The caller owns the shared state lock when locked is true. + +// opencode/connection-observer.js +observeConnections({ providers, previousConnections, scopeId, now }): Connection[]; +// providers is the actual host inventory; preserve unknown evidence. + +// server/usage-attribution-store.js +upsertUsageObservation({ settingsPath, observation }): Promise; +readUsageAttribution({ settingsPath, from, to }): Promise<{ + observations: UsageObservation[]; + coverage: { firstObservedAt: string | null; droppedCount: number; truncated: boolean }; +}>; + +interface UsageObservation { + eventKey: string; // private HMAC, never raw host/message identifiers + observedAt: string; + connectionId: string | null; + bindingRevision: string | null; + billingKind: BillingKind; + billingSource: EvidenceSource; + tokens: { + input: number | null; output: number | null; + reasoning: number | null; cacheRead: number | null; cacheWrite: number | null; + }; + recordedCost: { amount: number; currency: string | null } | null; + priceSnapshotId: string | null; +} +``` + +Persist immutable, sanitized price evidence with referenced attribution records in the same bounded store. Prune unreferenced snapshots and count them toward its byte limit. Never depend on the mutable current catalog to resolve an old price snapshot. Include supported provider token-semantics metadata with evidence; the fields above alone do not establish whether token categories overlap. + +Do not build a generic plugin ecosystem. Provider adapters, where necessary, are narrow mappings of documented metadata with tests. Unknown providers still work through the generic configured-connection path. + +Use one pure call shape throughout: + +```js +resolveEligibility({ model, connection, settings, role, hostInventory, now }) +// => Eligibility; no writes, requests, credential access, or implicit fallback +``` + +Catalog-only clients may lack a live inventory. Represent host availability as unverified in preview; the runtime must always recheck the actual directory-scoped host inventory before dispatch. The panel cannot certify a future dispatch from an old snapshot. + +### 3.3 Binding and pricing are different identity checks + +Maintain separate results for: + +1. **Dispatch binding:** Does the chosen connection/model still match the configured host route? A mismatch is a hard block. +2. **Public price applicability:** Does the exact public provider/model/endpoint describe that route? A mismatch makes those rates inapplicable, not necessarily the configured paid route unusable. + +Never reuse one generic `identity-conflict` boolean for both decisions. Suggested reason codes include `connection-binding-changed`, `public-price-route-mismatch`, `invalid-endpoint`, `pricing-expired`, `free-access-unverified`, `legacy-paid-pricing-required`, `host-model-missing`, and existing capability-specific codes. + +For raw endpoint normalization, treat exactly `''`, absent, and null as an unspecified SDK default where the upstream contract permits it. A nonempty malformed value, whitespace-only value, URL with forbidden embedded credentials, or unapproved query-bearing endpoint remains invalid. Preserve prior invalid provenance when merely reloading cached normalized data. + +Public metadata may only certify a default endpoint when the SDK/provider default is itself established. A missing URL is not a wildcard. Custom routes can have unknown pricing and still be enabled under the appropriate paid policy. + +### 3.4 Provider-owned transport + +Trust OpenCode as the authority that owns the configured provider's execution transport. Accept a host-owned opaque authentication transport under configured-connection paid policy when the exact selected provider/model and observable connection binding match. + +Do not claim that an opaque fetch's final network destination was independently verified. Mark transport visibility `host-managed`. Continue rejecting conflicting task/model-level route overrides, changed declared endpoints, wrong SDK/model identities, and inherited variants that do not apply to the selected model. + +Free policy must not use public zero rates to certify an opaque transport whose effective billing route cannot be established. An adapter may provide stronger route evidence, but a user declaration or fetch presence cannot supply it. + +The observer must distinguish provider-level options from task/model overrides using the real OpenCode hook structures. If the supported versions cannot expose that distinction reliably, preserve the block for the ambiguous case, explain it, and record a release limitation. Do not ship a blanket `fetch is allowed` bypass to satisfy a test. + +### 3.5 Refresh and writes + +Keep 15-minute stale refreshes and the shared refresh lock. Refresh public metadata without credentials, retain conditional requests and bounded validation, and preserve separate attempt/discovery/pricing success times. Pricing still expires after 24 hours for Free and legacy verified-pricing access; configured-connection Paid can continue with a stale-estimate warning. + +Extend `readControlSnapshot()` and existing locks to read coherent settings/catalog/connections revisions. Persist discoveries separately from user choices. User declarations and role bindings are settings intent, not values a background refresh may overwrite. + +Settings saves carry expected settings and connection revisions plus the existing catalog conflict context. Revalidate edited fields against the current snapshot; merge untouched discoveries. Return a structured conflict for a changed edited binding and keep the UI draft intact. Avoid introducing separate locks with inverted acquisition order. + +## 4. Usage and cost reporting contract + +### 4.1 Tokens are usage; billing is a separate observation + +Subscription requests consume tokens too. Show tokens whenever the host reports them, regardless of billing kind. Distinguish: + +| Value | Meaning | Allowed label | +| --- | --- | --- | +| OpenCode stored cost | A host-recorded value, potentially calculated from its catalog | OpenCode-recorded cost | +| Exact supported API calculation | Estimate using the actual applicable dated API rates and sufficient token dimensions | Estimated API cost | +| Optional public-rate comparison for a subscription | What a comparable API request might cost; not money charged | API-equivalent estimate | +| Authorized provider billing record | Actual bill/charge only when the source explicitly establishes it | Provider-reported charge | +| No reliable value | Unknown, not zero | Not reported / Estimate unavailable | + +Never label all OpenCode cost as provider-reported billing. Never label subscription zero cost as free usage. Do not estimate a subscription's monthly fee per request. Do not add monthly fees, credits, API estimates, and provider charges into one total. + +Retain null versus explicit zero. Report partial totals with observation coverage and provenance. Do not sum currencies or estimate bases together. If a source does not establish a currency, leave it unknown. + +### 4.2 Historical attribution must not change after login changes + +Existing OpenCode history commonly records provider/model but not the connection's billing mode at dispatch. Do not classify that history using today's authentication or current user declaration. + +Add a private attribution store for future owned-role requests: + +- Capture connection ID, binding revision, billing evidence source/kind, and applicable pricing snapshot at dispatch time. +- Bind completion to that snapshot using host/session/message identifiers in memory and private HMAC identifiers on disk. Store the HMAC salt privately; never export raw session/message IDs, prompts, headers, or credentials. +- Confirm the supported OpenCode event lifecycle before implementation. If the final assistant-message identity cannot be associated reliably, mark it unattributed rather than join by time or model name. +- Upsert completed messages idempotently. Multiple message updates must not count the same tokens twice. Failed requests without accounting are not zero-cost completions. +- If an observed connection change makes the dispatch evidence ambiguous, retain the observation with unknown billing rather than attach the new account's identity. +- Use bounded asynchronous writes, the shared state-lock convention, mode 0600 files, mode 0700 directories, and atomic replacement. Persistence failure must not interrupt a successful provider answer; expose a sanitized diagnostic and incomplete coverage. +- Bound retention to 90 days, 10,000 completed records, and 10 MiB, applying whichever limit is reached first. Bound pending writes to 1,000 records; report dropped observations and coverage gaps. Test shutdown flushing and interruption recovery. + +Keep all-time OpenCode provider/model totals as the overall host view. The attributed connection view is a captured subset with an explicit observation period and retention limits. Never add the subset to the overall totals. Historical and unrelated/unobserved traffic remains unattributed. + +### 4.3 Estimates and quota must remain honest + +Use the rates captured for the request, not today's rates retroactively. Calculate only when required billing dimensions and token semantics are known. Account for input/output, cache read/write, context tiers, and reasoning/audio dimensions when applicable. Do not double-count reasoning tokens already included in output or cached tokens already included in input. + +An unsupported or missing billing dimension yields unavailable or explicitly partial estimation; it must not silently become zero. A schema may contain a positive rate without enough token usage to compute a complete charge. + +Quota adapters only consume sanitized host metadata or a documented, explicitly configured provider integration. Do not scrape account pages, discover local companion ports, or reuse provider tokens for speculative endpoints. The [Kimi CLI server API](https://www.kimi.com/code/docs/en/kimi-code-cli/reference/server-api.html) describes a separate companion, not an OpenCode quota endpoint. Its existence does not authorize automatic calls from this plugin. + +For this release, ship quota display and ingestion contracts with `Not reported` as a valid complete result. Live quota retrieval is supported only for sources actually exposed and verified in the target host. Do not claim universal quota availability. + +## 5. Runtime contract + +For every new owned-role message: + +1. Read a coherent settings/catalog/connections snapshot. +2. Read the running instance's `GET /config/providers` through `client.config.providers({ query: { directory }, throwOnError: true })`. Do not substitute the broader public catalog endpoint. +3. Resolve requested role, exact model ID, and connection binding using the shared eligibility function. +4. For Automatic, choose among currently eligible loaded candidates using existing ranking. Do not invent duplicate billing connections or change ranking quality policy. +5. For a pin absent from the host, preserve the pin and stop with reload guidance before a provider request. +6. Apply the exact selected provider/model; clear incompatible inherited variants and validate the actual `chat.params` binding without rejecting legitimate provider-owned transport merely because it is a function. +7. Capture attribution context before the request and completion accounting when available. + +Apply this to all four owned agents: `omc-router`, `omc-code-worker`, `omc-vision-worker`, and `omc-reviewer`. + +A resumed repair must retain the original exact code-worker model **and connection binding**, then recheck current policy and capabilities. If revoked, disabled, unavailable, or changed, stop with the reason. Never silently move the repair to another paid connection. + +Keep media-only tool denial, reviewer restrictions, delegation limits, and unrelated agents unchanged. Routine role/billing preference edits require no generated config writes or restart. Plugin instruction/permission changes still require the guarded integration update/restart boundary. Do not dispose the host automatically. + +## 6. Implementation tasks + +Record each completed checkbox, commands, outcomes, and remaining limitations in a private execution log under `docs/plans/`. Stop at a concrete blocker with evidence; do not mark unexecuted platform tests as passed. + +### Task 1 — Baseline and preserve the working tree + +**Read:** `package.json`, lockfile, `CONTRIBUTING.md`, nearest `AGENTS.md` files, existing `docs/plans/2026-09-07-v030-implementation.md`, repository support/security/integration docs, and existing tests for the files below. + +- [ ] Record `git status --short`, HEAD, remotes, current version, Node/npm/OpenCode versions, and the existing two-file diff. Save a private patch before moving anything. Do not include local account data. +- [ ] Create a work branch or isolated worktree without discarding the original changes. Bring the patch into the worktree only deliberately and record whether each hunk is retained, replaced, or excluded. +- [ ] Run `npm ci` using the locked dependencies on the current platform. Do not reuse copied macOS native binaries on Linux. +- [ ] Establish `npm run verify` baseline. Diagnose environment failures before attributing them to the product. Use existing package scripts; do not invent `lint` or `typecheck` scripts from unrelated ancestor guidance. +- [ ] Add failing regression fixtures for the two reproduced problems before changing behavior. Confirm failure messages match the intended defects. + +**Gate:** Cleanly attributable baseline and preserved source edits. No credentials or paid calls used. + +### Task 2 — Fix normalization without relaxing public-price identity + +**Modify:** `src/core/pricing.js`, `src/server/opencode-cli.js`, their existing pricing/discovery tests. Extend `test/fixtures/public-metadata.js` with sanitized endpoint shapes. + +- [ ] Write a table-driven test for raw absent/null/empty URL, valid explicit default, documented custom endpoint, malformed nonempty URL, whitespace-only URL, and cached invalid provenance. +- [ ] Assert exact `xai/grok-4.6` fixture with raw empty endpoint no longer becomes invalid solely because it uses the SDK default. Use unfamiliar and nested IDs in the same test to prove generic behavior. +- [ ] Assert missing public URL does not certify an arbitrary custom endpoint, and positive CLI cost cannot override a true price-route mismatch. +- [ ] Implement explicit raw-versus-normalized handling. Test CLI parse, merge, serialized reload, and recovery after successful fresh discovery. +- [ ] Keep missing rates distinct from zero and maintain all supported billing-dimension and 24-hour freshness checks. + +Example intended assertions, adapted to actual function signatures after reading the code: + +```js +assert.equal(normalizeFreshIdentity({ url: '' }).urlValid, true); +assert.equal(reloadIdentity({ url: null, urlValid: false }).urlValid, false); +assert.equal(publicRatesApply(publicDefault, customGateway), false); +``` + +These helper names illustrate the behavioral contract; either introduce them with these responsibilities or test the equivalent existing entry points. Do not leave unused facade functions. + +**Run:** `node --test test/core/pricing.test.js test/server/opencode-cli.test.js test/server/models-dev.test.js`. + +**Commit:** Sign off a focused endpoint-normalization fix once its regression tests pass. + +### Task 3 — Add connection snapshots and safe migration + +**Create:** `src/core/connections.js`, `src/server/connection-store.js`, `src/opencode/connection-observer.js`, `benchmarks/schemas/connection-store.schema.json`, `test/core/connections.test.js`, and `test/server/connection-store.test.js`. + +**Modify:** `src/core/constants.js`, `src/core/settings.js`, `src/core/catalog.js`, `src/server/state-snapshot.js`, `src/server/settings-store.js`, `src/server/catalog-store.js`, `src/server/service.js`, `benchmarks/schemas/router-settings.schema.json`, `benchmarks/schemas/model-catalog.schema.json`, and UI state types. + +- [ ] Add failing migration tests covering Free/Paid v3 settings, every disabled model, explicit pins, auto-inclusion, absent provider, multiple possible connections, and older supported settings versions. +- [ ] Implement the schema versions and contracts from §3. Keep role model IDs stable and add connection bindings without silently changing requested assignments. +- [ ] Preserve old Paid semantics through `paidEligibility: verified-pricing`. Test the explicit settings transition to configured-connection access. +- [ ] Create private pre-migration backups with rollback instructions. A failed migration must leave the previous valid state readable and must not partially advance revisions. +- [ ] Extend the coherent snapshot and compare-and-save paths. Test simultaneous refresh/save, another process discovering a connection, and an edited connection changing before save. +- [ ] Test adapters using xAI/OpenAI/Kimi shapes plus an unfamiliar provider. Supported auth methods alone must not populate the active auth type; API-key Kimi Code must not be auto-labelled metered API. +- [ ] Preserve missing auth/billing metadata as unknown. Permit an explicit user billing declaration, label its provenance, and invalidate its binding when the observable configured connection changes. + +**Run:** Focused new connection tests plus `node --test test/core/settings*.test.js test/core/schemas.test.js test/server/state-store-v3.test.js test/server/settings-api-v3.test.js`. + +**Gate:** No refresh writes user intent, no token-derived identity, no silent expansion of migrated Paid policy. + +### Task 4 — Centralize eligibility and expose it consistently + +**Create:** `src/core/eligibility.js`, `test/core/eligibility.test.js`. + +**Modify:** `src/core/catalog.js`, `src/core/planner.js`, `src/core/settings.js`, `src/server/service.js`, `src/mcp/server.js`, `src/ui/model-control.js`, API/state types, and schema consumers affected by response additions. + +- [ ] Encode the §2.2 matrix as parameterized tests before implementation. +- [ ] Replace duplicated price-only gates with `resolveEligibility`. Keep stable blocking/warning reason codes and human-readable explanations at API/UI boundaries. +- [ ] Ensure explicit Disable always saves; enabling validates access policy, not the presence of an estimate alone. +- [ ] Allow unknown-priced configured routes under the newly selected Paid behavior, including valid custom gateways. Keep malformed endpoints and changed saved bindings blocked. +- [ ] Preserve unavailable pins. A blocked reviewer pin must not block an unrelated valid media or coding task. +- [ ] Reload saved catalog and connections as well as settings before MCP planning. Test a long-running MCP process seeing another process's discoveries. +- [ ] Verify capability changes update role compatibility even when prices are unchanged, preserving explicit false values and unknown fields. + +**Run:** `node --test test/core/eligibility.test.js test/core/planner.test.js test/mcp/snapshot-v3.test.js test/mcp/server.test.js test/ui/model-control.test.js` plus the new settings/policy tests. + +**Gate:** Panel, MCP, planner, and runtime cannot disagree merely because one still uses the old pricing gate. + +### Task 5 — Fix actual routing and retain connection identity on repair + +**Modify:** `src/opencode/plugin-runtime.js`, `src/opencode/plugin.js`, `src/opencode/connection-observer.js`, plugin runtime/live-routing tests, and integration metadata in `src/installer/index.js` when the managed payload changes. + +- [ ] Add failing hook tests for provider-owned fetch, task/model route overrides, wrong SDK/model, changed endpoint, unknown pricing in Paid, and the same unknown pricing in Free. +- [ ] Observe the provider object and directory-scoped inventory through supported host surfaces. Implement the opaque-transport distinction from §3.4; do not inspect transport function source. +- [ ] Recheck the binding immediately before dispatch. Assert no provider request for wrong binding, disabled model, missing host model, expired verified-free evidence, incompatible media/tools, or revoked entitlement. +- [ ] Extend all four owned-role routes and resumed repairs to retain connection ID/revision as well as exact model identity. Test a subscription-to-API switch between initial code generation and repair. +- [ ] Preserve inherited variant cleanup, media tool denial, reviewer permissions, and existing delegation caps. +- [ ] Add a real OpenCode process test that exercises the actual built-in authentication-loader path with synthetic credentials and loopback transport. A handmade `chat.params` object is not enough. + +**Run:** `node --test test/opencode/plugin-runtime.test.js test/opencode/live-routing-v3.test.js` and the new auth-transport acceptance case in `npm run test:host`. + +**Gate:** Actual host dispatch succeeds on allowed transport; blocked cases have a counted zero provider requests. No real account request is needed or allowed in these tests. + +### Task 6 — Correct accounting and capture future connection attribution + +**Create:** `src/core/usage-accounting.js`, `src/server/usage-attribution-store.js`, `test/core/usage-accounting.test.js` and `test/server/usage-attribution-store.test.js`. + +**Modify:** `src/server/opencode-usage.js`, `src/server/service.js`, usage API handler/types, plugin completion-event handling, `test/server/opencode-usage.test.js`, and `test/server/usage-api.test.js`. + +- [ ] Add failing tests for missing versus explicit zero cost/tokens, mixed currencies, invalid numeric values, repeated completion updates, partial windows, and a billing-mode change mid-session. +- [ ] Inspect real supported host database and completion-event schemas. Preserve a bounded read-only SQL query; do not select prompts or arbitrary message bodies into application logs. +- [ ] Replace coercion of unknown accounting to zero with nullable values and coverage. Keep corrupted-schema detection, but do not reject an entire valid usage response merely because individual optional fields are absent. +- [ ] Label host-recorded cost honestly. Keep overall historical totals separate from the captured connection subset. +- [ ] Capture dispatch-time attribution and idempotent completion updates with private HMAC identifiers, bounded retention, bounded pending writes, atomic persistence, and recoverable diagnostics. +- [ ] Test write failures, lock contention, process interruption, repeated events after restart, queue overflow, retention pruning, and clean shutdown. +- [ ] Implement only supported rate calculations, using request-time pricing evidence and tested token semantics. Return unavailable for unsupported dimensions; test cache, reasoning, context tier, and audio cases. +- [ ] Expose quota observations when supported and otherwise `Not reported`. Never calculate remaining plan allowance from API prices. + +**Run:** New accounting/store tests plus `node --test test/server/opencode-usage.test.js test/server/usage-api.test.js` and plugin completion integration tests. + +**Gate:** Switching a login cannot relabel old usage; no report implies $0, unlimited quota, or a provider charge without evidence. + +### Task 7 — Make the panel and API explain the distinction + +**Modify:** `src/ui/types.ts`, `src/ui/api.ts`, `src/ui/App.tsx`, `src/ui/editor-state.js`, `src/ui/model-control.js`, existing `ModelTable`, `RoleAssignments`, `UsagePanel`, `RoutingOverview`, `RouteTester`, and `ConfigPanel` components; `src/server/app.js` and state responses as needed. + +- [ ] Add connection/billing/access/pricing fields using the existing responsive layout. Keep model search, provider/capability filters, and expandable complete capability details. +- [ ] Add the Paid behavior migration notice and normal save action from §2.3. Keep automatic inclusion beside policy with clear wording about new configured paid models. +- [ ] Offer billing declarations only for metadata the host does not establish; display their source. A declaration cannot override Free verification, incompatible capabilities, a changed binding, or host absence. +- [ ] Keep explicit blocked pins visible with the saved model/connection and actionable reason. Allow Disable even when Enable is unavailable. +- [ ] Separate recorded tokens, host-recorded cost, estimates, charges, and quota. Show coverage, currency/source, freshness, and unknown values without misleading totals. +- [ ] Extend save/refresh generation fences to connection metadata. Delayed responses must not replace newer edits or successful saves. A conflict preserves the user's draft. +- [ ] Ensure keyboard operation, readable errors, and mobile layout. Avoid using color alone for billing or access status. + +**Run:** Existing UI contract/draft tests, new billing presentation tests, `npm run check`, and `npm run test:browser`. + +**Browser scenarios:** Fresh Free install; migrated Paid install; adoption of configured Paid access; unknown-priced subscription route selection; explicit Disable after metadata expiry; pin after host connection change; refresh during editing; stale save response; usage with null cost and unknown quota; narrow viewport and keyboard-only controls. + +### Task 8 — Acceptance, documentation, and release readiness + +**Modify:** `scripts/host-acceptance.mjs`, `scripts/package-acceptance.mjs`, `scripts/browser/panel.spec.mjs`, fixture/environment helpers, artifact guards where needed, `.github/workflows/ci.yml`, `CONTRIBUTING.md`, README, support/security/integration docs, changelog, package version, and lockfile only when preparing the release. + +- [ ] Run the complete §7 matrix, preserving machine-readable results and sanitized logs. Record exact versions and artifact checksum with each result. +- [ ] Test upgrade from the exact public 0.3.0 package with private backups, policy-preserving migration, guarded connection update, explicit restart, disconnect, and recovery. No routine role change should rewrite config. +- [ ] Document connection versus model, subscription versus API billing, the Paid migration action, missing quotas, opaque transport trust, unknown historical attribution, and estimate limitations. +- [ ] Bump the managed integration version because plugin behavior changes. Explain the explicit update/restart boundary; do not silently update a running host. +- [ ] Run `npm run verify`, `npm run test:browser`, `npm run test:host`, and separately `npm run test:metadata`. Public metadata smoke must not invoke models. +- [ ] Open a signed-off PR with concrete behavior, migration semantics, validation results, and limitations. Run pre-merge acceptance using clearly identified candidate artifacts; these are not the final public artifact. Obtain review before protected-main merge. Do not claim unavailable platform acceptance is complete. +- [ ] After the reviewed change reaches protected main, build one final tarball from its clean source tree using `node scripts/pack-artifact.mjs /absolute/path/to/artifact-directory`, following `docs/releasing.md`. Record its SHA-256 and validate those identical bytes on Linux and macOS with Node 22.12.0 and 24.x using `npm run test:package -- /absolute/path/to/final.tgz`. Set the required exact host binary paths according to that release guide. If any final-byte gate fails, stop publication; fix through another reviewed change and designate a new final candidate explicitly. +- [ ] Only with release authorization, publish that same validated artifact to npm and the immutable matching GitHub release. Verify downloaded artifacts/checksums and installation. Do not rebuild between validation and publication. + +**Gate:** Release-ready means all mandatory acceptance passed or a documented blocker remains. A local test pass is not a public release, and successful publication is not proof of real account entitlement for every provider. + +## 7. Required acceptance matrix + +### Automated behavior matrix + +| Area | Cases that must pass | +| --- | --- | +| Discovery | Grok 4.6, Muse Spark 1.3, unfamiliar free/paid IDs, nested provider/model IDs; no new model-name code required | +| Endpoints | Raw empty SDK default, explicit default, malformed endpoint, custom gateway, missing public endpoint, fresh recovery from cached invalid record | +| Prices | Missing/zero/positive, additional dimensions, source conflict, repricing, 24-hour expiry, failed refresh preserving last success | +| Billing | xAI subscription/API, OpenAI subscription/API-shaped transports, API-key Kimi coding plan, Moonshot API, prepaid/local/unknown provider | +| Policy | Free strictness, migrated legacy Paid, explicit configured Paid adoption, unknown price eligible only when policy permits, disabling always allowed | +| Connections | One slot is one connection, genuine separate slots selectable, binding change blocks pin, declarations labelled, auth-method list is not active auth proof | +| Capabilities | Full details, explicit false, unknown support, changing context/modalities/tools, missing benchmarks | +| Runtime | All four roles, live A-to-B, C absent until reload, variant cleanup, opaque host transport, task override rejected, no billing fallback | +| Repair | Exact model and connection retained; changed/revoked/disabled binding stops before provider call | +| Concurrency | Refresh/save overlap, edits during refresh, delayed responses, separate process discovery, coherent MCP reload, lock and shutdown cleanup | +| Usage | Unknown/zero distinction, partial coverage, no mixed-basis sums, historical attribution unknown, mid-session auth switch, idempotent events, retention/failure recovery | +| Isolation | Unrelated sessions/agents unchanged, no real paid endpoint, helpers remain local, secrets absent from artifacts and logs | + +### Real host and platform matrix + +Minimum supported-host validation: OpenCode **1.18.22 and 1.18.28**. If the built-in auth-loader behavior differs, record and test the exact supported behavior for each version. Do not extend a support claim to an untested newer version. + +For each version, start an isolated host with loopback model A and already-loaded B. Change roles while it runs; assert actual outbound requests use B across primary, specialist, media, and resumed workflows. Introduce C after initialization and assert reload guidance plus zero invalid requests. Verify subscription-shaped transport via the actual host/auth-loader path, not only mocked hook calls. + +Use fresh temporary HOME/XDG/config/data directories for child processes without changing the parent shell's HOME. Bind local servers explicitly to loopback, deny non-loopback test egress, and capture destination/count assertions. Synthetic auth data is test-only and must not be accepted by a production bypass. If the upstream built-in loader cannot be exercised safely within this harness, document the precise blocker; do not substitute a passing mocked hook and claim completion. + +Run source and packaged acceptance across Linux/macOS and Node 22.12.0/24.x. Record each actual platform/version combination. At minimum, actual host dispatch and browser interaction must be exercised on both platforms, and the identical final package must pass installation/migration/disconnect checks across all four platform/Node combinations. + +## 8. Executor checkpoint and completion checklist + +At each completed implementation task, rerun its focused tests and make a small signed-off commit with explicit paths after reviewing the diff. Do not stage unrelated files. At every task boundary, record: + +1. Files changed and why. +2. Exact failing regression observed before the fix, where applicable. +3. Commands run and results, including environment-only failures. +4. Migration, routing, privacy, or accounting assumptions verified against actual host behavior. +5. Remaining blockers and the next task. + +Before declaring implementation complete: + +- [ ] Both reproduced bugs are fixed independently and exercised together in a real isolated host. +- [ ] Configured Paid access works without a price estimate; Free remains verified-free only. +- [ ] Provider-independent connections support unknown vendors without billing/name heuristics. +- [ ] Kimi Code is not mistaken for ordinary API billing merely because it uses a key. +- [ ] No automatic subscription-to-API fallback exists. +- [ ] Legacy settings, disabled models, explicit pins, and drafts survive migration/refresh. +- [ ] Historical usage is not relabelled, costs are provenance-labelled, quota can honestly be unknown. +- [ ] Capabilities remain complete and effective host restrictions remain authoritative. +- [ ] Linux/macOS and host-version evidence is real, bounded, and reproducible. +- [ ] Existing uncommitted changes were preserved and their disposition is documented. +- [ ] Review and publication status are stated separately from implementation status. + +## 9. Primary references and research boundaries + +These sources informed the diagnosis and design on 2026-09-08. Recheck before implementing provider-specific adapters; do not encode today's model prices or account products as permanent rules. + +- [OpenCode xAI connection documentation](https://dev.opencode.ai/docs/providers/#xai): separate subscription and API connection methods. +- [OpenCode 1.18.28 provider implementation](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/provider/provider.ts): empty default endpoints, provider options, and public serialization. +- [OpenCode 1.18.28 xAI auth loader](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/plugin/xai.ts): provider-owned OAuth transport and SDK default endpoint behavior. +- [OpenCode 1.18.28 OpenAI auth loader](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/plugin/openai/codex.ts): custom transports and normalized subscription model costs. +- [OpenCode 1.18.28 request implementation](https://github.com/anomalyco/opencode/blob/v1.18.28/packages/opencode/src/session/llm/request.ts): actual hook inputs. +- [Models.dev API documentation](https://github.com/anomalyco/models.dev/blob/dev/README.md#api): public model metadata, not account entitlement or subscription quota. +- [xAI model pricing](https://docs.x.ai/developers/models): API rates; these are not subscription charges. +- [Codex with a ChatGPT plan](https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan): plan-based access and allowance context. +- [Kimi Code FAQ](https://www.kimi.com/code/docs/en/kimi-code/faq.html): coding-plan endpoint and credentials differ from the ordinary Moonshot API. +- [Kimi CLI server API](https://www.kimi.com/code/docs/en/kimi-code-cli/reference/server-api.html): a separate local companion API; not permission to access it automatically. + +## Copyable instruction for Grok 4.6 + +> Read this entire plan and the repository's current instructions. Execute it task by task after implementation is authorized. Start by preserving the existing working-tree changes and verifying the baseline. Use the product/data contracts in this document as the acceptance criteria. Do not substitute provider-name or authentication-type heuristics for connection evidence, weaken verified-free routing, invent usage/charges/quota, or silently switch billing connections. Maintain an evidence-based execution log and run the real isolated OpenCode acceptance, not only unit hooks. Preserve the existing UI and user choices. Stop before any unauthorized public release, and distinguish implemented, tested, reviewed, and published outcomes. diff --git a/docs/releasing.md b/docs/releasing.md index bc9bcca..3e6e8da 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -8,7 +8,7 @@ This maintainer procedure does not assert publication. The canonical repository - Update `package.json`, the root lockfile versions, changelog and behavior/security/support docs. Keep historical evidence dated; do not invent a final digest or publication link. - Independently review the whole change against the approved specification, code and security boundaries. Resolve material findings before merge. Preserve DCO sign-offs on every commit. - Merge through the reviewed PR and protected main, without bypassing required checks. Required contexts remain `verify (22.12.0)` and `verify (24.x)`; also wait for **all** pack and Linux/macOS acceptance jobs and applicable security checks, even if they are not branch-required contexts. -- Keep model ranking, benchmark campaigns, provider authentication integrations, new platform claims, and automatic OpenCode instance disposal outside 0.4.0 scope. +- Keep model ranking, benchmark campaigns, provider authentication integrations, new platform claims, and automatic OpenCode instance disposal outside 0.4.1 scope. ## 2. Source and package-content checks @@ -28,7 +28,7 @@ For source host work, `OMC_HOST_BINARY=/absolute/opencode npm run test:host` req ## 3. Prepare candidates, then build one final tarball from clean protected main -During PR preparation, use `OMC_ARTIFACT_STAGE=candidate node scripts/pack-artifact.mjs /absolute/candidate-artifact` after verification/build. Candidate evidence is review evidence only. Do not create final-mode bytes, publish, or claim the 0.4.0 platform matrix passed before the corresponding gates execute. +During PR preparation, use `OMC_ARTIFACT_STAGE=candidate node scripts/pack-artifact.mjs /absolute/candidate-artifact` after verification/build. Candidate evidence is review evidence only. Do not create final-mode bytes, publish, or claim the 0.4.1 platform matrix passed before the corresponding gates execute. After merge, dispatch `.github/workflows/ci.yml` on `main` with `artifact-stage: final`. It verifies source, builds, packs once, and sends the same `omc-final-tarball` artifact to all acceptance jobs. Final mode rejects a dirty source tree or another CI ref. An authorized equivalent local pack from the exact clean protected-main commit is: @@ -52,7 +52,7 @@ OMC_HOST_BINARY_122=/absolute/opencode-1.18.22 \ OMC_HOST_BINARY_128=/absolute/opencode-1.18.28 \ OMC_EXPECTED_SHA256='' \ OMC_EVIDENCE_PATH=/absolute/evidence/package.json \ -npm run test:package -- /absolute/final-artifact/opencode-model-control-0.4.0.tgz +npm run test:package -- /absolute/final-artifact/opencode-model-control-0.4.1.tgz ``` The harness retrieves only the fixed public npm 0.3.0 metadata and tarball, requires SHA-256 `26a532b44c96d643c0543a78d2fef1ab2c1a3b83886e6715cef0ab683d3413ab` and matching registry SHA-512 integrity before installing the baseline, and records its provenance separately. Retrieval failure or mismatch fails acceptance; it must never substitute another version or continue with unverified bytes. These requests are artifact/metadata traffic, not provider inference. @@ -69,7 +69,7 @@ Run the negative artifact binding proof once (CI does this on Linux/Node22): ```sh OMC_HOST_BINARY=/absolute/opencode-1.18.28 \ OMC_EVIDENCE_PATH=/absolute/evidence/artifact-guards.json \ -node scripts/artifact-guards.mjs /absolute/final-artifact/opencode-model-control-0.4.0.tgz +node scripts/artifact-guards.mjs /absolute/final-artifact/opencode-model-control-0.4.1.tgz ``` This must reject both a deliberately broken plugin tarball and a deliberately broken UI tarball while healthy checkout source is present. It proves acceptance cannot silently fall back to checkout code. @@ -86,9 +86,9 @@ That command retrieves public Models.dev metadata without inference. Neither it - Confirm npm authorization and package ownership. If login or CI authorization is unavailable, finish unaffected work and report the exact external gate. Do not infer success or use a different artifact to work around it. - Confirm GitHub immutable releases are enabled and protect the exact version tag from force updates/deletion **before** creating it. The setting is not retroactive. -- Create a draft GitHub release for `v0.4.0` at the exact reviewed protected-main commit. Stage the final tested tarball, `SHA256SUMS`, pack evidence and redacted acceptance results. Verify draft target, notes, filenames and downloaded digest before publication. -- Publish the exact tested file to npm using the authorized registry flow. The command, only after all gates and authorization, is `npm publish /absolute/final-artifact/opencode-model-control-0.4.0.tgz --access public`. Never publish from the checkout or rebuild for npm. -- Retrieve the exact npm 0.4.0 public tarball, verify registry integrity and its SHA-256 against the final file, and test its installed experience in isolation. +- Create a draft GitHub release for `v0.4.1` at the exact reviewed protected-main commit. Stage the final tested tarball, `SHA256SUMS`, pack evidence and redacted acceptance results. Verify draft target, notes, filenames and downloaded digest before publication. +- Publish the exact tested file to npm using the authorized registry flow. The command, only after all gates and authorization, is `npm publish /absolute/final-artifact/opencode-model-control-0.4.1.tgz --access public`. Never publish from the checkout or rebuild for npm. +- Retrieve the exact npm 0.4.1 public tarball, verify registry integrity and its SHA-256 against the final file, and test its installed experience in isolation. - Publish the finalized GitHub draft when all notes/assets are final, then verify the release is immutable and its public downloaded tarball matches the same SHA-256. - Never replace a published asset, move/reuse a published tag, or delete/recreate the release. Corrections require a new version and artifact. @@ -96,6 +96,6 @@ That command retrieves public Models.dev metadata without inference. Neither it Fetch each channel's public tarball into a separate directory and run the same exact-package acceptance command against each retrieved file. Preserve channel URL, retrieval time, digest/integrity and redacted results. Explicitly compare **both** public downloads with the original tested final bytes. -After confirmed publication, a clean user install can use `npm install --prefix /absolute/disposable-install opencode-model-control@0.4.0`. Verify its CLI version and public installed startup/refresh/Connect/restart/status/Disconnect/restart path; never change a maintainer's everyday global install as this check. README `@latest` commands resolve the registry's published channel, while this release check stays pinned. +After confirmed publication, a clean user install can use `npm install --prefix /absolute/disposable-install opencode-model-control@0.4.1`. Verify its CLI version and public installed startup/refresh/Connect/restart/status/Disconnect/restart path; never change a maintainer's everyday global install as this check. README `@latest` commands resolve the registry's published channel, while this release check stays pinned. -Only after final artifact/public installation verification may the maintainer describe 0.4.0 as published or close release-blocked issues. Do not use automatic issue-closing PR wording before that gate. Historical candidate results remain historical and do not become final-byte evidence. +Only after final artifact/public installation verification may the maintainer describe 0.4.1 as published or close release-blocked issues. Do not use automatic issue-closing PR wording before that gate. Historical candidate results remain historical and do not become final-byte evidence. diff --git a/docs/support-matrix.md b/docs/support-matrix.md index b02707d..a87cce2 100644 --- a/docs/support-matrix.md +++ b/docs/support-matrix.md @@ -1,10 +1,18 @@ # Support matrix -This matrix describes implemented 0.4.0 behavior and dated compatibility evidence. Source verification, final installed-artifact acceptance, and public-channel verification are separate claims. See [Releasing](releasing.md) for the final-byte gates and the [release index](https://github.com/BitL8-ByteShort/opencode-model-control/releases) for published versions. +This matrix describes implemented 0.4.1 behavior and dated compatibility evidence. Source verification, final installed-artifact acceptance, and public-channel verification are separate claims. See [Releasing](releasing.md) for the final-byte gates and the [release index](https://github.com/BitL8-ByteShort/opencode-model-control/releases) for published versions. -## 0.4.0 verification boundary +## 0.4.1 verification boundary -The release targets the same Linux/macOS and Node 22.12.0/24.x matrix, with OpenCode 1.18.22 and 1.18.28. Candidate [CI run 34304377304](https://github.com/BitL8-ByteShort/opencode-model-control/actions/runs/34304377304) verified 20 host scenarios and 57 synthetic loopback requests per host, 14 production browser interactions, and actual 0.3.0 Free/Paid upgrades plus legacy 0.2.1 coverage under every OS/Node combination. This dated candidate result predates the final mixed-endpoint binding correction and is not final-byte evidence. Final and public-download evidence belongs with the immutable release assets after the release checklist passes; no additional platform claim is made here. +The release targets the same Linux/macOS and Node 22.12.0/24.x matrix, with OpenCode 1.18.22 and 1.18.28. Final artifact and public-download evidence belongs with the immutable release assets after the release checklist passes. Earlier releases do not establish acceptance for the new bytes. + +## Historical 0.4.0 platform and artifact evidence + +Final [CI run 34688784337](https://github.com/BitL8-ByteShort/opencode-model-control/actions/runs/34688784337) passed on 2026-09-12 at source commit [`942a94c5eb354da877b22ddc6eb37b098a598f7c`](https://github.com/BitL8-ByteShort/opencode-model-control/commit/942a94c5eb354da877b22ddc6eb37b098a598f7c). All four Linux x64/macOS arm64 and Node 22.12.0/24.20.0 jobs consumed one tarball, SHA-256 `1ee2d3ab864f2615ec3f8c750a400694ac8587129bdef78b0d1b275793f0828b`. + +Each installed-package run passed 18 package checks, both OpenCode hosts (20 scenarios and 57 synthetic loopback requests each), and 14 production browser scenarios with zero failures, skips, or flakes. The matrix covered current installation, 0.3.0 Free/Paid upgrades, legacy 0.2.1 upgrades, private exact migration backups, connection updates, MCP, restart and disconnect recovery. Real-provider inference requests were zero. The [immutable release](https://github.com/BitL8-ByteShort/opencode-model-control/releases/tag/v0.4.0) contains the [final acceptance evidence](https://github.com/BitL8-ByteShort/opencode-model-control/releases/download/v0.4.0/acceptance-evidence.zip). + +On 2026-09-12, the public npm download matched that SHA-256 and registry integrity. A clean version-pinned npm install matched all 83 package files, and the downloaded artifact passed the same package and browser checks on Linux/Node 24.14.0 with both hosts. See [public npm verification](https://github.com/BitL8-ByteShort/opencode-model-control/releases/download/v0.4.0/npm-public-verification.zip) and the [package ledger](../packages/README.md). ## Historical 0.3.0 platform and artifact evidence @@ -41,9 +49,9 @@ A clean version-pinned npm name install reported CLI 0.3.0 and all 74 package fi | Surface | Contract | Boundary | | --- | --- | --- | -| All-provider discovery | Plugin-aware `opencode models --verbose`, no provider filter | `--pure` fallback is explicitly incomplete; preserve last usable records. OpenCode may normalize its own `$schema` line. | +| All-provider discovery | Plugin-aware `opencode models --verbose`, no provider filter | Host-listed beta and enabled alpha models remain available. Incomplete discovery preserves omitted routes and unchanged bindings; fresh endpoint changes invalidate bindings. OpenCode may normalize its own `$schema` line. | | Metadata refresh | Stale startup, every 15 minutes while active, or manual Update | Cross-process coalescing; separate attempted/successful timestamps; no inference or inferred settings/config writes. | -| Pricing | Exact provider/model/API match; complete rates across every supported supplied billing dimension | With complete valid evidence, any positive rate means paid; exact-zero valid public evidence means free; malformed/conflicting/expired pricing is unknown and cannot authorize Free or legacy verified-price Paid. Configured Paid uses eligible host connection evidence without requiring an estimate. No free-name roster. | +| Pricing | Exact provider/model/API match; complete rates across every supported supplied billing dimension | Positive rates mean paid. Zero rates on identified plan-specific slots remain unknown; other complete valid zero public rates can establish free pricing. Malformed/conflicting/expired pricing cannot authorize Free or legacy verified-price Paid. Configured Paid uses eligible host connection evidence without requiring an estimate. No free-name roster. | | Pricing freshness | 24-hour expiry evaluated at routing time | Successful 200/304 renews public evidence; failed requests do not. Neither the source nor OMC guarantees future billing. | | Capabilities | Effective OpenCode report plus separate supplemental public report | Unknown differs from false; full modalities/tools/reasoning/options/structured-output/limits retained. Supplemental metadata cannot expand effective restrictions. | | Inclusion | Default-on auto-include follows saved Free/Paid policy | Explicit disables win. Configured Paid permits future eligible configured models; explicit enables cannot bypass hard gates. | diff --git a/package-lock.json b/package-lock.json index e4468a0..f4f6c24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,45 +1,45 @@ { "name": "opencode-model-control", - "version": "0.4.0", + "version": "0.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencode-model-control", - "version": "0.4.0", + "version": "0.4.1", "license": "MIT", "dependencies": { - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/server": "2.2.0", "jsonc-parser": "3.3.1", - "zod": "4.5.4" + "zod": "4.6.5" }, "bin": { "opencode-model-control": "bin/opencode-model-control.js" }, "devDependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@playwright/test": "1.58.2", - "@types/node": "26.4.0", - "@types/react": "19.2.18", - "@types/react-dom": "19.2.5", + "@modelcontextprotocol/client": "2.2.0", + "@playwright/test": "1.63.0", + "@types/node": "26.6.3", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", "@vitejs/plugin-react": "6.1.1", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "typescript": "7.0.2", - "vite": "8.2.2" + "vite": "8.3.1" }, "engines": { "node": ">=22.12.0" } }, "node_modules/@modelcontextprotocol/client": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0.tgz", - "integrity": "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.2.0.tgz", + "integrity": "sha512-LxCou/CSYQ6dwEnjhLZY0KnEuc8V4UJ3IEQCl/uR2yHobSQIEdJKUlKLRYRF5i5FqRGHy1eK7une3aAWDHLtig==", "dev": true, "license": "MIT", "dependencies": { - "@modelcontextprotocol/core": "2.0.0", + "@modelcontextprotocol/core": "2.2.0", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", @@ -52,9 +52,9 @@ } }, "node_modules/@modelcontextprotocol/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz", - "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.2.0.tgz", + "integrity": "sha512-iLhmprRmWI8EcosOA3wVvww22z02NkgqhV4fBH6f/odQBsi7xnJc0HGmi21yWO+/iKw2yzqVE127Zhna5/+JXw==", "license": "MIT", "dependencies": { "zod": "^4.2.0" @@ -64,12 +64,12 @@ } }, "node_modules/@modelcontextprotocol/server": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz", - "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.2.0.tgz", + "integrity": "sha512-qFnltjus6Gk8Lx6J1w2RXmr3YPN22MNT6qD6zjAkaiWIlFIFxWClz0rXHNuJF/KSUyn5YGhkSvXkWDbQHVLFEQ==", "license": "MIT", "dependencies": { - "@modelcontextprotocol/core": "2.0.0", + "@modelcontextprotocol/core": "2.2.0", "zod": "^4.2.0" }, "engines": { @@ -77,35 +77,35 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.147.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", - "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "version": "0.151.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", + "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.58.2" + "playwright": "1.63.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", - "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", + "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", "cpu": [ "arm" ], @@ -120,9 +120,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", - "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", + "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", "cpu": [ "arm64" ], @@ -137,9 +137,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", - "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", + "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", "cpu": [ "arm64" ], @@ -154,9 +154,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", - "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", + "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", "cpu": [ "x64" ], @@ -171,9 +171,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", - "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", + "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", "cpu": [ "x64" ], @@ -188,9 +188,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", - "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", + "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", "cpu": [ "arm" ], @@ -205,9 +205,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", - "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", + "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", "cpu": [ "arm64" ], @@ -225,9 +225,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", - "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", + "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", "cpu": [ "arm64" ], @@ -245,9 +245,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", - "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", + "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", "cpu": [ "ppc64" ], @@ -265,9 +265,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", - "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", + "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", "cpu": [ "s390x" ], @@ -285,9 +285,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz", - "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", + "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", "cpu": [ "x64" ], @@ -305,9 +305,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", - "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", + "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", "cpu": [ "x64" ], @@ -325,9 +325,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", - "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", + "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", "cpu": [ "arm64" ], @@ -342,9 +342,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", - "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", + "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", "cpu": [ "arm64" ], @@ -359,9 +359,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", - "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", + "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", "cpu": [ "x64" ], @@ -383,19 +383,19 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", - "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "dev": true, "license": "MIT", "dependencies": { @@ -403,13 +403,13 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", "dev": true, "license": "MIT", "peerDependencies": { - "@types/react": "^19.2.0" + "@types/react": "^19.3.0" } }, "node_modules/@typescript/typescript-aix-ppc64": { @@ -1167,9 +1167,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, "funding": [ { @@ -1226,56 +1226,38 @@ } }, "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.58.2" + "playwright-core": "1.63.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" + "node": ">=20" } }, "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=20" } }, "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -1293,7 +1275,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.17", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1302,9 +1284,9 @@ } }, "node_modules/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", - "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", "dev": true, "license": "MIT", "engines": { @@ -1312,26 +1294,26 @@ } }, "node_modules/react-dom": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", - "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", "dev": true, "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "scheduler": "^0.28.0" }, "peerDependencies": { - "react": "^19.2.8" + "react": "^19.3.0" } }, "node_modules/rolldown": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", - "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", + "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.147.0", + "@oxc-project/types": "=0.151.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -1341,27 +1323,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.6", - "@rolldown/binding-android-arm64": "1.2.6", - "@rolldown/binding-darwin-arm64": "1.2.6", - "@rolldown/binding-darwin-x64": "1.2.6", - "@rolldown/binding-freebsd-x64": "1.2.6", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", - "@rolldown/binding-linux-arm64-gnu": "1.2.6", - "@rolldown/binding-linux-arm64-musl": "1.2.6", - "@rolldown/binding-linux-ppc64-gnu": "1.2.6", - "@rolldown/binding-linux-s390x-gnu": "1.2.6", - "@rolldown/binding-linux-x64-gnu": "1.2.6", - "@rolldown/binding-linux-x64-musl": "1.2.6", - "@rolldown/binding-openharmony-arm64": "1.2.6", - "@rolldown/binding-win32-arm64-msvc": "1.2.6", - "@rolldown/binding-win32-x64-msvc": "1.2.6" + "@rolldown/binding-android-arm-eabi": "1.2.11", + "@rolldown/binding-android-arm64": "1.2.11", + "@rolldown/binding-darwin-arm64": "1.2.11", + "@rolldown/binding-darwin-x64": "1.2.11", + "@rolldown/binding-freebsd-x64": "1.2.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", + "@rolldown/binding-linux-arm64-gnu": "1.2.11", + "@rolldown/binding-linux-arm64-musl": "1.2.11", + "@rolldown/binding-linux-ppc64-gnu": "1.2.11", + "@rolldown/binding-linux-s390x-gnu": "1.2.11", + "@rolldown/binding-linux-x64-gnu": "1.2.11", + "@rolldown/binding-linux-x64-musl": "1.2.11", + "@rolldown/binding-openharmony-arm64": "1.2.11", + "@rolldown/binding-win32-arm64-msvc": "1.2.11", + "@rolldown/binding-win32-x64-msvc": "1.2.11" } }, "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "dev": true, "license": "MIT" }, @@ -1451,23 +1433,23 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", + "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.9", "tinyglobby": "^0.2.17" }, "bin": { @@ -1484,7 +1466,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -1552,9 +1534,9 @@ } }, "node_modules/zod": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz", - "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index c75ac67..378c673 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-model-control", - "version": "0.4.0", + "version": "0.4.1", "description": "A local model routing control panel and MCP companion for OpenCode.", "keywords": [ "opencode", @@ -63,20 +63,20 @@ "test:metadata": "node scripts/metadata-smoke.mjs" }, "devDependencies": { - "@modelcontextprotocol/client": "2.0.0", - "@playwright/test": "1.58.2", - "@types/node": "26.4.0", - "@types/react": "19.2.18", - "@types/react-dom": "19.2.5", + "@modelcontextprotocol/client": "2.2.0", + "@playwright/test": "1.63.0", + "@types/node": "26.6.3", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", "@vitejs/plugin-react": "6.1.1", - "react": "19.2.8", - "react-dom": "19.2.8", + "react": "19.3.0", + "react-dom": "19.3.0", "typescript": "7.0.2", - "vite": "8.2.2" + "vite": "8.3.1" }, "dependencies": { - "@modelcontextprotocol/server": "2.0.0", + "@modelcontextprotocol/server": "2.2.0", "jsonc-parser": "3.3.1", - "zod": "4.5.4" + "zod": "4.6.5" } } diff --git a/packages/README.md b/packages/README.md index 266c440..1c98017 100644 --- a/packages/README.md +++ b/packages/README.md @@ -1,9 +1,21 @@ # Release packages -This ledger records verified release tarballs and their SHA-256 digests. npm is the default install channel after registry verification. Historical copies remain in this directory; 0.3.0 links to the exact public artifact instead of adding another binary copy to the repository. +This ledger records verified release tarballs and their SHA-256 digests. npm is the default install channel after registry verification. Historical copies remain in this directory; newer releases link to the exact public artifact instead of adding another binary copy to the repository. Each final tarball is produced once with `npm pack` from clean protected main, then tested on the installed-artifact matrix. Its checksum is recorded externally after the artifact is built; a ledger update does not rebuild or change the release bytes. +## 0.4.0 + +- File: `opencode-model-control-0.4.0.tgz` +- SHA-256: `1ee2d3ab864f2615ec3f8c750a400694ac8587129bdef78b0d1b275793f0828b` +- Source commit: [`942a94c5eb354da877b22ddc6eb37b098a598f7c`](https://github.com/BitL8-ByteShort/opencode-model-control/commit/942a94c5eb354da877b22ddc6eb37b098a598f7c) +- Source tag: [`v0.4.0`](https://github.com/BitL8-ByteShort/opencode-model-control/releases/tag/v0.4.0) (immutable GitHub release). +- Downloads: [GitHub tarball](https://github.com/BitL8-ByteShort/opencode-model-control/releases/download/v0.4.0/opencode-model-control-0.4.0.tgz), [npm tarball](https://registry.npmjs.org/opencode-model-control/-/opencode-model-control-0.4.0.tgz), or [npm package 0.4.0](https://www.npmjs.com/package/opencode-model-control/v/0.4.0). +- Release evidence: [SHA256SUMS](https://github.com/BitL8-ByteShort/opencode-model-control/releases/download/v0.4.0/SHA256SUMS), [pack evidence](https://github.com/BitL8-ByteShort/opencode-model-control/releases/download/v0.4.0/pack-evidence.json), [final matrix evidence](https://github.com/BitL8-ByteShort/opencode-model-control/releases/download/v0.4.0/acceptance-evidence.zip), and [public npm verification](https://github.com/BitL8-ByteShort/opencode-model-control/releases/download/v0.4.0/npm-public-verification.zip). +- Final acceptance: [CI run 34688784337](https://github.com/BitL8-ByteShort/opencode-model-control/actions/runs/34688784337), with the exact matrix and test boundaries in [support evidence](../docs/support-matrix.md). + +The public npm download matched the final SHA-256 and registry integrity on 2026-09-12. A clean `opencode-model-control@0.4.0` install matched all 83 package files and passed the recorded installed-package checks. This is historical evidence for 0.4.0, not acceptance for a later version. + ## 0.3.0 - File: `opencode-model-control-0.3.0.tgz`