Skip to content

Deliver the provider-quota signal to the orchestrator (blocked on opencode v2 plugin API) #1

Description

@AlexMKX

What is done

src/quota.js (commit f6d1d33) parses provider rate-limit headers into a gated, one-line clause for the orchestrator, with tests whose fixtures are headers captured off the wire. Nothing feeds it — this issue is about the missing delivery seam.

Why it is worth finishing

Both providers report how much of their rate-limit window is burnt on every successful response, and opencode stores none of it. Today the orchestrator learns a provider is saturated only by taking a 429 mid-dispatch, after paying for the work so far. With the signal it would read, before dispatching:

[QUOTA] anthropic: 5h 83% (binding), 7d 6% used. The 5h window resets in 2h 25m.

Captured live 2026-09-01, HTTP 200 both:

POST api.anthropic.com/v1/messages
  anthropic-ratelimit-unified-representative-claim  five_hour
  anthropic-ratelimit-unified-5h-utilization        0.07
  anthropic-ratelimit-unified-5h-reset              1788258600
  anthropic-ratelimit-unified-7d-utilization        0.06

POST chatgpt.com/backend-api/codex/responses
  x-codex-primary-used-percent    51    x-codex-primary-window-minutes    300
  x-codex-secondary-used-percent  15    x-codex-secondary-window-minutes  10080
  x-codex-credits-has-credits     True

Delivery paths, all measured

Path Outcome
Wrap globalThis.fetch Wrapper installed (patched=true, fetch not native, regex verified) and called for zero requests. ai-sdk only falls back to () => globalThis.fetch when options.fetch is unset, and the OAuth plugin already sets it.
provider.<id>.options.fetch from the config hook The documented ai-sdk seam (new AY(H, {provider, url, headers, fetch: W.fetch, ...}) in the bundle), but at config time the OAuth plugin has not yet claimed the slot — wrapping finds it empty, takes it first, delegates to globalThis.fetch, and drops token injection. 240s hang with the call live vs 9.8s with it disabled.
ctx.session.hook("http.response") Absent from opencode 1.18.23 ("http.response": 0 occurrences). Per upstream docs it would skip AI-SDK models anyway.
ctx.aisdk.hook("language") The right seam — wrap the language model, read response.headers off doStream/doGenerate, transport untouched, auth unbreakable. Present in the binary (aisdk ×41, language ×16) but absent from @opencode-ai/plugin 1.17.8's typed surface: it belongs to the v2 Plugin.define({id, setup(ctx)}) runtime, and Plugin.define is 0 occurrences in this build.
Out-of-band dummy request No free endpoint carries the headers — /v1/models and /v1/messages/count_tokens (documented as not billed) both return 200 with no quota headers at all. And hand-rolled inference is client-gated: 429 rate_limit_error, generic "Error", no headers. Not pursued further.

Also: api.type is "aisdk" or "native" at runtime, and is not in the typed Model (api: {id, url, npm}).

Plan

Wait for the v2 plugin API. When Plugin.define + ctx.aisdk.hook("language", ...) are reachable from a released @opencode-ai/plugin, delivery is roughly:

  1. wrap the model returned by the language hook;
  2. read response.headers from the doStream/doGenerate result;
  3. parseQuotaHeaders(headers)_quotaByProvider.set(providerID, snapshot);
  4. append formatQuotaClause(...) to the existing [CACHE STATUS] note in handleTaskToolAfter;
  5. restore the **Provider quota.** paragraph in skills/squad-delegate/SKILL.md (removed in f6d1d33 — it promised the orchestrator a line that cannot arrive).

A draft of the rejected options.fetch wrapper is not in tree; re-derive from this issue rather than reviving it.

Upstream

Six open issues ask for this and none is answered, labelled, or merged. anomalyco/opencode#42846 ("Plugins can't intercept outbound requests for AI-SDK models") is the exact blocker; #40588, #42765, #43324, #43983, #32423 are the same motivation from the user side. Worth watching #42846 rather than filing another.

Do not ship a globalThis.fetch monkey-patch as an interim: it is measured dead, not merely ugly.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions