Skip to content

feat(llm): provider packages own model request construction#35212

Open
kitlangton wants to merge 5 commits into
devfrom
provider-packages
Open

feat(llm): provider packages own model request construction#35212
kitlangton wants to merge 5 commits into
devfrom
provider-packages

Conversation

@kitlangton

Copy link
Copy Markdown
Contributor

What

Makes SessionRunnerModel provider-agnostic. Provider packages under @opencode-ai/llm/providers/* implement a uniform contract:

export const model = ProviderPackage.define<Settings>((modelID, settings) => Model)

The runner resolves a package specifier from the catalog api, folds api.settings, credentials, and transport overlays (headers/body/limits) into one Settings object, and calls module.model(id, settings). All provider knowledge (routes, auth shapes, backend quirks) lives in the packages.

ChatGPT / Codex (#34765)

The temporary runner seam is gone. The codex backend is now just a per-API entry point of the openai provider, @opencode-ai/llm/providers/openai/codex, selected by the OpenAI plugin's existing catalog transform when a ChatGPT connection is active (same transform that already handles eligibility and zero cost). The plugin writes credential.metadata.accountID; the runner folds OAuth metadata into settings; the codex package sets the backend URL and chatgpt-account-id header. The runner has no provider conditionals left.

Changes

  • llm: ProviderPackage contract (Settings, Definition, define); flat model(id, config) constructors on the openai-responses / anthropic-messages / openai-compatible-chat protocols; contract-shaped model exports on openai, anthropic, openai-compatible providers; new providers/openai/codex; explicit exports-map entries.
  • schema: Provider.Native gains optional package (client + sdk types regenerated).
  • core: runner loads packages via a static built-in map (dynamic import() with shape validation for foreign specifiers); one settings fold with explicit precedence (credential > config; key metadata → body as before, OAuth metadata → settings, never the body); legacy aisdk catalog entries dispatch through the same map, preserving behavior.
  • llm schema fix: ToolResultValue was self-referential through Object.assign and only typechecked under lucky check ordering; hoisted the union so core's typecheck passes with the new import graph.

Notes

Direction follows the native provider package work in #33689/#34462, rebuilt on current dev so the ChatGPT routing lands provider-owned rather than as a runner seam. The generic AI SDK adapter for arbitrary aisdk packages is deliberately out of scope (follow-up).

Verification

  • bun typecheck: llm, core, schema, client ✓
  • bun run test: llm 329 pass; core session-runner + plugin + model suites 362 pass ✓
  • New tests: provider-package request construction (bearer/x-api-key auth, apiKey kept out of wire body, codex URL + chatgpt-account-id header with/without accountID); plugin assigns codex package + zero cost to eligible models, disables ineligible ✓
  • Live ChatGPT OAuth check still recommended before relying on it

Provider packages in @opencode-ai/llm/providers/* now implement a uniform
ProviderPackage contract: model(modelID, settings) => Model. SessionRunnerModel
becomes provider-agnostic: it resolves a package specifier, folds catalog
settings, credentials, and transport overlays into one Settings object, and
delegates request construction to the package.

- llm: add ProviderPackage (Settings, Definition, define), flat model(id,
  config) constructors on the openai-responses, anthropic-messages, and
  openai-compatible-chat protocols, contract-shaped model exports on the
  openai, anthropic, and openai-compatible providers, and a new
  providers/openai/codex entry point that targets the ChatGPT codex backend
  and sets the chatgpt-account-id header from settings.accountID.
- schema: Provider.Native gains optional package.
- core: SessionRunnerModel loads packages through a static built-in map
  (dynamic import for foreign specifiers) and applies one settings fold;
  the ChatGPT conditional is deleted from the runner. The OpenAI plugin's
  catalog transform now assigns the codex package to eligible models when a
  ChatGPT connection is active, alongside the existing eligibility and cost
  rewrites.
- llm schema: hoist ToolResultValue union out of its Object.assign self
  reference; the previous shape only typechecked under lucky check ordering
  and broke under core's typecheck with the new import graph.

Closes #34765
…outes

Provider packages are now the only module-level model constructors; the
protocol-level model wrappers added earlier on this branch had one call
site each and duplicated the Settings vocabulary, so packages call
route.with(...).model({id}) directly. Deletes provider facades with no
importers outside llm tests; configure stays for V1 native-request.
The catalog treated any native model api with empty settings and no url as
a placeholder inheriting the provider api, which stomped plugin-assigned
native packages (ChatGPT codex retargeting) back to aisdk. A native api
carrying a package is explicitly targeted and now survives projection.
Replace the codex-substring heuristic with the empirically verified
allow/disallow lists plus the >5.4 version rule; the ChatGPT backend
rejects models like gpt-5.3-codex that the substring rule admitted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant