openai_responses.go RawHistoryFolder
1package provider
2
3// OpenAI Responses API client (api.openai.com/v1/responses).
4//
5// Strategy: reuse the existing codexClient (which already speaks the
6// Responses wire format) via a header-rewriting RoundTripper. The codex
7// client always sends `chatgpt-account-id` and `openai-beta`; the public
8// Responses API on api.openai.com accepts but doesn't require those —
9// we strip the account-id header on its way out so the request looks
10// like a normal authenticated OpenAI call.
11//
12// This is a separate provider from `openai` (which is Chat Completions);
13// users opt in by passing `--provider openai-responses` or by picking a
14// model whose catalog entry tags it under that provider id.
15//
16// Auth: API key via Authorization: Bearer (set by codexClient itself).
17// Different from openai-codex, which uses OAuth subscription tokens.
18
19import (
20 "net/http"
21 "strings"
22)
23
24const openaiResponsesDefaultBaseURL = "https://api.openai.com/v1/responses"
25
26// openaiResponsesTransport rewrites headers so the codex client (which
27// is designed for chatgpt.com OAuth tokens) can target the public
28// OpenAI Responses API with a normal API key.
29type openaiResponsesTransport struct {
30 inner http.RoundTripper
31}
32
33func (t *openaiResponsesTransport) RoundTrip(req *http.Request) (*http.Response, error) {
34 clone := req.Clone(req.Context())
35 // The codex client always sets chatgpt-account-id; api.openai.com
36 // doesn't need it and may reject the OAuth identity headers, so
37 // strip them.
38 clone.Header.Del("chatgpt-account-id")
39 clone.Header.Del("openai-beta")
40 clone.Header.Del("originator")
41 // Keep Authorization: Bearer <key> as set by the codex client.
42 return t.inner.RoundTrip(clone)
43}
44
45// NewOpenAIResponses returns an OpenAI Responses-API client (API-key
46// flow). Uses the same wire format as the ChatGPT Codex backend but
47// with the public api.openai.com endpoint and standard Bearer auth.
48//
49// baseURL may be empty; defaults to https://api.openai.com/v1/responses.
50func NewOpenAIResponses(apiKey, baseURL string) Client {
51 return NewOpenAIResponsesNamed(apiKey, baseURL, "openai-responses")
52}
53
54// NewOpenAIResponsesNamed returns a public OpenAI Responses-API client
55// reporting the supplied provider name. This lets the `openai` provider
56// route Responses-only preview models without changing the visible provider.
57func NewOpenAIResponsesNamed(apiKey, baseURL, name string) Client {
58 if baseURL == "" {
59 baseURL = openaiResponsesDefaultBaseURL
60 }
61 httpClient := &http.Client{
62 Transport: &openaiResponsesTransport{inner: http.DefaultTransport},
63 Timeout: 0,
64 }
65 inner := &codexClient{
66 token: apiKey,
67 accountID: "", // unused; transport strips the header
68 baseURL: strings.TrimRight(baseURL, "/"),
69 errorLabel: "openai",
70 providerName: name,
71 http: httpClient,
72 }
73 return &renamedClient{inner: inner, name: name}
74}