mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
Stacked on #27683. The OpenAI wire format is decided when the client is built, then thrown away, so downstream sites recompute it from `(provider, modelID, override)`. Any disagreement fails silently: the SDK type-asserts the concrete provider options struct and discards every OpenAI option, and text attachments are dropped because Responses natively accepts only images and PDFs. This adds `chatprovider.Model`, which pairs a fantasy client with the transport resolved from that client's own identity. Its fields are unexported and only the constructor sets the transport, deriving it from the client, so no caller can pick a transport that disagrees with the client it wraps. `chatopenai.Transport`'s zero value is invalid and panics when read rather than defaulting to a wire format, following the existing precedent for construction invariants. `Model` is threaded through construction, the resolve paths, and the four struct fields that store a model for later request preparation. Terminal call sites keep taking `fantasy.LanguageModel` and receive `LanguageModel()`, which avoids a new package edge from `chatloop` and `chatadvisor` into `chatprovider`. No decisions move yet. The consumers still recompute the transport, and `UsesResponsesAPI` now delegates to `TransportFor` so the two agree by construction. #27704 makes the consumers read it from the model. > Mux prepared this PR on Mike's behalf.
65 lines
2.0 KiB
Go
65 lines
2.0 KiB
Go
package chatopenai
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
fantasyazure "charm.land/fantasy/providers/azure"
|
|
fantasyopenai "charm.land/fantasy/providers/openai"
|
|
fantasyopenaicompat "charm.land/fantasy/providers/openaicompat"
|
|
)
|
|
|
|
// Transport identifies the OpenAI wire format a model's client speaks. It is
|
|
// resolved once when the client is built so that request preparation cannot
|
|
// disagree with the client it prepares for.
|
|
type Transport int
|
|
|
|
const (
|
|
// TransportInvalid is the zero value, meaning no transport was ever
|
|
// resolved.
|
|
TransportInvalid Transport = iota
|
|
// TransportNotApplicable marks providers outside the OpenAI wire formats.
|
|
TransportNotApplicable
|
|
TransportChatCompletions
|
|
TransportResponses
|
|
)
|
|
|
|
// UsesResponses reports whether t selects the Responses API. It panics on
|
|
// TransportInvalid instead of defaulting, because an unresolved transport is a
|
|
// construction bug rather than user input.
|
|
func (t Transport) UsesResponses() bool {
|
|
switch t {
|
|
case TransportResponses:
|
|
return true
|
|
case TransportChatCompletions, TransportNotApplicable:
|
|
return false
|
|
default:
|
|
panic(fmt.Sprintf("chatopenai: unresolved transport %d", int(t)))
|
|
}
|
|
}
|
|
|
|
// TransportFor resolves the wire format for a provider and model. override,
|
|
// when non-nil, forces the choice instead of consulting the provider SDK's
|
|
// known-model list. Azure follows the known-model list because its provider
|
|
// exposes no equivalent hook.
|
|
func TransportFor(provider, modelID string, override *bool) Transport {
|
|
var useResponses bool
|
|
switch provider {
|
|
case fantasyopenai.Name:
|
|
useResponses = fantasyopenai.IsResponsesModel(modelID)
|
|
if override != nil {
|
|
useResponses = *override
|
|
}
|
|
case fantasyazure.Name:
|
|
useResponses = fantasyopenai.IsResponsesModel(modelID)
|
|
case fantasyopenaicompat.Name:
|
|
// chatd never builds an openai-compat client with Responses enabled.
|
|
return TransportChatCompletions
|
|
default:
|
|
return TransportNotApplicable
|
|
}
|
|
if useResponses {
|
|
return TransportResponses
|
|
}
|
|
return TransportChatCompletions
|
|
}
|