mirror of
https://github.com/ollama/ollama.git
synced 2026-09-21 13:38:14 -05:00
proxy: consolidate Codex desktop routing
This commit is contained in:
@@ -17,10 +17,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ollama/ollama/api"
|
||||
"github.com/ollama/ollama/app/codexproxy"
|
||||
"github.com/ollama/ollama/cmd/config"
|
||||
"github.com/ollama/ollama/cmd/internal/fileutil"
|
||||
"github.com/ollama/ollama/envconfig"
|
||||
"github.com/ollama/ollama/internal/proxy"
|
||||
modelpkg "github.com/ollama/ollama/types/model"
|
||||
)
|
||||
|
||||
@@ -29,8 +29,8 @@ const (
|
||||
codexAppIntegrationName = "codex-app"
|
||||
codexAppProfileName = "ollama-launch-codex-app"
|
||||
codexAppBundleID = "com.openai.codex"
|
||||
codexAppModelCatalogFilename = codexproxy.ModelCatalogFilename
|
||||
codexAppRoutingCatalogFilename = codexproxy.RoutingCatalogFilename
|
||||
codexAppModelCatalogFilename = proxy.CodexDesktopModelCatalogFilename
|
||||
codexAppRoutingCatalogFilename = proxy.CodexDesktopRoutingCatalogFilename
|
||||
codexAppAutoReviewModelEnv = "OLLAMA_CODEX_AUTO_REVIEW_MODEL"
|
||||
codexAppOllamaProfileDirName = "chatgpt-ollama"
|
||||
codexAppOllamaUserDataName = "electron-data"
|
||||
@@ -548,7 +548,7 @@ func codexAppReplacementLineEnding(replaced string) string {
|
||||
}
|
||||
|
||||
func codexAppProxyBaseURL() string {
|
||||
return strings.TrimRight(envconfig.ConnectableHost().String(), "/") + codexproxy.PathPrefix + "/v1"
|
||||
return strings.TrimRight(envconfig.ConnectableHost().String(), "/") + proxy.CodexDesktopPathPrefix + "/v1"
|
||||
}
|
||||
|
||||
type codexAppManagedAuth struct {
|
||||
@@ -566,7 +566,7 @@ func ensureCodexAppManagedAuth(configPath string) (bool, error) {
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(codexAppManagedAuth{
|
||||
OpenAIAPIKey: codexproxy.ManagedAPIKey,
|
||||
OpenAIAPIKey: proxy.CodexDesktopManagedAPIKey,
|
||||
AuthMode: "apikey",
|
||||
}, "", " ")
|
||||
if err != nil {
|
||||
@@ -623,7 +623,7 @@ func isCodexAppManagedAuth(data []byte) bool {
|
||||
if json.Unmarshal(data, &auth) != nil {
|
||||
return false
|
||||
}
|
||||
return auth.AuthMode == "apikey" && auth.OpenAIAPIKey == codexproxy.ManagedAPIKey
|
||||
return auth.AuthMode == "apikey" && auth.OpenAIAPIKey == proxy.CodexDesktopManagedAPIKey
|
||||
}
|
||||
|
||||
func (c *CodexApp) Onboard() error {
|
||||
@@ -726,7 +726,7 @@ func defaultCodexAppRouterHealth() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
endpoint := strings.TrimRight(envconfig.ConnectableHost().String(), "/") + codexproxy.PathPrefix + "/_health"
|
||||
endpoint := strings.TrimRight(envconfig.ConnectableHost().String(), "/") + proxy.CodexDesktopPathPrefix + "/_health"
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create ChatGPT router health check: %w", err)
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ollama/ollama/api"
|
||||
"github.com/ollama/ollama/app/codexproxy"
|
||||
"github.com/ollama/ollama/cmd/internal/fileutil"
|
||||
"github.com/ollama/ollama/internal/proxy"
|
||||
"github.com/ollama/ollama/types/model"
|
||||
)
|
||||
|
||||
@@ -238,7 +238,7 @@ func TestCodexAppConfigureMarksOnlyOllamaModelsForNonChatGPTSessions(t *testing.
|
||||
func TestCodexAppRouterHealth(t *testing.T) {
|
||||
t.Run("ready", func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != codexproxy.PathPrefix+"/_health" {
|
||||
if r.URL.Path != proxy.CodexDesktopPathPrefix+"/_health" {
|
||||
t.Fatalf("health path = %q", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -561,7 +561,7 @@ func TestCodexAppManagedAuthLifecycle(t *testing.T) {
|
||||
if err := json.Unmarshal(data, &auth); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if auth.AuthMode != "apikey" || auth.OpenAIAPIKey != codexproxy.ManagedAPIKey {
|
||||
if auth.AuthMode != "apikey" || auth.OpenAIAPIKey != proxy.CodexDesktopManagedAPIKey {
|
||||
t.Fatalf("managed auth = %+v", auth)
|
||||
}
|
||||
info, err := os.Stat(authPath)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package codexproxy
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -22,24 +22,24 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// PathPrefix is the loopback-only Ollama server route used as Codex's
|
||||
// openai_base_url. Codex appends /v1/responses and related paths to it.
|
||||
PathPrefix = "/api/codex"
|
||||
// CodexDesktopPathPrefix is the loopback-only Ollama server route used as
|
||||
// Codex's openai_base_url. Codex appends /v1/responses and related paths.
|
||||
CodexDesktopPathPrefix = "/api/codex"
|
||||
|
||||
// ModelCatalogFilename is the combined native and Ollama catalog shown by
|
||||
// the Codex model picker.
|
||||
ModelCatalogFilename = "ollama-launch-models.json"
|
||||
// CodexDesktopModelCatalogFilename is the combined native and Ollama catalog
|
||||
// shown by the Codex model picker.
|
||||
CodexDesktopModelCatalogFilename = "ollama-launch-models.json"
|
||||
|
||||
// RoutingCatalogFilename contains only Ollama model slugs. Keeping the
|
||||
// router allow-list separate from the picker catalog ensures native Codex
|
||||
// CodexDesktopRoutingCatalogFilename contains only Ollama model slugs.
|
||||
// Keeping the router allow-list separate from the picker catalog ensures native Codex
|
||||
// models continue to pass through to their normal OpenAI upstream.
|
||||
RoutingCatalogFilename = "ollama-launch-codex-routing.json"
|
||||
CodexDesktopRoutingCatalogFilename = "ollama-launch-codex-routing.json"
|
||||
|
||||
// ManagedAPIKey is a local sentinel used to let signed-out ChatGPT users
|
||||
// open Codex directly with Ollama models. It is never valid for an OpenAI
|
||||
// CodexDesktopManagedAPIKey is a local sentinel used to let signed-out
|
||||
// ChatGPT users open Codex directly with Ollama models. It is never valid for an OpenAI
|
||||
// upstream and the router must reject it before any native request leaves
|
||||
// the machine.
|
||||
ManagedAPIKey = "ollama-local-codex"
|
||||
CodexDesktopManagedAPIKey = "ollama-local-codex"
|
||||
|
||||
// autoReviewModel is the native Codex reviewer alias. The routing catalog
|
||||
// may map requests for this alias to a selected Ollama model.
|
||||
@@ -96,9 +96,8 @@ var hopByHopHeaders = map[string]struct{}{
|
||||
"upgrade": {},
|
||||
}
|
||||
|
||||
// Config describes the upstreams and Ollama-only routing catalog.
|
||||
type Config struct {
|
||||
PathPrefix string
|
||||
// CodexDesktopConfig describes the upstreams and Ollama-only routing catalog.
|
||||
type CodexDesktopConfig struct {
|
||||
OllamaURL string
|
||||
ChatGPTURL string
|
||||
OpenAIURL string
|
||||
@@ -109,10 +108,11 @@ type Config struct {
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Handler routes catalog-listed model slugs to Ollama and passes every other
|
||||
// request to the native upstream selected by Codex's authentication mode.
|
||||
type Handler struct {
|
||||
pathPrefix string
|
||||
// CodexDesktop routes catalog-listed model slugs to Ollama and passes every
|
||||
// other request to the native upstream selected by Codex's authentication mode.
|
||||
// The Ollama server mounts this handler on its existing listener, so one
|
||||
// loopback port serves both routes.
|
||||
type CodexDesktop struct {
|
||||
ollamaURL *url.URL
|
||||
chatGPTURL *url.URL
|
||||
openAIURL *url.URL
|
||||
@@ -144,15 +144,7 @@ type statusResponse struct {
|
||||
LastUpstreamStatus int `json:"last_upstream_status,omitempty"`
|
||||
}
|
||||
|
||||
func New(config Config) (*Handler, error) {
|
||||
pathPrefix := strings.TrimRight(strings.TrimSpace(config.PathPrefix), "/")
|
||||
if pathPrefix == "" {
|
||||
pathPrefix = PathPrefix
|
||||
}
|
||||
if !strings.HasPrefix(pathPrefix, "/") {
|
||||
return nil, fmt.Errorf("Codex proxy path prefix must start with '/': %q", pathPrefix)
|
||||
}
|
||||
|
||||
func NewCodexDesktop(config CodexDesktopConfig) (*CodexDesktop, error) {
|
||||
ollamaURL, err := parseBaseURL("Ollama", config.OllamaURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -183,8 +175,7 @@ func New(config Config) (*Handler, error) {
|
||||
transport = http.DefaultTransport.(*http.Transport).Clone()
|
||||
}
|
||||
|
||||
handler := &Handler{
|
||||
pathPrefix: pathPrefix,
|
||||
handler := &CodexDesktop{
|
||||
ollamaURL: ollamaURL,
|
||||
chatGPTURL: chatGPTURL,
|
||||
openAIURL: openAIURL,
|
||||
@@ -213,13 +204,13 @@ func parseBaseURL(name, raw string) (*url.URL, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *CodexDesktop) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !isLoopbackRequest(r) {
|
||||
writeJSONError(w, http.StatusForbidden, "Codex proxy only accepts loopback requests")
|
||||
return
|
||||
}
|
||||
|
||||
suffix, ok := strings.CutPrefix(r.URL.Path, h.pathPrefix)
|
||||
suffix, ok := strings.CutPrefix(r.URL.Path, CodexDesktopPathPrefix)
|
||||
if !ok || suffix == "" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
@@ -412,7 +403,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.logActivity(started, r.Method, suffix, model, route, resp.StatusCode, result)
|
||||
}
|
||||
|
||||
func (h *Handler) logActivity(started time.Time, method, path, model, route string, status int, result string) {
|
||||
func (h *CodexDesktop) logActivity(started time.Time, method, path, model, route string, status int, result string) {
|
||||
h.writeActivity(
|
||||
"route=%s model=%q method=%s path=%s status=%d duration=%s result=%s",
|
||||
route,
|
||||
@@ -425,7 +416,7 @@ func (h *Handler) logActivity(started time.Time, method, path, model, route stri
|
||||
)
|
||||
}
|
||||
|
||||
func (h *Handler) writeActivity(format string, args ...any) {
|
||||
func (h *CodexDesktop) writeActivity(format string, args ...any) {
|
||||
if h.activityLogPath == "" {
|
||||
return
|
||||
}
|
||||
@@ -449,7 +440,7 @@ func (h *Handler) writeActivity(format string, args ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) writeStatus(w http.ResponseWriter) {
|
||||
func (h *CodexDesktop) writeStatus(w http.ResponseWriter) {
|
||||
status := statusResponse{
|
||||
OK: true,
|
||||
OllamaRequests: h.ollamaRequests.Load(),
|
||||
@@ -475,7 +466,7 @@ func isAcceptedModelRequest(method, path string, hasModel bool, status int) bool
|
||||
status < http.StatusMultipleChoices
|
||||
}
|
||||
|
||||
func (h *Handler) readBodies(r *http.Request) ([]byte, []byte, error) {
|
||||
func (h *CodexDesktop) readBodies(r *http.Request) ([]byte, []byte, error) {
|
||||
if r.Body == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -1482,7 +1473,7 @@ func usesChatGPTBackend(header http.Header) bool {
|
||||
|
||||
func usesManagedAPIKey(header http.Header) bool {
|
||||
parts := strings.Fields(header.Get("Authorization"))
|
||||
return len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") && parts[1] == ManagedAPIKey
|
||||
return len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") && parts[1] == CodexDesktopManagedAPIKey
|
||||
}
|
||||
|
||||
func copyOllamaRequestHeaders(dst, src http.Header) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package codexproxy
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/klauspost/compress/zstd"
|
||||
)
|
||||
|
||||
func TestHandlerRoutesCatalogModelToOllamaAndStripsCredentials(t *testing.T) {
|
||||
func TestCodexDesktopRoutesCatalogModelToOllamaAndStripsCredentials(t *testing.T) {
|
||||
var gotPath, gotQuery, gotAuthorization, gotEncoding, gotMetadata string
|
||||
var gotBody []byte
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -36,7 +36,7 @@ func TestHandlerRoutesCatalogModelToOllamaAndStripsCredentials(t *testing.T) {
|
||||
defer chatGPT.Close()
|
||||
|
||||
catalogPath := writeCatalog(t, "glm-5.2:cloud")
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL+"/backend-api/codex", catalogPath)
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", catalogPath)
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -48,8 +48,8 @@ func TestHandlerRoutesCatalogModelToOllamaAndStripsCredentials(t *testing.T) {
|
||||
compressed := encoder.EncodeAll(payload, nil)
|
||||
encoder.Close()
|
||||
|
||||
for _, authorization := range []string{"Bearer chatgpt-secret", "Bearer " + ManagedAPIKey} {
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+PathPrefix+"/v1/responses?trace=1", bytes.NewReader(compressed))
|
||||
for _, authorization := range []string{"Bearer chatgpt-secret", "Bearer " + CodexDesktopManagedAPIKey} {
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+CodexDesktopPathPrefix+"/v1/responses?trace=1", bytes.NewReader(compressed))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -81,6 +81,49 @@ func TestHandlerRoutesCatalogModelToOllamaAndStripsCredentials(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexDesktopRoutesOllamaAndNativeModelsThroughOneEndpoint(t *testing.T) {
|
||||
var ollamaCalls, chatGPTCalls int
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
ollamaCalls++
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer ollama.Close()
|
||||
chatGPT := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
chatGPTCalls++
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
endpoint := httptest.NewServer(handler)
|
||||
defer endpoint.Close()
|
||||
|
||||
for _, model := range []string{"glm-5.2:cloud", "gpt-5.6-sol"} {
|
||||
req, err := http.NewRequest(
|
||||
http.MethodPost,
|
||||
endpoint.URL+CodexDesktopPathPrefix+"/v1/responses",
|
||||
strings.NewReader(fmt.Sprintf(`{"model":%q}`, model)),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer chatgpt-secret")
|
||||
req.Header.Set("ChatGPT-Account-ID", "account-123")
|
||||
resp, err := endpoint.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("model %q status = %d", model, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
if ollamaCalls != 1 || chatGPTCalls != 1 {
|
||||
t.Fatalf("routes through one endpoint: Ollama calls=%d ChatGPT calls=%d", ollamaCalls, chatGPTCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeOllamaThinkingUsesRoutedModelContract(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -272,7 +315,7 @@ func TestNormalizeOllamaThinkingRejectsMalformedReasoning(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadCatalogModelsReadsOptionalThinkingMetadata(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), ModelCatalogFilename)
|
||||
path := filepath.Join(t.TempDir(), CodexDesktopModelCatalogFilename)
|
||||
data := []byte(`{"models":[{"slug":"legacy"},{"slug":"binary","thinking":{"supported":true,"levels":["none","medium"],"values":{"none":false,"medium":true}}}]}`)
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -294,7 +337,7 @@ func TestLoadCatalogModelsReadsOptionalThinkingMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRoutesAutoReviewToConfiguredOllamaModel(t *testing.T) {
|
||||
func TestCodexDesktopRoutesAutoReviewToConfiguredOllamaModel(t *testing.T) {
|
||||
var gotBody []byte
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotBody, _ = io.ReadAll(r.Body)
|
||||
@@ -308,12 +351,12 @@ func TestHandlerRoutesAutoReviewToConfiguredOllamaModel(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL, writeCatalogWithAutoReview(t, "glm-5.3-flash:cloud", "glm-5.3-flash:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL, writeCatalogWithAutoReview(t, "glm-5.3-flash:cloud", "glm-5.3-flash:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
resp, err := http.Post(
|
||||
proxy.URL+PathPrefix+"/v1/responses",
|
||||
proxy.URL+CodexDesktopPathPrefix+"/v1/responses",
|
||||
"application/json",
|
||||
strings.NewReader(`{"model":"codex-auto-review","input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"review"}]}]}`),
|
||||
)
|
||||
@@ -597,7 +640,7 @@ func TestTransformAutoReviewResponsePreservesProviderFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerKeepsAutoReviewOnChatGPTByDefault(t *testing.T) {
|
||||
func TestCodexDesktopKeepsAutoReviewOnChatGPTByDefault(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("native Auto-review request should not reach Ollama")
|
||||
}))
|
||||
@@ -616,11 +659,11 @@ func TestHandlerKeepsAutoReviewOnChatGPTByDefault(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+PathPrefix+"/v1/responses", strings.NewReader(`{"model":"codex-auto-review"}`))
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+CodexDesktopPathPrefix+"/v1/responses", strings.NewReader(`{"model":"codex-auto-review"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -639,15 +682,15 @@ func TestHandlerKeepsAutoReviewOnChatGPTByDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRejectsAutoReviewModelOutsideRoutingCatalog(t *testing.T) {
|
||||
func TestCodexDesktopRejectsAutoReviewModelOutsideRoutingCatalog(t *testing.T) {
|
||||
called := false
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
called = true
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
handler := newTestHandler(t, upstream.URL, upstream.URL, writeCatalogWithAutoReview(t, "qwen3:8b", "glm-5.3-flash:cloud"))
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost"+PathPrefix+"/v1/responses", strings.NewReader(`{"model":"codex-auto-review"}`))
|
||||
handler := newTestCodexDesktop(t, upstream.URL, upstream.URL, writeCatalogWithAutoReview(t, "qwen3:8b", "glm-5.3-flash:cloud"))
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost"+CodexDesktopPathPrefix+"/v1/responses", strings.NewReader(`{"model":"codex-auto-review"}`))
|
||||
req.RemoteAddr = "127.0.0.1:1234"
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -660,7 +703,7 @@ func TestHandlerRejectsAutoReviewModelOutsideRoutingCatalog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerNormalizesCodexOnlyHistoryForOllama(t *testing.T) {
|
||||
func TestCodexDesktopNormalizesCodexOnlyHistoryForOllama(t *testing.T) {
|
||||
var gotBody []byte
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotBody, _ = io.ReadAll(r.Body)
|
||||
@@ -672,7 +715,7 @@ func TestHandlerNormalizesCodexOnlyHistoryForOllama(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.2:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.2:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -688,7 +731,7 @@ func TestHandlerNormalizesCodexOnlyHistoryForOllama(t *testing.T) {
|
||||
{"type":"future_codex_item","secret":"ignored"}
|
||||
]
|
||||
}`
|
||||
resp, err := http.Post(proxy.URL+PathPrefix+"/v1/responses", "application/json", strings.NewReader(payload))
|
||||
resp, err := http.Post(proxy.URL+CodexDesktopPathPrefix+"/v1/responses", "application/json", strings.NewReader(payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -726,7 +769,7 @@ func TestHandlerNormalizesCodexOnlyHistoryForOllama(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerPreservesToolSearchAndOllamaCompactionForOllama(t *testing.T) {
|
||||
func TestCodexDesktopPreservesToolSearchAndOllamaCompactionForOllama(t *testing.T) {
|
||||
var gotBody []byte
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotBody, _ = io.ReadAll(r.Body)
|
||||
@@ -738,7 +781,7 @@ func TestHandlerPreservesToolSearchAndOllamaCompactionForOllama(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -753,7 +796,7 @@ func TestHandlerPreservesToolSearchAndOllamaCompactionForOllama(t *testing.T) {
|
||||
{"type":"compaction_trigger"}
|
||||
]
|
||||
}`
|
||||
resp, err := http.Post(proxy.URL+PathPrefix+"/v1/responses", "application/json", strings.NewReader(payload))
|
||||
resp, err := http.Post(proxy.URL+CodexDesktopPathPrefix+"/v1/responses", "application/json", strings.NewReader(payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -779,7 +822,7 @@ func TestHandlerPreservesToolSearchAndOllamaCompactionForOllama(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerFiltersNativeReasoningWhenSwitchingToOllama(t *testing.T) {
|
||||
func TestCodexDesktopFiltersNativeReasoningWhenSwitchingToOllama(t *testing.T) {
|
||||
var gotBody []byte
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotBody, _ = io.ReadAll(r.Body)
|
||||
@@ -791,7 +834,7 @@ func TestHandlerFiltersNativeReasoningWhenSwitchingToOllama(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL, writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -804,7 +847,7 @@ func TestHandlerFiltersNativeReasoningWhenSwitchingToOllama(t *testing.T) {
|
||||
{"type":"message","role":"user","content":[{"type":"input_text","text":"continue"}]}
|
||||
]
|
||||
}`
|
||||
resp, err := http.Post(proxy.URL+PathPrefix+"/v1/responses", "application/json", strings.NewReader(payload))
|
||||
resp, err := http.Post(proxy.URL+CodexDesktopPathPrefix+"/v1/responses", "application/json", strings.NewReader(payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -830,7 +873,7 @@ func TestHandlerFiltersNativeReasoningWhenSwitchingToOllama(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerPassesNativeModelToChatGPT(t *testing.T) {
|
||||
func TestCodexDesktopPassesNativeModelToChatGPT(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("native model should not reach Ollama")
|
||||
}))
|
||||
@@ -848,12 +891,12 @@ func TestHandlerPassesNativeModelToChatGPT(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
payload := []byte(`{"model":"gpt-5.6-sol","stream":true,"input":[{"type":"message","role":"developer","content":[{"type":"input_text","text":"native instructions"}]}]}`)
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+PathPrefix+"/v1/responses", bytes.NewReader(payload))
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+CodexDesktopPathPrefix+"/v1/responses", bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -880,7 +923,7 @@ func TestHandlerPassesNativeModelToChatGPT(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerPassesNativeModelToOpenAIAPIWithAPIKey(t *testing.T) {
|
||||
func TestCodexDesktopPassesNativeModelToOpenAIAPIWithAPIKey(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("native model should not reach Ollama")
|
||||
}))
|
||||
@@ -899,8 +942,7 @@ func TestHandlerPassesNativeModelToOpenAIAPIWithAPIKey(t *testing.T) {
|
||||
}))
|
||||
defer openAI.Close()
|
||||
|
||||
handler, err := New(Config{
|
||||
PathPrefix: PathPrefix,
|
||||
handler, err := NewCodexDesktop(CodexDesktopConfig{
|
||||
OllamaURL: ollama.URL,
|
||||
ChatGPTURL: chatGPT.URL + "/backend-api/codex",
|
||||
OpenAIURL: openAI.URL + "/v1",
|
||||
@@ -914,7 +956,7 @@ func TestHandlerPassesNativeModelToOpenAIAPIWithAPIKey(t *testing.T) {
|
||||
|
||||
req, err := http.NewRequest(
|
||||
http.MethodPost,
|
||||
proxy.URL+PathPrefix+"/v1/responses",
|
||||
proxy.URL+CodexDesktopPathPrefix+"/v1/responses",
|
||||
strings.NewReader(`{"model":"gpt-5.6-sol"}`),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -939,7 +981,7 @@ func TestHandlerPassesNativeModelToOpenAIAPIWithAPIKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRejectsManagedAPIKeyForNativeModel(t *testing.T) {
|
||||
func TestCodexDesktopRejectsManagedAPIKeyForNativeModel(t *testing.T) {
|
||||
ollamaCalled := false
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
ollamaCalled = true
|
||||
@@ -956,8 +998,7 @@ func TestHandlerRejectsManagedAPIKeyForNativeModel(t *testing.T) {
|
||||
}))
|
||||
defer openAI.Close()
|
||||
|
||||
handler, err := New(Config{
|
||||
PathPrefix: PathPrefix,
|
||||
handler, err := NewCodexDesktop(CodexDesktopConfig{
|
||||
OllamaURL: ollama.URL,
|
||||
ChatGPTURL: chatGPT.URL + "/backend-api/codex",
|
||||
OpenAIURL: openAI.URL + "/v1",
|
||||
@@ -971,13 +1012,13 @@ func TestHandlerRejectsManagedAPIKeyForNativeModel(t *testing.T) {
|
||||
|
||||
req, err := http.NewRequest(
|
||||
http.MethodPost,
|
||||
proxy.URL+PathPrefix+"/v1/responses",
|
||||
proxy.URL+CodexDesktopPathPrefix+"/v1/responses",
|
||||
strings.NewReader(`{"model":"gpt-5.6-sol"}`),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+ManagedAPIKey)
|
||||
req.Header.Set("Authorization", "Bearer "+CodexDesktopManagedAPIKey)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -996,7 +1037,7 @@ func TestHandlerRejectsManagedAPIKeyForNativeModel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerPreservesCompressedNativeRequestWithoutOllamaReasoning(t *testing.T) {
|
||||
func TestCodexDesktopPreservesCompressedNativeRequestWithoutOllamaReasoning(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("native model should not reach Ollama")
|
||||
}))
|
||||
@@ -1011,7 +1052,7 @@ func TestHandlerPreservesCompressedNativeRequestWithoutOllamaReasoning(t *testin
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -1023,7 +1064,7 @@ func TestHandlerPreservesCompressedNativeRequestWithoutOllamaReasoning(t *testin
|
||||
compressed := encoder.EncodeAll(payload, nil)
|
||||
encoder.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+PathPrefix+"/v1/responses", bytes.NewReader(compressed))
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+CodexDesktopPathPrefix+"/v1/responses", bytes.NewReader(compressed))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1046,7 +1087,7 @@ func TestHandlerPreservesCompressedNativeRequestWithoutOllamaReasoning(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerFiltersOllamaProviderStateWhenSwitchingToNativeModel(t *testing.T) {
|
||||
func TestCodexDesktopFiltersOllamaProviderStateWhenSwitchingToNativeModel(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("native model should not reach Ollama")
|
||||
}))
|
||||
@@ -1062,7 +1103,7 @@ func TestHandlerFiltersOllamaProviderStateWhenSwitchingToNativeModel(t *testing.
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.3-flash:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -1084,7 +1125,7 @@ func TestHandlerFiltersOllamaProviderStateWhenSwitchingToNativeModel(t *testing.
|
||||
compressed := encoder.EncodeAll(payload, nil)
|
||||
encoder.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+PathPrefix+"/v1/responses", bytes.NewReader(compressed))
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+CodexDesktopPathPrefix+"/v1/responses", bytes.NewReader(compressed))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1148,7 +1189,7 @@ func TestIsOllamaReasoningItemID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRequestsHTTPFallbackForWebSocketUpgrade(t *testing.T) {
|
||||
func TestCodexDesktopRequestsHTTPFallbackForWebSocketUpgrade(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("WebSocket fallback should not reach Ollama")
|
||||
}))
|
||||
@@ -1158,11 +1199,11 @@ func TestHandlerRequestsHTTPFallbackForWebSocketUpgrade(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, proxy.URL+PathPrefix+"/v1/responses", nil)
|
||||
req, err := http.NewRequest(http.MethodGet, proxy.URL+CodexDesktopPathPrefix+"/v1/responses", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1181,7 +1222,7 @@ func TestHandlerRequestsHTTPFallbackForWebSocketUpgrade(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerStatusReportsObservedRoutes(t *testing.T) {
|
||||
func TestCodexDesktopStatusReportsObservedRoutes(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
@@ -1192,7 +1233,7 @@ func TestHandlerStatusReportsObservedRoutes(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
@@ -1200,7 +1241,7 @@ func TestHandlerStatusReportsObservedRoutes(t *testing.T) {
|
||||
`{"model":"glm-5.2:cloud"}`,
|
||||
`{"model":"gpt-5.6-sol"}`,
|
||||
} {
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+PathPrefix+"/v1/responses", strings.NewReader(payload))
|
||||
req, err := http.NewRequest(http.MethodPost, proxy.URL+CodexDesktopPathPrefix+"/v1/responses", strings.NewReader(payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1213,7 +1254,7 @@ func TestHandlerStatusReportsObservedRoutes(t *testing.T) {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
|
||||
resp, err := http.Get(proxy.URL + PathPrefix + "/_status")
|
||||
resp, err := http.Get(proxy.URL + CodexDesktopPathPrefix + "/_status")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1230,7 +1271,7 @@ func TestHandlerStatusReportsObservedRoutes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerCountersExcludeProbesAndFailedRetries(t *testing.T) {
|
||||
func TestCodexDesktopCountersExcludeProbesAndFailedRetries(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}))
|
||||
@@ -1240,11 +1281,11 @@ func TestHandlerCountersExcludeProbesAndFailedRetries(t *testing.T) {
|
||||
}))
|
||||
defer chatGPT.Close()
|
||||
|
||||
handler := newTestHandler(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
handler := newTestCodexDesktop(t, ollama.URL, chatGPT.URL+"/backend-api/codex", writeCatalog(t, "glm-5.2:cloud"))
|
||||
proxy := httptest.NewServer(handler)
|
||||
defer proxy.Close()
|
||||
|
||||
probeReq, err := http.NewRequest(http.MethodGet, proxy.URL+PathPrefix+"/v1/responses", nil)
|
||||
probeReq, err := http.NewRequest(http.MethodGet, proxy.URL+CodexDesktopPathPrefix+"/v1/responses", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1255,7 +1296,7 @@ func TestHandlerCountersExcludeProbesAndFailedRetries(t *testing.T) {
|
||||
}
|
||||
_ = probeResp.Body.Close()
|
||||
failedResp, err := http.Post(
|
||||
proxy.URL+PathPrefix+"/v1/responses",
|
||||
proxy.URL+CodexDesktopPathPrefix+"/v1/responses",
|
||||
"application/json",
|
||||
strings.NewReader(`{"model":"glm-5.2:cloud"}`),
|
||||
)
|
||||
@@ -1264,7 +1305,7 @@ func TestHandlerCountersExcludeProbesAndFailedRetries(t *testing.T) {
|
||||
}
|
||||
_ = failedResp.Body.Close()
|
||||
|
||||
statusResp, err := http.Get(proxy.URL + PathPrefix + "/_status")
|
||||
statusResp, err := http.Get(proxy.URL + CodexDesktopPathPrefix + "/_status")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1281,15 +1322,14 @@ func TestHandlerCountersExcludeProbesAndFailedRetries(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerWritesSafeActivityLog(t *testing.T) {
|
||||
func TestCodexDesktopWritesSafeActivityLog(t *testing.T) {
|
||||
ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer ollama.Close()
|
||||
|
||||
activityLogPath := filepath.Join(t.TempDir(), "logs", "codex-proxy.log")
|
||||
handler, err := New(Config{
|
||||
PathPrefix: PathPrefix,
|
||||
handler, err := NewCodexDesktop(CodexDesktopConfig{
|
||||
OllamaURL: ollama.URL,
|
||||
ChatGPTURL: "https://chatgpt.com/backend-api/codex",
|
||||
RoutingCatalogPath: writeCatalog(t, "glm-5.2:cloud"),
|
||||
@@ -1304,7 +1344,7 @@ func TestHandlerWritesSafeActivityLog(t *testing.T) {
|
||||
prompt := "do-not-record-this-prompt"
|
||||
req, err := http.NewRequest(
|
||||
http.MethodPost,
|
||||
proxy.URL+PathPrefix+"/v1/responses",
|
||||
proxy.URL+CodexDesktopPathPrefix+"/v1/responses",
|
||||
strings.NewReader(`{"model":"glm-5.2:cloud","input":"`+prompt+`"}`),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -1338,9 +1378,9 @@ func TestHandlerWritesSafeActivityLog(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerRejectsNonLoopbackClients(t *testing.T) {
|
||||
handler := newTestHandler(t, "http://127.0.0.1:11434", "https://chatgpt.com/backend-api/codex", writeCatalog(t, "glm"))
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.test"+PathPrefix+"/_health", nil)
|
||||
func TestCodexDesktopRejectsNonLoopbackClients(t *testing.T) {
|
||||
handler := newTestCodexDesktop(t, "http://127.0.0.1:11434", "https://chatgpt.com/backend-api/codex", writeCatalog(t, "glm"))
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.test"+CodexDesktopPathPrefix+"/_health", nil)
|
||||
req.RemoteAddr = "192.0.2.10:1234"
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -1350,9 +1390,9 @@ func TestHandlerRejectsNonLoopbackClients(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerFailsClosedWhenCatalogIsMissing(t *testing.T) {
|
||||
handler := newTestHandler(t, "http://127.0.0.1:11434", "https://chatgpt.com/backend-api/codex", filepath.Join(t.TempDir(), "missing.json"))
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost"+PathPrefix+"/v1/responses", strings.NewReader(`{"model":"glm"}`))
|
||||
func TestCodexDesktopFailsClosedWhenCatalogIsMissing(t *testing.T) {
|
||||
handler := newTestCodexDesktop(t, "http://127.0.0.1:11434", "https://chatgpt.com/backend-api/codex", filepath.Join(t.TempDir(), "missing.json"))
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost"+CodexDesktopPathPrefix+"/v1/responses", strings.NewReader(`{"model":"glm"}`))
|
||||
req.RemoteAddr = "127.0.0.1:1234"
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -1362,10 +1402,9 @@ func TestHandlerFailsClosedWhenCatalogIsMissing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newTestHandler(t *testing.T, ollamaURL, chatGPTURL, catalogPath string) *Handler {
|
||||
func newTestCodexDesktop(t *testing.T, ollamaURL, chatGPTURL, catalogPath string) *CodexDesktop {
|
||||
t.Helper()
|
||||
handler, err := New(Config{
|
||||
PathPrefix: PathPrefix,
|
||||
handler, err := NewCodexDesktop(CodexDesktopConfig{
|
||||
OllamaURL: ollamaURL,
|
||||
ChatGPTURL: chatGPTURL,
|
||||
RoutingCatalogPath: catalogPath,
|
||||
@@ -1390,7 +1429,7 @@ func writeCatalogWithAutoReview(t *testing.T, autoReviewModel string, models ...
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := filepath.Join(t.TempDir(), ModelCatalogFilename)
|
||||
path := filepath.Join(t.TempDir(), CodexDesktopModelCatalogFilename)
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
+10
-11
@@ -6,30 +6,29 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ollama/ollama/app/codexproxy"
|
||||
"github.com/ollama/ollama/envconfig"
|
||||
"github.com/ollama/ollama/internal/proxy"
|
||||
)
|
||||
|
||||
const (
|
||||
codexProxyChatGPTURL = "https://chatgpt.com/backend-api/codex"
|
||||
codexProxyOpenAIURL = "https://api.openai.com/v1"
|
||||
codexProxyLogFilename = "codex-proxy.log"
|
||||
codexDesktopChatGPTURL = "https://chatgpt.com/backend-api/codex"
|
||||
codexDesktopOpenAIURL = "https://api.openai.com/v1"
|
||||
codexDesktopLogFilename = "codex-proxy.log"
|
||||
)
|
||||
|
||||
func newCodexProxyHandler() (http.Handler, error) {
|
||||
func newCodexDesktopProxy() (http.Handler, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
slog.Warn("failed to find home directory for Codex proxy", "error", err)
|
||||
home = os.TempDir()
|
||||
}
|
||||
|
||||
return codexproxy.New(codexproxy.Config{
|
||||
PathPrefix: codexproxy.PathPrefix,
|
||||
return proxy.NewCodexDesktop(proxy.CodexDesktopConfig{
|
||||
OllamaURL: envconfig.ConnectableHost().String(),
|
||||
ChatGPTURL: codexProxyChatGPTURL,
|
||||
OpenAIURL: codexProxyOpenAIURL,
|
||||
RoutingCatalogPath: filepath.Join(home, ".codex", codexproxy.RoutingCatalogFilename),
|
||||
ActivityLogPath: filepath.Join(home, ".ollama", "logs", codexProxyLogFilename),
|
||||
ChatGPTURL: codexDesktopChatGPTURL,
|
||||
OpenAIURL: codexDesktopOpenAIURL,
|
||||
RoutingCatalogPath: filepath.Join(home, ".codex", proxy.CodexDesktopRoutingCatalogFilename),
|
||||
ActivityLogPath: filepath.Join(home, ".ollama", "logs", codexDesktopLogFilename),
|
||||
Logger: slog.Default(),
|
||||
})
|
||||
}
|
||||
|
||||
+5
-3
@@ -32,13 +32,13 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/ollama/ollama/api"
|
||||
"github.com/ollama/ollama/app/codexproxy"
|
||||
"github.com/ollama/ollama/auth"
|
||||
"github.com/ollama/ollama/discover"
|
||||
"github.com/ollama/ollama/envconfig"
|
||||
"github.com/ollama/ollama/format"
|
||||
"github.com/ollama/ollama/fs/ggml"
|
||||
internalcloud "github.com/ollama/ollama/internal/cloud"
|
||||
"github.com/ollama/ollama/internal/proxy"
|
||||
"github.com/ollama/ollama/llm"
|
||||
"github.com/ollama/ollama/logutil"
|
||||
"github.com/ollama/ollama/manifest"
|
||||
@@ -1840,7 +1840,7 @@ func allowedHostsMiddleware(addr net.Addr) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
func (s *Server) GenerateRoutes() (http.Handler, error) {
|
||||
codexProxyHandler, err := newCodexProxyHandler()
|
||||
codexDesktopProxy, err := newCodexDesktopProxy()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1885,7 +1885,9 @@ func (s *Server) GenerateRoutes() (http.Handler, error) {
|
||||
r.HEAD("/api/version", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"version": version.Version}) })
|
||||
r.GET("/api/version", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"version": version.Version}) })
|
||||
r.GET("/api/status", s.StatusHandler)
|
||||
r.Any(codexproxy.PathPrefix+"/*path", gin.WrapH(codexProxyHandler))
|
||||
// Codex uses this existing Ollama listener for both native and Ollama
|
||||
// models. The proxy selects the upstream per request.
|
||||
r.Any(proxy.CodexDesktopPathPrefix+"/*path", gin.WrapH(codexDesktopProxy))
|
||||
|
||||
// Local model cache management (new implementation is at end of function)
|
||||
r.POST("/api/pull", s.PullHandler)
|
||||
|
||||
Reference in New Issue
Block a user