Skip to content

refactor(coderd/x/chatd): inline the model config provider identity - #31000

Merged
ibetitsmike merged 1 commit into
mainfrom
mike/chatd-inline-provider-identity
Oct 30, 2026
Merged

ibetitsmike merged 1 commit into
mainfrom
mike/chatd-inline-provider-identity

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

This PR deletes modelConfigProviderIdentity and its test table; both callers now use AIProviderID.UUID.String() directly. The helper fell back to the provider type name for model configs without an ai_provider_id. It changes no behavior, because no such config reaches either caller: the chat_model_configs_ai_provider_required_when_active check constraint allows a NULL ai_provider_id only on deleted configs, and both callers reject such a config before they read the identity.

  • Turn preparation resolves the chat's config through gatewayProviderForConfig, which returns "AI Gateway routing requires AI provider metadata" for a config without a provider ID.
  • History sanitization resolves each row's origin config through resolveNormalizedProviderForModelConfig, which returns sql.ErrNoRows. The sanitizer treats that origin as unavailable and strips the row's provider-specific state (fail closed), as before.

Why it is safe

The deleted table tested the helper. Its live contract is that identity is the provider instance UUID, so two providers of the same type are distinct. I mutated the two inlined lines and ran ./coderd/x/chatd/ with go test -overlay. The tests catch all three mutants:

  • Turn target identity set to the provider type: TestActiveServer_AnthropicProviderToolPreRequestGuard, TestOpenAIResponsesFullReplayPairsReasoningAndWebSearch and others fail.
  • Origin identity set to the normalized type: the same tests fail.
  • Both sites set to the type, so they agree: TestOpenAIResponsesReasoningNotReplayedAfterModelConfigChange/ProviderMoved and TestProviderSwitchSanitizesAndRestoresPEToolHistory fail.

git grep modelConfigProviderIdentity finds no references left in the repository, including site/, enterprise/, cli/ and docs/.

Review focus

sanitizeForeignProviderStateRows now discards the normalized provider name that resolveModelConfigAndNormalizedProvider returns. It still calls that function, because the function also checks that the config is enabled and its provider is valid.

Validation

  • go test ./coderd/x/chatd/ -count=1 -p 4 -parallel 4: ok
  • go vet ./coderd/x/chatd/ and golangci-lint run --fix=false ./coderd/x/chatd/...: clean
  • pre-commit hook: passed

Stack

This stack retires chatd compatibility paths for chat data that older Coder versions stored.

  1. test(coderd): stamp chat message fixtures with the current content version #30996 stamp chat message fixtures with the current content version
  2. test(coderd/x/chatd/chatprompt): drop tests of the legacy content codec #30997 drop tests of the legacy content codec
  3. refactor(coderd)!: retire the V0 chat message content decoder #30998 retire the V0 chat message content decoder
  4. refactor(coderd/x/chatd): inline the model config provider identity #31000 inline the model config provider identity (this PR)
  5. refactor(coderd/x/chatd)!: retire the close_agent tool alias #31005 retire the close_agent tool alias

Xum, an AI agent, opened this PR for @ibetitsmike and wrote this description.

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum, an AI agent, posted this comment for @ibetitsmike.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T18:14:06.406679Z 825f14a Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 817c96dcba

ℹ️ 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 removed this pull request from stack #30999 October 18, 2026 19:27
@ibetitsmike
ibetitsmike added this pull request to stack #31006 October 18, 2026 19:28
@ibetitsmike
ibetitsmike marked this pull request as ready for review October 19, 2026 12:16
@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum, an AI agent, posted this comment for @ibetitsmike.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: ce72f1f249

ℹ️ 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

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum, an AI agent, posted this comment for @ibetitsmike.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: 52343877b2

ℹ️ 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

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum, an AI agent, posted this comment for @ibetitsmike.

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 14ee83c794

ℹ️ 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

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum, an AI agent, posted this comment for @ibetitsmike.

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 03d7fde859

ℹ️ 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".

@linear-code

linear-code Bot commented Oct 21, 2026

Copy link
Copy Markdown

CODAGT-1445

@ibetitsmike

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum, an AI agent, posted this comment for @ibetitsmike.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: e26a46a186

ℹ️ 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

Copy link
Copy Markdown
Collaborator Author

@codex review

Xum, an AI agent, posted this comment for @ibetitsmike.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 825f14a182

ℹ️ 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".

@ethanndickson

ethanndickson commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

This is fine to land as is. Heads up though: I'm going to look at improving how the provider switch filter works out which provider produced a row, as part of the OpenAI web search replay work (coder/fantasy#78, coder/fantasy#77, #31355). Right now it re-reads each row's model config every turn and compares exact provider rows, which is probably stricter than it needs to be for web search blocks. That'll likely touch these same lines, and will probably come after this is merged.

Generated by an agent on behalf of the user

ibetitsmike added a commit that referenced this pull request Oct 28, 2026
…rsion (#30996)

Chat prompt tests built most of their stored-message fixtures in the
legacy content format (`content_version` 0), so they exercised the
legacy decoder instead of the current one. This PR changes tests only:
it stamps those fixtures with `ContentVersionV1` and builds them with
`MarshalParts`, so they keep passing after the legacy decoder is removed
later in this stack. Tests of the version 0 decoder itself keep their
version 0 rows until the layers above remove that decoder.

**Why it is safe**

- `mustMarshalContent` and `mustMarshalToolResult` now build current
parts (`PartFromContent`, `ChatMessageToolResult`) and encode them with
`MarshalParts`, the production write path.
- `TestMixedFormatConversation` becomes
`TestConvertMessagesWithFiles_Conversation` and uses current-format rows
only.
- `TestParseContent_BackwardCompat` does not change. Migration 000434
stamped every existing row with version 0, so its six cases of
current-format parts in version 0 rows still cover the version 0
heuristics. #30998 moves those six cases to version 1 rows when it
removes that decoder.
- `TestProviderExecutedResult_LegacyToolRow` calls the legacy writers
(`MarshalContent`, `MarshalToolResult`) directly, so its
provider-executed tool row still goes through the legacy tool-row
decoder.
- `TestParseContent_ErrorPaths` runs its malformed-input cases on
version 1 rows, and runs the same input on version 0 rows of each role
(`v0/<role>/malformed_json`), so a version 0 parser that accepts
malformed content still fails the suite. #30998 deletes the version 0
cases with that decoder.
- I ran the 17 mutants listed below, each a `go test -overlay` edit of
the current decode and prompt-conversion code in `chatprompt.go`,
against the `chatprompt` and `db2sdk` tests on `main` and on this
branch. Every mutant `main` catches is still caught. Two more are caught
here: mutant 3, because
`TestParseContent_ErrorPaths/system/malformed_json` used to hit the
legacy system decoder, and mutant 14, by the new JSON `null` case of
`TestConvertMessagesWithFiles_NormalizesAssistantToolCallInput`. Mutants
4 and 8 survive those tests on both. The `chatd` test
`TestOrganizationSkillsFollowChatOwnerAccess` catches mutant 4, so 8 is
an existing gap.
- Mutants of the version 0 decoder fail on `main` and on this branch:
version 0 assistant or user rows always take the envelope path, their
current-format parts are dropped, legacy tool rows lose
`provider_executed`, or a version 0 parser accepts malformed JSON.
- With the version 0 case removed from `ParseContent` by an overlay,
only tests of version 0 rows fail. #30997 deletes them, except the six
`TestParseContent_BackwardCompat` cases, which #30998 moves, and the
version 0 cases of `TestParseContent_ErrorPaths`, which #30998 deletes.

<details>
<summary>The 17 mutants</summary>

1. `ParseContent` returns no parts for a V1 row.
2. V1 decoding skips NUL restoration (`decodeNulInParts`).
3. The V1 parse error says "decode" instead of "parse".
4. Tool error string extraction is off (`toolResultPartToMessagePart`).
Only a `chatd` test catches it.
5. Tool call input is dropped.
6. Tool call `ProviderExecuted` is dropped when reading.
7. Resolved file data is dropped.
8. The file media type fallback is dropped (`partsToMessageParts`).
Survives.
9. User file IDs are not collected for resolution.
10. Text provider metadata is dropped.
11. File reference text is replaced by the raw content.
12. System messages are dropped.
13. Tool call `ProviderExecuted` is dropped when writing
(`PartFromContent`).
14. `normalizeToolCallInput` keeps a JSON `null` input instead of `{}`.
Survives on `main` only.
15. Text tool result `ProviderExecuted` is dropped.
16. Tool result IDs are dropped.
17. Assistant file IDs are resolved like user file IDs.

</details>

**Review focus**

The rewritten conversation test in `chatprompt_test.go`. It checks the
same prompt shape as before, but every row is in the current format. In
the same file, `TestProviderExecutedResult_LegacyToolRow` keeps its
legacy rows, `TestParseContent_ErrorPaths` checks malformed input on
both content versions, and the input normalization table gains a JSON
`null` case. The other changed test files only switch fixtures to the
current format: `coderd/x/chatd/chatprompt/file_inline_test.go`
(+4/-16), `coderd/database/db2sdk/db2sdk_test.go` (+11/-13),
`coderd/x/chatd/message_conversion_test.go` (+3/-2) and
`coderd/x/chatd/quickgen_internal_test.go` (+1).

**Validation**

- `go test ./coderd/x/chatd/... ./coderd/database/db2sdk/ -count=1 -p 4
-parallel 4`: ok
- `go vet` on the touched packages and `golangci-lint run --fix=false
./coderd/x/chatd/chatprompt/...`: clean
- pre-commit hook: passed

**Stack**

This stack retires chatd compatibility paths for chat data that older
Coder versions stored.

1. #30996 stamp chat message fixtures with the current content version
(this PR)
2. #30997 drop tests of the legacy content codec
3. #30998 retire the V0 chat message content decoder
4. #31000 inline the model config provider identity
5. #31005 retire the `close_agent` tool alias

> Xum, an AI agent, opened this PR for @ibetitsmike and wrote this
description.
@ibetitsmike
ibetitsmike force-pushed the mike/chatd-inline-provider-identity branch from 825f14a to 7b3e5b4 Compare October 29, 2026 22:57
ibetitsmike added a commit that referenced this pull request Oct 30, 2026
…ec (#30997)

This PR deletes the tests of the legacy chat message encodings (plain
strings, fantasy envelopes and legacy tool rows, all stored with
`content_version` 0), and the test-only writers that built those rows
(`MarshalContent` and `MarshalToolResult` in `export_test.go`). It
changes tests only. The next layer removes the legacy decoder that these
tests cover.

**Why it is safe**

Deleted tests that covered only the legacy format:

- The legacy cases of `TestParseContent_BackwardCompat`: legacy system
strings, fantasy envelopes and legacy tool rows. Its six cases of
current-format parts in version 0 rows stay, because they need no legacy
writer. The next layer moves them to version 1 rows.
- `TestInjectFileID_StripsInlineData`: the test-only `MarshalContent`
writer.
- `TestSDKPartsNeverProduceFantasyEnvelopeShape`: the invariant behind
`isFantasyEnvelopeFormat`, the legacy format detector.
- `TestParseContent_ErrorPaths/unknown_role`: the role switch in the
legacy decoder. The V1 decoder has no role switch. The version 0
malformed-input cases of this test stay, because they need no legacy
writer. The next layer deletes them with the decoder.

Deleted tests that also covered current behavior, checked with mutants
of `chatprompt.go`:

- `TestConvertMessagesWithFiles_BackwardCompat` (file ID resolution):
dropping resolved file data, or not collecting user file IDs, still
fails more than 10 remaining tests, including
`TestConvertMessagesWithFiles_ResolvesFileData`.
- `TestProviderMetadataRoundTrip` (provider metadata becomes provider
options): dropping the conversion still fails
`TestConvertMessagesWithFiles_Conversation` and
`TestAssistantWriteRoundTrip`.
- `TestProviderExecutedResult_LegacyToolRow` (provider-executed results
in legacy tool rows are dropped before replay): keeping
provider-executed results in `injectMissingToolUses` still fails
`TestInjectMissingToolUses_DropsProviderExecutedOrphans` and
`TestInjectMissingToolUses_DropsOnlyProviderExecutedMessage`.

The 17 mutants from the layer below give the same result here as on that
layer.

**Review focus**

The `export_test.go` deletion. `MarshalToolResult` uses `toolResultRaw`,
which the next layer deletes, so the writer goes first.

**Validation**

- `go test ./coderd/x/chatd/chatprompt/ -count=1`: ok
- `golangci-lint run --fix=false ./coderd/x/chatd/chatprompt/...`: 0
issues
- pre-commit hook: passed

**Stack**

This stack retires chatd compatibility paths for chat data that older
Coder versions stored.

1. #30996 stamp chat message fixtures with the current content version
2. #30997 drop tests of the legacy content codec (this PR)
3. #30998 retire the V0 chat message content decoder
4. #31000 inline the model config provider identity
5. #31005 retire the `close_agent` tool alias

> Xum, an AI agent, opened this PR for @ibetitsmike and wrote this
description.
@ibetitsmike
ibetitsmike force-pushed the mike/chatd-inline-provider-identity branch from 7b3e5b4 to 6877e0c Compare October 30, 2026 00:19
Base automatically changed from mike/chatprompt-retire-legacy-codec to main October 30, 2026 01:00
ibetitsmike added a commit that referenced this pull request Oct 30, 2026
This PR breaks compatibility with chat messages stored in the legacy
content format (`chat_messages.content_version = 0`). No release stored
such messages, because Coder Agents (#22290) and `content_version`
(#23042) first shipped together in v2.32.0. Only deployments that ran a
preview build of `main` between 2026-02-27 and 2026-03-13 (in practice,
dogfood) have them: migration 000434 (#23042) stamped every row that
existed then with version 0, and nothing converts them later. The PR
deletes the legacy decoder from `chatprompt`: `ContentVersionV0`,
`parseLegacyContent` and its per-role parsers, the fantasy envelope
detector `isFantasyEnvelopeFormat`, `ExtractFileID`, and the legacy
tool-row type `toolResultRaw`. Coder Agents is experimental
(`coderd/x`), so we drop legacy persisted shapes and the helpers that
parse them outright instead of carrying a migration.

What happens to those messages: `ParseContent` now returns `unsupported
content version 0`. Throwaway API tests on this head show the effect on
a chat with version 0 rows:

- `GET /api/v2/chats/{chat}/messages` returns 200, but the version 0
messages have no content.
- A send to a waiting or errored chat, and an edit, first parse the last
assistant message (for an edit, the last one before the edited message)
to cancel its pending tool calls. If it is version 0, they return 500
(`Failed to create chat message.` or `Failed to edit chat message.`,
detail `execute transaction: parse assistant message: unsupported
content version 0`). By code reading, promote does the same.
- Otherwise they return 200, and an edit deletes the edited message and
every later one. If a version 0 row remains before the new message, the
turn fails in the worker (`build chat prompt: unsupported content
version 0`) and the chat goes to error.
- Recovery: editing the first user message deletes it and every later
row, so a new turn runs; another send alone leaves the chat in error.
Chats created through the API on those preview builds cannot recover
this way: each starts with its system prompt stored as a version 0 row,
which no edit deletes, so their users must start a new chat.

Chats with only version 1 rows do not change.

**Why it is safe**

- New content is always written as `CurrentContentVersion` (1), and
`content_version` is `NOT NULL` with no default, so no new row gets
version 0.
- The layer below removed the tests of the legacy encodings. This PR
handles the last version 0 test cases. It moves the six cases of
current-format parts in `TestParseContent_BackwardCompat` to version 1
rows in `TestParseContent_V1`, and their checks do not change. It
deletes the version 0 malformed-input cases of
`TestParseContent_ErrorPaths`; the version 1 cases with the same input
stay. Every mutant that `main` catches, out of the 17 listed in #30996,
is still caught on this head.
- `git grep` for each removed symbol across the repository, including
`site/`, `enterprise/`, `cli/`, `docs/` and `scripts/`, finds nothing on
this head. Before this PR, the only hit outside `chatprompt.go` was
`coderd/exp_chats_test.go`, which now writes the version as a literal
`0`. That test checks that the SQL `jsonb_typeof` guard skips scalar
JSON rows.

**Review focus**

Version 0 rows now take the existing `unsupported content version`
branch of `ParseContent`; the PR adds no new error path.

**Validation**

- `go test ./coderd/x/chatd/... ./coderd/database/db2sdk/ -count=1 -p 4
-parallel 4`: ok
- `go test ./coderd/ ./enterprise/coderd/ -run
'(?i)chat|agent|mcp|skill' -count=1 -p 4 -parallel 4`: ok
- `golangci-lint run --fix=false ./coderd/x/chatd/chatprompt/...`: 0
issues; pre-commit hook: passed
- Throwaway API tests (not committed) of GET, send and edit with version
0 rows: results above

**Stack**

This stack retires chatd compatibility paths for chat data that older
Coder versions stored.

1. #30996 stamp chat message fixtures with the current content version
2. #30997 drop tests of the legacy content codec
3. #30998 retire the V0 chat message content decoder (this PR)
4. #31000 inline the model config provider identity
5. #31005 retire the `close_agent` tool alias

> Xum, an AI agent, opened this PR for @ibetitsmike and wrote this
description.
@ibetitsmike
ibetitsmike force-pushed the mike/chatd-inline-provider-identity branch from 6877e0c to f144ac7 Compare October 30, 2026 01:00
@ibetitsmike
ibetitsmike merged commit 08a1022 into main Oct 30, 2026
27 checks passed
@ibetitsmike
ibetitsmike deleted the mike/chatd-inline-provider-identity branch October 30, 2026 01:30
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 30, 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.

2 participants