diff --git a/internal/providers/codex.go b/internal/providers/codex.go index cfb04b7b..27dc7caa 100644 --- a/internal/providers/codex.go +++ b/internal/providers/codex.go @@ -30,7 +30,7 @@ func NewCodexProvider(name string, tokenSource TokenSource, apiBase, defaultMode apiBase = strings.TrimRight(apiBase, "/") if defaultModel == "" { - defaultModel = "gpt-5.3-codex" + defaultModel = "gpt-5.4" } return &CodexProvider{ diff --git a/internal/providers/codex_build.go b/internal/providers/codex_build.go index 94eeb950..9449b0e6 100644 --- a/internal/providers/codex_build.go +++ b/internal/providers/codex_build.go @@ -7,9 +7,13 @@ import ( "fmt" "io" "net/http" + "regexp" "strings" ) +// invalidFcIDChars matches characters not allowed in Responses API tool call IDs. +var invalidFcIDChars = regexp.MustCompile(`[^a-zA-Z0-9_-]`) + // buildRequestBody converts internal ChatRequest to Responses API format. func (p *CodexProvider) buildRequestBody(req ChatRequest, stream bool) map[string]any { model := req.Model @@ -164,16 +168,17 @@ func (p *CodexProvider) doRequest(ctx context.Context, body any) (io.ReadCloser, return resp.Body, nil } -// toFcID ensures a tool call ID starts with "fc_" as required by the Responses API. +// toFcID ensures a tool call ID starts with "fc_" and contains only +// letters, numbers, underscores, or dashes as required by the Responses API. func toFcID(id string) string { - if strings.HasPrefix(id, "fc_") { - return id - } if strings.HasPrefix(id, "tool_") { - return "fc_" + id[len("tool_"):] - } - if strings.HasPrefix(id, "call_") { - return "fc_" + id[len("call_"):] + id = id[len("tool_"):] + } else if strings.HasPrefix(id, "call_") { + id = id[len("call_"):] + } else if strings.HasPrefix(id, "fc_") { + id = id[len("fc_"):] } + // Replace invalid characters (e.g. colons from session keys) with underscores. + id = invalidFcIDChars.ReplaceAllString(id, "_") return "fc_" + id } diff --git a/internal/providers/codex_test.go b/internal/providers/codex_test.go index 0dbd3e09..fec97a10 100644 --- a/internal/providers/codex_test.go +++ b/internal/providers/codex_test.go @@ -47,8 +47,8 @@ func TestCodexProviderName(t *testing.T) { func TestCodexProviderDefaultModel(t *testing.T) { p := NewCodexProvider("test", &staticTokenSource{token: "test"}, "", "") - if p.DefaultModel() != "gpt-5.3-codex" { - t.Errorf("DefaultModel() = %q, want %q", p.DefaultModel(), "gpt-5.3-codex") + if p.DefaultModel() != "gpt-5.4" { + t.Errorf("DefaultModel() = %q, want %q", p.DefaultModel(), "gpt-5.4") } p2 := NewCodexProvider("test", &staticTokenSource{token: "test"}, "", "o3") diff --git a/internal/store/pg/memory_docs.go b/internal/store/pg/memory_docs.go index e9af2215..ccacb5a7 100644 --- a/internal/store/pg/memory_docs.go +++ b/internal/store/pg/memory_docs.go @@ -85,16 +85,25 @@ func (s *PGMemoryStore) PutDocument(ctx context.Context, agentID, userID, path, func (s *PGMemoryStore) DeleteDocument(ctx context.Context, agentID, userID, path string) error { aid := mustParseUUID(agentID) + var res sql.Result + var err error if userID == "" { - _, err := s.db.ExecContext(ctx, + res, err = s.db.ExecContext(ctx, "DELETE FROM memory_documents WHERE agent_id = $1 AND path = $2 AND user_id IS NULL", aid, path) + } else { + res, err = s.db.ExecContext(ctx, + "DELETE FROM memory_documents WHERE agent_id = $1 AND path = $2 AND user_id = $3", + aid, path, userID) + } + if err != nil { return err } - _, err := s.db.ExecContext(ctx, - "DELETE FROM memory_documents WHERE agent_id = $1 AND path = $2 AND user_id = $3", - aid, path, userID) - return err + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("document not found: %s", path) + } + return nil } func (s *PGMemoryStore) ListDocuments(ctx context.Context, agentID, userID string) ([]store.DocumentInfo, error) { diff --git a/ui/web/src/pages/memory/hooks/use-memory.ts b/ui/web/src/pages/memory/hooks/use-memory.ts index df466591..e445a10e 100644 --- a/ui/web/src/pages/memory/hooks/use-memory.ts +++ b/ui/web/src/pages/memory/hooks/use-memory.ts @@ -95,10 +95,15 @@ export function useMemoryDocuments(filters: MemoryDocFilters) { ); const deleteDocument = useCallback( - async (path: string, userId?: string) => { + async (path: string, userId?: string, agentId?: string) => { + const aid = agentId || filters.agentId; + if (!aid) { + toast.error(i18n.t("memory:toast.docDeleteFailed"), "No agent selected"); + return; + } try { const qs = userId ? `?user_id=${encodeURIComponent(userId)}` : ""; - await http.delete(`/v1/agents/${filters.agentId}/memory/documents/${path}${qs}`); + await http.delete(`/v1/agents/${aid}/memory/documents/${path}${qs}`); await invalidate(); toast.success(i18n.t("memory:toast.docDeleted"), path); } catch (err) { diff --git a/ui/web/src/pages/memory/memory-page.tsx b/ui/web/src/pages/memory/memory-page.tsx index 9b1cf9b5..d958c723 100644 --- a/ui/web/src/pages/memory/memory-page.tsx +++ b/ui/web/src/pages/memory/memory-page.tsx @@ -82,7 +82,7 @@ export function MemoryPage() { if (!deleteTarget) return; setDeleteLoading(true); try { - await deleteDocument(deleteTarget.path, deleteTarget.user_id); + await deleteDocument(deleteTarget.path, deleteTarget.user_id, deleteTarget.agent_id); setDeleteTarget(null); } finally { setDeleteLoading(false); diff --git a/ui/web/src/pages/providers/hooks/use-provider-models.ts b/ui/web/src/pages/providers/hooks/use-provider-models.ts index 7d4681dd..f8f855da 100644 --- a/ui/web/src/pages/providers/hooks/use-provider-models.ts +++ b/ui/web/src/pages/providers/hooks/use-provider-models.ts @@ -8,13 +8,15 @@ export type { ModelInfo }; // Hardcoded models for ChatGPT OAuth provider. // OAuth token lacks api.model.read scope, so we can't call /v1/models. const CODEX_MODELS: ModelInfo[] = [ + { id: "gpt-5.4", name: "GPT-5.4" }, + { id: "gpt-5.4-mini", name: "GPT-5.4 Mini" }, { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, { id: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" }, { id: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, { id: "gpt-5.2", name: "GPT-5.2" }, + { id: "gpt-5.1-codex", name: "GPT-5.1 Codex" }, { id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" }, { id: "gpt-5.1-codex-mini", name: "GPT-5.1 Codex Mini" }, - { id: "gpt-5.1-codex", name: "GPT-5.1 Codex" }, { id: "gpt-5.1", name: "GPT-5.1" }, ];