mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-16 10:15:12 +00:00
fix(codex,memory): sanitize tool call IDs, fix memory delete, add GPT-5.4 (#313)
* fix(codex,memory): sanitize tool call IDs, fix memory delete, add GPT-5.4 - codex_build: sanitize colons in tool call IDs before sending to Responses API (OpenAI rejects non-alphanumeric chars except _ and -) - memory_docs: check RowsAffected on delete — return error instead of silent 200 when no document matched - memory UI: pass document's agent_id to delete call so deletion works from the "all agents" view (previously sent undefined as agent ID) - codex models: add GPT-5.4 and GPT-5.4 Mini, update default model * fix(codex): update default model assertion in test to gpt-5.4 * fix(codex): regex sanitize all invalid chars in tool call IDs, restore GPT-5.1 models - toFcID: replace all non-alphanumeric/underscore/dash chars (not just colons) to prevent future Responses API rejections - Restore GPT-5.1 Codex Max and Codex Mini in model dropdown to avoid breaking existing user configurations --------- Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com> Co-authored-by: viettranx <viettranx@gmail.com>
This commit is contained in:
7 files changed
+41
-20
No files matched your search
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" },
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user