feat: add per-model OpenAI Responses API toggle by ibetitsmike · Pull Request #27683 · coder/coder · GitHub
Skip to content

feat: add per-model OpenAI Responses API toggle - #27683

Merged
ibetitsmike merged 2 commits into
mainfrom
mike/codagt-874-responses-api-toggle
Aug 4, 2026
Merged

feat: add per-model OpenAI Responses API toggle#27683
ibetitsmike merged 2 commits into
mainfrom
mike/codagt-874-responses-api-toggle

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

chatd hardcoded WithUseResponsesAPI(), so the provider SDK's static known-model list decided whether an OpenAI model spoke the Responses API or Chat Completions. A model absent from that list silently fell back to Chat Completions until the fantasy fork was patched.

This exposes the SDK's WithResponsesAPIFunc hook as a per-model setting, openai_config.use_responses_api, stored in the existing chat_model_configs.options JSONB. Unset keeps the known-model list, true forces Responses, false forces Chat Completions. There is no migration.

It sits in a new construction-time openai_config section rather than in provider_options.openai because it selects the API when the client is built, while provider_options holds per-request parameters. That placement is also load-bearing: a config setting only this field would otherwise materialize an OpenAI request-options struct and turn on provider-side response storage, since Store defaults to true there.

Three places independently decided the transport and would silently disagree with the client actually built:

Site Effect when it disagrees
ModelFromConfig the transport being overridden
AcceptsFilePartMediaType text attachments dropped, since Responses natively accepts only images and PDFs
UsesResponsesOptions the SDK type-asserts the concrete options struct, so every OpenAI option is discarded

They share one predicate here, chatopenai.UsesResponsesAPI, with the override threaded to each. The rest of the stack removes that threading by resolving the transport once and carrying it. Compaction overrides and the quickgen debug model built clients without ConfigOptions, so they now pass it and pick up both this setting and the existing Anthropic beta headers.

The toggle also makes transport-conditional option handling admin-switchable, so two hardening changes ride along. ServiceTierFromChat now maps every tier the codersdk enum advertises (auto, default, flex, scale, priority); it previously returned nil for default and scale, so flipping a model to Responses silently dropped a configured service_tier that the API accepts (fantasy forwards the value unchanged). And a new TestProviderOptionsTransportParity pins, per provider_options.openai field, which transport honors it, against a table in ARCHITECTURE.md, so a field honored on one transport and silently ignored on the other fails the test unless recorded as intentional.

Review rounds also caught two lifecycle gaps around the new field. isZeroChatModelCallConfig now inspects OpenAIConfig, so a stored options blob whose only setting is this toggle survives into GET/list responses instead of reading as model_config: null; TestIsZeroChatModelCallConfigCoversEveryField sets each config field in isolation and fails if any field is invisible to the zero check. And the model editor's update path sends an explicit empty model_config when an edit clears the last field, since an omitted property preserves the stored options server-side; covered by the EditClearingLastOptionSendsEmptyConfig story.

Azure keeps following the known-model list, because the Azure provider exposes no equivalent hook. The model editor renders Azure with the OpenAI option schema, so instead of shipping a visible but inert control, the option schema generator gains a providers struct tag that it emits as visible_for_providers. Gating uses the raw provider type rather than the alias table, so the control appears only for openai-typed providers. No hand-written frontend field: the editor renders it from the generated schema.

Closes https://linear.app/codercom/issue/CODAGT-874/add-completionsresponses-api-toggle-in-model-editor

Mux prepared this PR on Mike's behalf.

@linear-code

linear-code Bot commented Jul 30, 2026

Copy link
Copy Markdown

CODAGT-874

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0421c8bc00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/x/chatd/chatopenai/options.go Outdated
Comment thread site/src/api/chatModelOptionsGenerated.json Outdated
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 3e71f10a22

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-874-responses-api-toggle branch from 3e71f10 to b03a721 Compare July 30, 2026 17:25
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: b03a7214d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ibetitsmike
ibetitsmike marked this pull request as ready for review August 2, 2026 20:33
@coderagents

coderagents Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • docs/ai-coder/agents/models.md - Add Use Responses API to the OpenAI provider options table under Provider-specific options. It is a new admin-visible field in the model editor: unset follows the provider SDK's known-model list, true forces the Responses API, false forces Chat Completions.

    ⚠️ Still not addressed as of 323675fc. No changes to docs/ in this PR.

  • docs/ai-coder/agents/models.md - The note at the end of that section states that "Azure OpenAI uses the same options as OpenAI". That is no longer accurate: the field is scoped to raw openai-typed providers via visible_for_providers, and Azure keeps following the known-model list. The note needs a carve-out for this option.

    ⚠️ Still not addressed as of 323675fc.

  • docs/ai-coder/agents/models.md - The transport parity table in coderd/x/chatd/ARCHITECTURE.md records that max_completion_tokens is honored only on Chat Completions, with max_output_tokens bounding output on Responses. The public OpenAI options table lists Max Completion Tokens without that caveat, so once Use Responses API is admin-settable the docs should say the option applies only when the model speaks Chat Completions.
  • coderd/x/chatd/ARCHITECTURE.md - The "OpenAI transport selection" section lists the paths that must thread the override, but computer-use turns are now an exception and are not mentioned: prepareGeneration clears callConfig.OpenAIConfig for the substituted computer-use model because that client is built without ConfigOptions. As of 323675fc the substitution also moved earlier in prepareGeneration so file-part classification, history sanitization, and provider option preparation all see the substituted model. That ordering constraint is exactly what the section's "decisions must agree with the client that was built" rule depends on, so it belongs there. The final-turn status label path also now threads dbConfig.Options via StatusLabelOptions.

    ⚠️ ARCHITECTURE.md is unchanged from the previous revision, so this is still open.

Not flagged: codersdk chat types are not part of the generated docs/reference/api/ schemas, and the remaining changes are internal plumbing and tests.


Automated review via Coder Agents

@DanielleMaywood DanielleMaywood left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of comments are pure waffle 🧇 and can be either deleted or massively simplified.

Also a quick question: Is this definitely the simplest way to approach this? Not blocking or anything, just a net 621 lines added feels excessive. Understand that most of that appears to be tests (and comments 😜) though

Comment thread coderd/x/chatd/chatopenai/options.go Outdated
Comment on lines +120 to +123
// UsesResponsesAPI reports whether a model uses the OpenAI Responses API.
// Callers must pass the same override the client was built with, otherwise
// provider options and file parts are prepared for the wrong API. Azure
// follows the SDK's model list because its provider exposes no override.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧇 waffle

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed to two lines in the current push.

Mux replied on Mike's behalf.

Comment on lines +124 to +136
func UsesResponsesAPI(provider, modelID string, override *bool) bool {
switch provider {
case fantasyopenai.Name:
if override != nil {
return *override
}
return fantasyopenai.IsResponsesModel(modelID)
case fantasyazure.Name:
return fantasyopenai.IsResponsesModel(modelID)
default:
return false
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgive me if there is a justification, but why have we created a new function UsesResponsesAPI despite it not appearing to add any value over UsesResponsesOptions?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They take different inputs at different stages: UsesResponsesAPI works from (provider, modelID, override) strings and is called during client construction in ModelFromConfig, before any fantasy.LanguageModel exists. UsesResponsesOptions is the wrapper for request preparation, where callers hold a model. Both are transitional: #27704 in this stack deletes the pair entirely in favor of a transport resolved once at construction and carried on the model.

Mux replied on Mike's behalf.

Comment on lines +900 to +908
// OpenAIResponsesAPIOverride returns the configured OpenAI Responses API
// override, or nil when the model config leaves the choice to the provider
// SDK's known-model list.
func OpenAIResponsesAPIOverride(config *codersdk.ChatModelOpenAIConfig) *bool {
if config == nil {
return nil
}
return config.UseResponsesAPI
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this helper need to exist? Feels a bit unneeded

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a nil-safe accessor repeated at six call sites (quickgen, compaction override, generation preparer x2, advisor, aibridge routing); inlining it means repeating the nil check at each. It is also transitional: #27704 removes it together with the recomputation sites it serves.

Mux replied on Mike's behalf.

Comment on lines +91 to +92
// ApplyReasoningEffort uses the override when it must create OpenAI provider
// options, keeping their type aligned with the selected API.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧇

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted in the current push, restoring the undocumented state this function has on main.

Mux replied on Mike's behalf.

Comment thread codersdk/chats.go Outdated

// ChatModelOpenAIConfig configures how the OpenAI client is constructed.
// Unlike ChatModelOpenAIProviderOptions, these settings are not request
// parameters: they are applied once when the client is built.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧇

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortened to two lines in the current push.

Mux replied on Mike's behalf.

Comment thread scripts/modeloptionsgen/main.go Outdated
Comment on lines +29 to +30
// VisibleForProviders restricts a general field to the listed raw
// provider types. Empty means the field applies to every provider.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧇

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted in the current push; the field name and json tag carry it.

Mux replied on Mike's behalf.

Comment thread site/src/api/chatModelOptions.ts Outdated
Comment on lines +28 to +31
/**
* If present, the field only applies to these raw provider types.
* Absent or empty means it applies to every provider.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧇

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortened to one line in the current push.

Mux replied on Mike's behalf.

Comment thread site/src/api/chatModelOptions.ts Outdated
Comment on lines +187 to +191
/**
* Matches against the raw provider type rather than {@link resolveProvider},
* because aliases such as "azure" inherit another provider's request options
* but not its client construction settings.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧇

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortened to two lines in the current push.

Mux replied on Mike's behalf.

Comment thread site/src/api/chatModelOptions.ts Outdated
return scope.includes(provider.trim().toLowerCase());
}

/** Get only the visible (non-hidden) general fields for a provider. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧇

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted in the current push.

Mux replied on Mike's behalf.

@ibetitsmike
ibetitsmike force-pushed the mike/codagt-874-responses-api-toggle branch from b03a721 to d1aec0a Compare August 3, 2026 14:12
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1aec0a96f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codersdk/chats.go
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-874-responses-api-toggle branch from d1aec0a to 507c0b8 Compare August 3, 2026 14:35
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 507c0b80e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codersdk/chats.go
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-874-responses-api-toggle branch from 507c0b8 to 0a0ed17 Compare August 3, 2026 14:52
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a0ed17451

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/x/chatd/generation_preparer.go
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-874-responses-api-toggle branch from 0a0ed17 to 6aa50d8 Compare August 3, 2026 15:10
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6aa50d8025

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/x/chatd/generation_preparer.go
OpenAI models speak either the Responses API or Chat Completions, and the
provider SDK picks per model from a static known-model list. A model absent
from that list falls back to Chat Completions with no way to opt in.

Add `openai_config.use_responses_api` to the model call config: unset keeps
the known-model list, true forces Responses, false forces Chat Completions.
It lives in a construction-time section rather than in
`provider_options.openai` because it selects the API instead of being a
request parameter.

The transport decision must agree with the client that was built, so the
override is threaded to provider option conversion, reasoning effort
injection, and file part acceptance, and to every path that builds its own
OpenAI client. `chatopenai.UsesResponsesAPI` is the single predicate.

The option schema generator gains a `providers` struct tag, emitted as
`visible_for_providers`, so the model editor renders the control only for
openai-typed providers. Gating on the raw provider type keeps it out of
editors for aliased types such as azure, which cannot honor it.
@ibetitsmike
ibetitsmike force-pushed the mike/codagt-874-responses-api-toggle branch from 6aa50d8 to 323675f Compare August 3, 2026 15:25
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

@ibetitsmike
ibetitsmike merged commit c6cee10 into main Aug 4, 2026
29 checks passed
@ibetitsmike
ibetitsmike deleted the mike/codagt-874-responses-api-toggle branch August 4, 2026 07:30
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants