78 lines
3.2 KiB
Go
78 lines
3.2 KiB
Go
package provider
|
|
|
|
// Protocol normalization (US-001, #538). The user-facing --protocol / protocol
|
|
// value accepts three OpenAI wire variants in addition to anthropic:
|
|
//
|
|
// openai → Chat Completions (POST {base_url}/chat/completions)
|
|
// openai/chat → Chat Completions (alias of "openai")
|
|
// openai/resp_api → Responses API (POST {base_url}/responses)
|
|
// anthropic → Anthropic Messages
|
|
// "" → unset; downstream falls back to model-id heuristics
|
|
//
|
|
// NormalizeProtocol collapses these into a small set of canonical internal
|
|
// selectors so ResolveProvider (#543) can switch on chat vs resp_api without
|
|
// re-parsing surface syntax. "openai" and "openai/chat" both normalize to
|
|
// ProtocolOpenAI, keeping the existing Chat Completions path byte-for-byte
|
|
// unchanged; only "openai/resp_api" produces the new selector.
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ProtocolOpenAIResponses is the canonical selector for the OpenAI Responses
|
|
// API wire format (POST {base_url}/responses). It is distinct from
|
|
// ProtocolOpenAI (Chat Completions) so ResolveProvider can route to the
|
|
// SDK-based Responses driver.
|
|
const ProtocolOpenAIResponses = "openai/resp_api"
|
|
|
|
// NormalizeProtocol maps a raw --protocol / protocol value to a canonical
|
|
// internal selector. Input is trimmed and lower-cased before matching. An empty
|
|
// value stays empty (unset → model-id heuristics). Recognized values normalize
|
|
// to ProtocolOpenAI, ProtocolOpenAIResponses, or ProtocolAnthropic. Any other
|
|
// value is an error naming the accepted set, so a typo surfaces to the caller
|
|
// for exit-code mapping instead of silently falling through.
|
|
func NormalizeProtocol(raw string) (string, error) {
|
|
switch strings.ToLower(strings.TrimSpace(raw)) {
|
|
case "":
|
|
return "", nil
|
|
case ProtocolOpenAI, "openai/chat":
|
|
return ProtocolOpenAI, nil
|
|
case ProtocolOpenAIResponses:
|
|
return ProtocolOpenAIResponses, nil
|
|
case ProtocolAnthropic:
|
|
return ProtocolAnthropic, nil
|
|
default:
|
|
return "", fmt.Errorf("unknown --protocol %q (want openai|openai/chat|openai/resp_api|anthropic)", raw)
|
|
}
|
|
}
|
|
|
|
// ProtocolLabel maps a raw --protocol value to the human-facing label shown in
|
|
// the startup banner's Protocol row, so the displayed wire format matches what
|
|
// pigo actually speaks. It differs from NormalizeProtocol in one deliberate way:
|
|
// the bare "openai" input is surfaced as "openai/chat", making the Chat
|
|
// Completions variant explicit rather than ambiguous. "openai/resp_api" and
|
|
// "anthropic" pass through as themselves.
|
|
//
|
|
// An empty input returns empty (the banner then falls back to "—" or the
|
|
// provider name, so an unset protocol on a named/inferred provider is not
|
|
// mislabeled). An unrecognized value returns the trimmed input verbatim — the
|
|
// label is presentation-only and must never fail; a real typo is already
|
|
// rejected upstream by NormalizeProtocol during resolution.
|
|
func ProtocolLabel(raw string) string {
|
|
canonical, err := NormalizeProtocol(raw)
|
|
if err != nil {
|
|
return strings.TrimSpace(raw)
|
|
}
|
|
switch canonical {
|
|
case ProtocolOpenAI:
|
|
return "openai/chat"
|
|
case ProtocolOpenAIResponses:
|
|
return ProtocolOpenAIResponses
|
|
case ProtocolAnthropic:
|
|
return ProtocolAnthropic
|
|
default:
|
|
return ""
|
|
}
|
|
}
|