Repository navigation
feat(ai): add image and decision entrypoints for catalog providers - #59380
Conversation
There was a problem hiding this comment.
I found no problems: the new images/decisions entrypoints build working models from the same settings as each provider's chat model(), and the settings refactors leave the existing chat paths unchanged.
Core doesn't load these entrypoints yet, so I checked them at the package level. In packages/ai, media-entrypoints.test.ts passes (19/19), the full suite passes (1927 pass, 0 fail), and tsc --noEmit is clean with both the default config and tsconfig.types.json.
Public API: additions only to @opencode/ai, all backwards compatible:
- 19 new subpath exports,
providers/<P>/imagesandproviders/<P>/decisions, for the providers in the description's table - new
imageModel/decisionModelexports on those facades - new
imagefactories on openrouter, digitalocean, azure, google-vertex and openai-compatible, anddecisionon openai-compatible
Nothing was removed or changed.
There was a problem hiding this comment.
The new commit adds the 19 new entrypoints to core's bundled package list and changes nothing else. I found no new concerns, and my earlier review raised none.
Every new @opencode/ai entrypoint is in the list. In packages/core, bun test test/provider.test.ts passes (4/4) and tsc -b tsconfig.json tsconfig.tests.json is clean. Registering the entrypoints alone doesn't change core's behaviour, since nothing in the catalog points at them yet.
Public API: this commit doesn't change it. The pull request as a whole only adds things to @opencode/ai: the providers/<P>/images and providers/<P>/decisions subpath exports and the imageModel/decisionModel facade exports. All of it is backwards compatible.
There was a problem hiding this comment.
The new commit is fine and I found no new concerns; my earlier reviews raised none.
It changes one test in packages/core/test/variant.test.ts: the check that every bundled package has reasoning variants now skips packages ending in /images or /decisions. That check fails as of the previous commit, which added those entrypoints to core's bundled list, and media models have no reasoning settings. variant.test.ts and provider.test.ts in packages/core now pass (21/21).
Public API: this commit doesn't change it. The pull request as a whole only adds things to @opencode/ai: the providers/<P>/images and providers/<P>/decisions subpath exports and the imageModel/decisionModel facade exports. All of it is backwards compatible.
There was a problem hiding this comment.
The new commit looks right and I found no new concerns; my earlier reviews raised none.
I checked how the new default providerOptions are merged when a request is sent. Decisions go through the same media send path, so they get the merge too. The new tests show a thinkingLevel setting reaches the Google image body and a request's own providerOptions override it. Moving System One decisions to typesafe-ai/decisions and opencode/decisions keeps each provider's own metadata key and defaults.
Results: full bun test in packages/ai passes (1930 pass, 0 fail). tsc is clean in packages/ai and packages/core. The five core test files you listed pass (93).
Public API, all in @opencode/ai:
- removes
providers/openai-compatible/decisionsand openai-compatible'sdecision/decisionModel, which were only added earlier in this pull request and never released - adds
providers/typesafe-ai/decisionsandproviders/opencode/decisions, withdecisionModelandSettingsexports on both facades - adds an optional
providerOptionsfield on image, video, speech, transcription and decision models; the providerimage(id)anddecision(id)factories take it as an optional second argument
All of it is backwards compatible.
There was a problem hiding this comment.
This commit can send chat-only reasoning fields to image APIs that don't accept them. It maps each new image and decision entrypoint to its provider's chat reasoning spelling, and leftover settings are copied straight into the image request.
I confirmed it for openai-compatible/images: with the chat spelling { reasoningEffort: "high" }, the sent OpenAI Images body includes a raw "reasoningEffort":"high" field. The bundled catalog lists reasoning effort for 8 edenai Gemini image models on that API. The same risk applies to openrouter/images (reasoning: { effort }) and to openai/images, azure/images and xai/images (the Responses fields reasoningEffort, reasoningSummary and include). google/images is fine: the Gemini spelling produces a valid generationConfig.thinkingConfig.
The commit also removes the tests that checked settings reaching the image and decision request bodies. That merge still exists in the code, so it would be good to keep a small test for it.
Results: the full packages/ai suite passes (1928 pass, 0 fail), the five core test files you listed pass (92), and tsc is clean in packages/core. This commit doesn't change the public API.
| "@opencode/ai/providers/xai/images": openResponses, | ||
|
|
||
| "@opencode/ai/providers/openai-compatible": openaiCompatible, | ||
| "@opencode/ai/providers/openai-compatible/images": openaiCompatible, |
There was a problem hiding this comment.
This entrypoint now uses the chat spelling { reasoningEffort }, but OpenAIImages sends leftover options to the Images API unchanged. With reasoningEffort: "high", the body is {"model":"google/gemini-3.1-flash-image","prompt":"x","reasoningEffort":"high"}, and the catalog lists effort for 8 edenai Gemini image models on this API. openrouter/images, openai/images, azure/images and xai/images have the same problem with their chat or Responses fields. Could image entrypoints without a matching image-API spelling stay unmapped, as in the previous commit, or get a spelling written for their image API?
There was a problem hiding this comment.
This fixes the earlier variant concern. Only google/images and google-vertex/images now get reasoning variants, using Gemini's spelling. The other image and decision entrypoints are unmapped, and Variant.resolve gives no variants for unmapped packages, so chat reasoning fields no longer reach the Images bodies. The core test files you listed pass (92) and tsc is clean in packages/core. This commit doesn't change the public API.
One point from last round is still open but not blocking: the tests that checked settings reaching the image and decision request bodies are still gone, so nothing covers that merge.
Problem
Image and decision models in the models.dev catalog point their
opencodeaipackage at protocol packages (protocols/openai-images,protocols/system-one, ...) or at a bare provider root. Protocols don't know the host or auth, and the provider roots only expose image/decision factories on an unconfigured default, so there's nomodel(modelID, settings)core can call for them.Change
I added provider-plus-API entrypoints, the same shape as the chat ones:
Each one is a two-line file that re-exports a configured
imageModel/decisionModelfrom the provider facade. Those take the sameSettingsas the provider's chatmodel()and reuse its config, so auth, base URL, headers and body match chat. Leftover settings become the model's default provider options, like chat, so a variant setting such as{ thinkingLevel: "HIGH" }ends up in the request:To make that work, media and decision models now carry optional default
providerOptions, merged under each request's own:New code was only needed where a facade had no factory yet: images on
openai-compatible,openrouter,digitalocean,azureandgoogle-vertex, and a settings-baseddecisionModelontypesafe-aiandopencode.<P>is the package the same models.dev provider uses for its chat models (openai-compatiblewhen it has none). The exception: if the model already pointed at a provider package that knows its wire format, I kept it. Decision APIs come in three formats (OpenAI decisions, System One, Cloudflare run), so the System One hosts (amd, greenpt, melious, nano-gpt, ollama-cloud, vivgrid) go totypesafe-ai/decisionsand opencode Zen toopencode/decisions.models.dev provider → old → new
opencodeaiCore changes, kept small:
provider.test.tsrequires every@opencode/aientrypoint that exportsmodel()to be bundledgoogle/imagesandgoogle-vertex/imagesget Gemini's reasoning variants: effort becomesthinkingConfig.thinkingLevel, which the Gemini image API reads fromgenerationConfig. The other image and decision APIs have no reasoning setting, so they get no variants, and the "every bundled package has a spelling" test skips/imagesand/decisionsentrypointsOut of scope
BUILDS_OWN_URLlist in that change.chat-completions/responsesAPI keys) are unchanged.Verification
bun test test/provider/media-entrypoints.test.tsinpackages/ai: one case per entrypoint (20) checking the sent URL, auth header, model id, custom header and bodybun testinpackages/ai: 1928 pass, 0 fail.bun typecheck: cleanbun run script/test.ts test/variant.test.ts test/provider.test.ts test/aisdk-native.test.ts test/model-resolver.test.ts test/models-dev.test.tsinpackages/core: 92 passbun run checkat the root: cleanAdded by the opencode bot (AI-generated).
Public API: adds
providers/<P>/imagesandproviders/<P>/decisionsentrypoints,imageModel/decisionModelfacade exports and optional model-levelproviderOptionson media and decision models in @opencode/ai (backwards compatible)