Repository navigation
refactor(ai): move endpoint support flags to model capabilities - #61492
Conversation
There was a problem hiding this comment.
The refactor holds up. I checked the snapshot against the base: copied into the pr/base worktree, model-wire.test.ts passes there unchanged, so the 36 sample models send the same requests before and after. The AI package tests (2045 pass) and the listed core tests (112 pass) pass, and typecheck:tsc passes in ai and core.
Public API: OpenCode's config schema, SDK, server and plugin types don't change. The published @opencode/ai package has a breaking type change:
LanguageModelCompatibilitylosessupportsStore,streamUsage,supportsStrictMode,supportsPromptCacheKeyandsupportsEffortUpdates.LanguageModelgainscapabilities(store,streamUsage,promptCacheKey,effortUpdates,tools.strict), and a newModelCapabilitiestype is exported.
Direct callers of @opencode/ai that pass the old keys in .model({ compatibility }) will fail type-checking; in untyped JS, those keys are now silently ignored. As stated in the description, there are no aliases. Worth a changelog note when this ships.
No blocking concerns.
Related: #52617
f6b63d8 to
2178d0a
Compare
There was a problem hiding this comment.
The new commit is the same change rebased onto request-generation. Its only edits switch to that branch's flat maxTokens and temperature fields; the capabilities move is unchanged. I re-checked it against the new base: copied there, the wire snapshot test passes unchanged. packages/ai tests (2046 pass), the listed core tests (112 pass) and typecheck:tsc in ai and core all pass.
The packages/stats and services/www changes in the diff come from a dev commit (#61459) that isn't on request-generation yet. They should drop out once the PR is retargeted.
Public API impact is the same as in my last review: a breaking type change for direct @opencode/ai callers (LanguageModelCompatibility.supports* and streamUsage → LanguageModel.capabilities). OpenCode config, SDK, server and plugin APIs don't change. No new concerns.
2178d0a to
42ca2bc
Compare
There was a problem hiding this comment.
The new push is the same commit (git range-diff reports it as identical), now placed directly on request-generation. The unrelated packages/stats and services/www changes from #61459 are no longer in the diff. The wire snapshot and model-resolver tests still pass. Public API impact is unchanged from my last review. No new concerns.
42ca2bc to
88a666e
Compare
There was a problem hiding this comment.
This push only removes model-wire.test.ts and its snapshot; the source is the same as what I reviewed before. The remaining core tests still pass. Before it was removed, I ran the wire snapshot on both the base and the PR head and the requests were identical. Public API impact is unchanged from my last review. No new concerns.
88a666e to
f4c105a
Compare
There was a problem hiding this comment.
The new push still sends the same requests: I restored the removed 36-model wire snapshot locally on this head and it passes unchanged. packages/ai tests (2046 pass), the listed core tests (111 pass) and typecheck:tsc in ai and core all pass. The core supportsEffortUpdates → reasoning.updates mapping keeps any other reasoning values on the base model.
Public API (@opencode/ai, breaking for direct callers): LanguageModelCompatibility loses supportsStore, streamUsage, supportsStrictMode, supportsPromptCacheKey and supportsEffortUpdates. LanguageModel gains capabilities: Capabilities: supportsEffortUpdates becomes capabilities.reasoning.updates (the description's table still says effortUpdates), and there is a new systemUpdates override. OpenCode config, SDK, server and plugin APIs don't change.
One concern, inline: most of the newly declared Capabilities fields aren't read anywhere, so setting them has no effect.
| * What the model accepts on its API: models.dev v2 `capabilities` in camelCase, with the API's own overrides merged in. | ||
| * Unset fields keep the protocol default, which may still guess from the provider, base URL or model ID. | ||
| */ | ||
| export interface Capabilities { |
There was a problem hiding this comment.
Only store, streamUsage, promptCacheKey, tools.strict, reasoning.updates and systemUpdates are read anywhere. Nothing reads tools.supported/choice/search/freeform/updates, reasoning.supported/default/toggle/effort/budget, cache, structuredOutput, temperature, topP, topK or serviceTier, and core never fills them. On a published type, a caller setting e.g. temperature: false or cache: false would get no error and no effect. Could this PR declare only the fields that are read, and add the rest when a later PR in the series reads them? If they need to stay, mark them as informational, not enforced.
There was a problem hiding this comment.
Intentional: the type mirrors models.dev v2 CapabilitiesV2 so a catalog entry maps onto the model 1:1. Only the fields that replace an existing flag or guess are read in this PR, so requests stay byte-identical; the rest are carried for callers and listed as not read yet in the description.
There was a problem hiding this comment.
That works. Mirroring CapabilitiesV2 1:1 is a good reason to keep the whole shape. Only the six fields that replace a flag or a guess are read, so the request stays byte-identical, and later PRs in the stack can start reading the other fields without changing the type.
One small gap: the "not read yet" note is only in the PR description. Someone using the published type won't see it, and setting temperature: false or cache: false will silently do nothing. A one-line addition to the interface doc in packages/ai/src/schema/options.ts (~L217) would close that:
/**
* What the model accepts on its API: models.dev v2 `capabilities` in camelCase, with the API's own overrides merged in.
* Unset fields keep the protocol default, which may still guess from the provider, base URL or model ID.
* Protocols currently read only `store`, `streamUsage`, `promptCacheKey`, `systemUpdates`, `tools.strict`
* and `reasoning.updates`; the other fields are informational and not enforced yet.
*/That's optional, and fine to leave out if the next PRs will read these fields soon. I made no changes in the checkout.
f4c105a to
a9e4b73
Compare
There was a problem hiding this comment.
This push only rebases the same commit onto dev; the patch is identical. Tests in ai and core and typecheck:tsc still pass, and the 36-model wire snapshot still matches when I restored it locally. The open point about declared-but-unread Capabilities fields still applies to the code. The description now explains it, but the type itself doesn't say those fields aren't enforced. Public API impact is unchanged from my last review. Nothing new.
a9e4b73 to
2b3d0b5
Compare
First PR in the
@opencode/aimodel redesign: the packageModelmirrors models.dev v2ModelV2with one API resolved and camelCase names (id,provider,type,api,baseURL,modalities,capabilities,limit,compatibility,cost, plus package-onlyauthandoptions). Targetsdev(#60971, flat generation knobs, is merged). This step addscapabilitiesin the v2 shape and moves the "does the endpoint accept X" flags offcompatibilityinto it.Problem
LanguageModel.compatibilitymixed two kinds of things:max_tokensvsmax_completion_tokens, cache marker syntaxstore,stream_options.include_usage,prompt_cache_key,stricton tools, per-message effort updates, mid-conversation system messagesmodels.dev v2 publishes the second kind as
capabilities, under other names. The package had no capabilities at all and guessed some of them from the model ID.Change
capabilitiesis models.dev v2CapabilitiesV2in camelCase, all fields optional (unset keeps today's protocol default):compatibility)capabilities)supportsStorestorestreamUsagestreamUsagesupportsStrictModetools.strictsupportsPromptCacheKeypromptCacheKeysupportsEffortUpdatesreasoning.updatessystemUpdatesOpenAIChat.route.with({ endpoint: { baseURL } }).model({ id: "kimi-k2.6", - compatibility: { maxTokensField: "max_tokens", supportsStrictMode: false, supportsPromptCacheKey: true }, + compatibility: { maxTokensField: "max_tokens" }, + capabilities: { tools: { strict: false }, promptCacheKey: true }, })Protocols read these fields first and keep today's guessing only as the fallback when a field is unset: provider/URL detection for
storeandtools.strict(OpenAI Chat), model-ID detection forreasoning.updates(OpenAI Responses, Anthropic Messages, Bedrock Mantle) and forsystemUpdates(Anthropic Messages). The 22 provider call sites and the three shared constants (AlibabaChat,ZAIChat,VeniceChat) are updated. The other fields (cache,tools.updates,serviceTier, ...) are carried for callers and not read yet. No aliases for the old names.Core
OpenCode config keeps its
compatibilitykeys (supportsPromptCacheKey,streamUsage,supportsEffortUpdates); they are a public config contract.ModelResolvermaps them onto the runtime model'scapabilities(promptCacheKey,streamUsage,reasoning.updates) with the same precedence as before (config over package default). That is the only core source change, inmodel-resolver.ts; it renames keys, it doesn't add behavior. Core does not pass catalog capabilities it dropped before. No plugin, SDK, server or config types change.aisdk.tsis untouched: AI SDK models never read these flags.Checked locally: 36 sample models byte-identical (method, URL, headers, body vs a snapshot taken before the redesign, including each config
compatibilitykey); full catalog 8544 models, 0 requests differ (7732 send a request; the rest are AI SDK packages or need real cloud config). Both checks resolve models throughModelResolverand capture the exact request the AI package sends. They stay out of the repo.Out of scope
limit,modalities, unifiedcompatibilityandcost(later PRs in this stack).Size
src +148 / -51 (the v2
Capabilitiestype, protocol reads, the core key mapping), tests +22 / -23.Verification
bun typecheckandbun run typecheck:tscinpackages/ai,core,server,sdk,plugin: passbun testinpackages/ai: 2046 pass, 0 fail (all cassettes replay unchanged)packages/core:model-resolver,config/provider,session-runner-recorded,plugin/: 644 passbun run check: passAdded by the opencode bot (AI-generated).
Related: #52617
Public API: @opencode/ai: five LanguageModelCompatibility support flags move to the new LanguageModel.capabilities (supportsEffortUpdates → capabilities.reasoning.updates; adds systemUpdates); breaking for direct @opencode/ai callers, OpenCode config/SDK/server/plugin APIs unchanged