mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor(coderd/x/chatd): extract OpenAI logic into chatopenai package (#24788)
Extracts OpenAI-specific logic from `coderd/x/chatd` into `coderd/x/chatd/chatopenai` so the main chat path no longer references `fantasyopenai` directly for chain mode info, response IDs, web search tooling, or option mapping. Structural refactor. The only deliberate behavioral narrowing is consolidating Responses store checks and related keyed option or metadata access on `opts[fantasyopenai.Name]`. That is documented by `TestIsResponsesStoreEnabledIgnoresMalformedNonOpenAIKey` and is unreachable in production where Responses options always live under `fantasyopenai.Name`. Summary: - Moves OpenAI Responses chain mode info, response ID helpers, web search tool construction, and provider option conversion into `chatopenai`. - Keeps Anthropic, Google, OpenRouter, and Vercel provider branches as thin, existing code paths. - `chatopenai` only imports `chatprompt` from chatd subpackages. It does not import `chatd`, `chatloop`, `chatprovider`, or `chaterror`. - Follow-up review fixes align helper names, keyed provider option access, map cloning behavior, and PR documentation with the extracted package boundary. - Final sweep trims unused chain-mode state, removes a duplicate store-check test case, drops an unused provider-tool parameter, and shares the chat-message test helper through `chattest`. > Mux is updating this PR on Mike's behalf.
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"charm.land/fantasy"
|
||||
fantasyanthropic "charm.land/fantasy/providers/anthropic"
|
||||
fantasyopenai "charm.land/fantasy/providers/openai"
|
||||
"charm.land/fantasy/schema"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -24,6 +23,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatdebug"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chaterror"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatopenai"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatprompt"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatretry"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatsanitize"
|
||||
@@ -512,7 +512,7 @@ func Run(ctx context.Context, opts RunOptions) error {
|
||||
Content: result.content,
|
||||
Usage: result.usage,
|
||||
ContextLimit: contextLimit,
|
||||
ProviderResponseID: extractOpenAIResponseIDIfStored(opts.ProviderOptions, result.providerMetadata),
|
||||
ProviderResponseID: chatopenai.ExtractResponseIDIfStored(opts.ProviderOptions, result.providerMetadata),
|
||||
Runtime: time.Since(stepStart),
|
||||
ToolCallCreatedAt: result.toolCallCreatedAt,
|
||||
ToolResultCreatedAt: result.toolResultCreatedAt,
|
||||
@@ -538,8 +538,8 @@ func Run(ctx context.Context, opts RunOptions) error {
|
||||
// when previous_response_id is set, so we must leave chain
|
||||
// mode and reload the full history before the next call.
|
||||
stepMessages := result.toResponseMessages()
|
||||
if hasPreviousResponseID(opts.ProviderOptions) {
|
||||
clearPreviousResponseID(opts.ProviderOptions)
|
||||
if chatopenai.HasPreviousResponseID(opts.ProviderOptions) {
|
||||
opts.ProviderOptions = chatopenai.ClearPreviousResponseID(opts.ProviderOptions)
|
||||
if opts.DisableChainMode != nil {
|
||||
opts.DisableChainMode()
|
||||
}
|
||||
@@ -1233,7 +1233,7 @@ func persistPendingDynamicStep(
|
||||
Content: result.content,
|
||||
Usage: result.usage,
|
||||
ContextLimit: contextLimit,
|
||||
ProviderResponseID: extractOpenAIResponseIDIfStored(opts.ProviderOptions, result.providerMetadata),
|
||||
ProviderResponseID: chatopenai.ExtractResponseIDIfStored(opts.ProviderOptions, result.providerMetadata),
|
||||
Runtime: time.Since(stepStart),
|
||||
PendingDynamicToolCalls: pending,
|
||||
}); err != nil {
|
||||
@@ -1709,85 +1709,6 @@ func addAnthropicPromptCaching(messages []fantasy.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
// hasPreviousResponseID checks whether the provider options contain
|
||||
// an OpenAI Responses entry with a non-empty PreviousResponseID.
|
||||
func hasPreviousResponseID(providerOptions fantasy.ProviderOptions) bool {
|
||||
if providerOptions == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, entry := range providerOptions {
|
||||
if options, ok := entry.(*fantasyopenai.ResponsesProviderOptions); ok {
|
||||
return options.PreviousResponseID != nil &&
|
||||
*options.PreviousResponseID != ""
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// clearPreviousResponseID removes PreviousResponseID from the OpenAI
|
||||
// Responses provider options entry, if present.
|
||||
func clearPreviousResponseID(providerOptions fantasy.ProviderOptions) {
|
||||
if providerOptions == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range providerOptions {
|
||||
if options, ok := entry.(*fantasyopenai.ResponsesProviderOptions); ok {
|
||||
options.PreviousResponseID = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractOpenAIResponseID extracts the OpenAI Responses API response
|
||||
// ID from provider metadata. Returns an empty string if no OpenAI
|
||||
// Responses metadata is present.
|
||||
func extractOpenAIResponseID(metadata fantasy.ProviderMetadata) string {
|
||||
if len(metadata) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, entry := range metadata {
|
||||
if providerMetadata, ok := entry.(*fantasyopenai.ResponsesProviderMetadata); ok && providerMetadata != nil {
|
||||
return providerMetadata.ResponseID
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractOpenAIResponseIDIfStored returns the OpenAI response ID
|
||||
// only when the provider options indicate store=true. Response IDs
|
||||
// from store=false turns are not persisted server-side and cannot
|
||||
// be used for chaining.
|
||||
func extractOpenAIResponseIDIfStored(
|
||||
providerOptions fantasy.ProviderOptions,
|
||||
metadata fantasy.ProviderMetadata,
|
||||
) string {
|
||||
if !isResponsesStoreEnabled(providerOptions) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return extractOpenAIResponseID(metadata)
|
||||
}
|
||||
|
||||
// isResponsesStoreEnabled checks whether the OpenAI Responses
|
||||
// provider options explicitly enable store=true.
|
||||
func isResponsesStoreEnabled(providerOptions fantasy.ProviderOptions) bool {
|
||||
if providerOptions == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, entry := range providerOptions {
|
||||
if options, ok := entry.(*fantasyopenai.ResponsesProviderOptions); ok {
|
||||
return options.Store != nil && *options.Store
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// recordToolResultTimestamp lazily initializes the
|
||||
// toolResultCreatedAt map on the stepResult and records
|
||||
// the completion timestamp for the given tool-call ID.
|
||||
|
||||
Reference in New Issue
Block a user