diff --git a/core/bifrost.go b/core/bifrost.go index a5a32343a26..c0fdb70d06d 100644 --- a/core/bifrost.go +++ b/core/bifrost.go @@ -7430,6 +7430,13 @@ func prepareResponsesRequest(ctx *schemas.BifrostContext, config *schemas.Provid if r == nil { return nil, nil } + // Codex's explicit "functions" namespace is the default namespace by definition, + // so it is unwrapped for every wire before the support check: Bedrock Mantle + // reserves the name, and flattening wires would otherwise prefix its members. + r, bifrostErr := providerUtils.UnwrapDefaultNamespaceTools(r) + if bifrostErr != nil { + return nil, bifrostErr + } var supported bool if capable, ok := provider.(schemas.ResponsesNamespaceToolProvider); ok { supported = capable.SupportsResponsesNamespaceTools(ctx, key, r.Model) diff --git a/core/changelog.md b/core/changelog.md index e8fa9d9b463..50262e50af6 100644 --- a/core/changelog.md +++ b/core/changelog.md @@ -1,3 +1,5 @@ +- feat: the namespace-tool names a provider reserves for its own server tools (`web`, `image_gen`, `browser`, `python` on Bedrock and Bedrock Mantle) are now read from the datasheet row `reserved_tool_namespaces` (new `ModelCapabilities` field and `ModelCaps.ReservedToolNamespaces`) for the base provider and canonical model; a non-empty row replaces the hardcoded list, an absent or empty row keeps it, so any OpenAI-wire provider can reserve names through a row without a code change +- fix: Codex >= 0.147 wraps its default tools in a namespace literally named `functions` (openai/codex#37022), which Bedrock Mantle reserves and rejects with `User-defined namespace 'functions' collides with an existing tool namespace`. Core now unwraps that namespace to top-level tools, unprefixed, for every provider before dispatch; Codex treats the bare name and the explicit `functions` namespace as the same tool, so no mapping back is needed; a description on the `functions` namespace is prepended to each hoisted member, as flattening does - fix: Responses `namespace` tools are now flattened in core for every provider whose wire does not support the type (Anthropic, Gemini, Vertex, Bedrock Converse, DeepSeek, and every OpenAI-compatible third party), with nested functions renamed to `__` so two namespaces that share a function name no longer collide into an upstream `Tool names must be unique` 400; returned `function_call` items are mapped back to the bare `name` plus `namespace` (unary and streaming), prior-turn calls carrying `namespace` and `tool_choice` names are re-aliased to match, and a name that is still duplicated after flattening or a `tool_choice` that matches several namespaces is rejected with a clear 400 before reaching the provider. Bedrock answers namespace support from its own surface resolver via the new optional `ResponsesNamespaceToolProvider` interface. A datasheet row `supports_namespace_tools` (new `ModelCapabilities` field and `ModelCaps.SupportsNamespaceTools`) overrides the per-provider default for a (provider, model) pair; with no row the default applies. A row can only narrow within what the wire can carry: the Anthropic Messages API (Anthropic, Claude on Azure or Bedrock Mantle), the Gemini API (Gemini, Vertex) and Bedrock Converse have no namespace container, so they answer false regardless of the row and always flatten. Flattened names honour the target wire's documented tool-name limit, 64 characters of `[A-Za-z0-9_-]` for OpenAI-compatible wires, Bedrock Converse and Fireworks, 128 for Anthropic, and 128 with `.` and `:` allowed for Gemini and Vertex, overridable per model through the datasheet row `tool_name_max_length` (a row below 10 cannot hold the hashed form and is ignored); a longer alias becomes an 8-hex hash prefix plus the function name, deterministically, so history and `tool_choice` re-alias to the same string. The alias map travels on the prepared request (`BifrostResponsesRequest.NamespaceToolAliases`) and is applied to that attempt's response, unary and streaming; nothing is kept on the request context or in process-wide state (#7048) - fix: drop namespace tools whose name Amazon Bedrock reserves (`web`, `image_gen`, `browser`, `python`) on the Bedrock and Bedrock Mantle Responses paths instead of forwarding them into a `tools.namespace` collision 400; a dropped Codex `web` namespace becomes the hosted `web_search` tool on Bedrock Mantle - fix: Bedrock Mantle chat streaming no longer drops the usage-only chunk that arrives after `finish_reason`; `ProviderSendsDoneMarker` now treats `bedrock_mantle` (and the legacy Mantle route under the `bedrock` key) as sending `[DONE]`, so streamed usage and cost are recorded (#7065) diff --git a/core/go.mod b/core/go.mod index 922c5618c6f..be2634bdf75 100644 --- a/core/go.mod +++ b/core/go.mod @@ -59,7 +59,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/core/go.sum b/core/go.sum index c4e6bba53d1..6c3caaf3758 100644 --- a/core/go.sum +++ b/core/go.sum @@ -110,8 +110,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/molecule-man/go-brrr v1.0.1 h1:cEjgx8hgNw6UGdhQ94SPDbPkKuRbkUcxBO3IzbGpA/o= github.com/molecule-man/go-brrr v1.0.1/go.mod h1:7ybW6/7gA3oKY45jOfVNjSJDtrr6ea4tzbsTkjmQDC4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= diff --git a/core/promptcachedispatch_test.go b/core/promptcachedispatch_test.go index 73786cb4046..2755d7f047f 100644 --- a/core/promptcachedispatch_test.go +++ b/core/promptcachedispatch_test.go @@ -347,3 +347,47 @@ func TestWrapNamespaceRestorePostHookRunner(t *testing.T) { plain(nil, chunk(), nil) assert.Equal(t, "namespace_a__js", *seen.ResponsesStreamResponse.Item.Name) } + +// Codex >= 0.147 sends a "functions" namespace on every request. It is the default +// namespace by definition, so it is unwrapped for EVERY provider, including ones +// whose wire accepts namespaces: Bedrock Mantle reserves the name and 400s, and on +// OpenAI the unwrap is a no-op semantically. +func TestPrepareResponsesRequest_UnwrapsFunctionsNamespaceForEveryWire(t *testing.T) { + functionsNS := schemas.ResponsesTool{ + Type: schemas.ResponsesToolTypeNamespace, + Name: new("functions"), + ResponsesToolNamespace: &schemas.ResponsesToolNamespace{Tools: []schemas.ResponsesTool{ + {Type: schemas.ResponsesToolTypeFunction, Name: new("wait"), ResponsesToolFunction: &schemas.ResponsesToolFunction{}}, + }}, + } + + t.Run("namespace-capable provider still gets it unwrapped", func(t *testing.T) { + ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline) + req := responsesReqWithNamespaces(schemas.Bedrock) + req.Params.Tools = []schemas.ResponsesTool{functionsNS, req.Params.Tools[0]} + + out, bifrostErr := prepareResponsesRequest(ctx, &schemas.ProviderConfig{}, + namespaceCapableStub{stubProvider: stubProvider{key: schemas.Bedrock}, supported: true}, schemas.Key{}, req) + + require.Nil(t, bifrostErr) + require.NotSame(t, req, out) + require.Len(t, out.Params.Tools, 2) + assert.Equal(t, schemas.ResponsesToolTypeFunction, out.Params.Tools[0].Type) + assert.Equal(t, "wait", *out.Params.Tools[0].Name, "functions members are hoisted without a prefix") + assert.Equal(t, schemas.ResponsesToolTypeNamespace, out.Params.Tools[1].Type, "other namespaces pass through on a capable wire") + assert.Equal(t, schemas.ResponsesToolTypeNamespace, req.Params.Tools[0].Type, "the shared request was mutated") + }) + + t.Run("flattening wire hoists functions members without a prefix", func(t *testing.T) { + ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline) + req := responsesReqWithNamespaces(schemas.Anthropic) + req.Params.Tools = []schemas.ResponsesTool{functionsNS, req.Params.Tools[0]} + + out, bifrostErr := prepareResponsesRequest(ctx, &schemas.ProviderConfig{}, stubProvider{key: schemas.Anthropic}, schemas.Key{}, req) + + require.Nil(t, bifrostErr) + require.Len(t, out.Params.Tools, 2) + assert.Equal(t, "wait", *out.Params.Tools[0].Name) + assert.Equal(t, "namespace_a__js", *out.Params.Tools[1].Name) + }) +} diff --git a/core/providers/openai/responses.go b/core/providers/openai/responses.go index 7b55e716ae0..a1c79619977 100644 --- a/core/providers/openai/responses.go +++ b/core/providers/openai/responses.go @@ -60,17 +60,37 @@ var ProviderFeatures = map[schemas.ModelProvider]ResponsesFeatureSupport{ schemas.BedrockMantle: {AdditionalToolsItem: false, ContextManagement: false}, } -// reservedToolNamespaces lists the namespace-tool names a provider keeps for -// its own server-side tools. Bedrock rejects a user-defined namespace with one -// of these names outright on both the bedrock-mantle and bedrock-runtime -// Responses endpoints: "Invalid Value: 'tools.namespace'. User-defined namespace -// 'web' collides with an existing tool namespace." (HTTP 400). Codex registers a -// client-side "web" namespace (web.run) whenever it believes it is talking to -// OpenAI, which is the case when Bifrost is configured through openai_base_url. -// Live-verified against openai.gpt-5.6-luna on 2026-09-09. -var reservedToolNamespaces = map[schemas.ModelProvider]map[string]bool{ - schemas.Bedrock: {"web": true, "image_gen": true, "browser": true, "python": true}, - schemas.BedrockMantle: {"web": true, "image_gen": true, "browser": true, "python": true}, +// reservedToolNamespaces is the hardcoded fallback for the namespace-tool names a +// provider keeps for its own server-side tools, used when the datasheet row for the +// (base provider, model) publishes no reserved_tool_namespaces. Bedrock rejects a +// user-defined namespace with one of these names outright on both the +// bedrock-mantle and bedrock-runtime Responses endpoints: "Invalid Value: +// 'tools.namespace'. User-defined namespace 'web' collides with an existing tool +// namespace." (HTTP 400). Codex registers a client-side "web" namespace (web.run) +// whenever it believes it is talking to OpenAI, which is the case when Bifrost is +// configured through openai_base_url. Live-verified against openai.gpt-5.6-luna on +// 2026-09-09. +var reservedToolNamespaces = map[schemas.ModelProvider][]string{ + schemas.Bedrock: {"web", "image_gen", "browser", "python"}, + schemas.BedrockMantle: {"web", "image_gen", "browser", "python"}, +} + +// resolveReservedToolNamespaces returns the reserved-name set for one attempt: +// the datasheet row for (toolProvider, capModel) when it publishes one, else the +// hardcoded fallback. toolProvider is the BASE provider, so a custom provider +// wrapping Mantle reads the bedrock_mantle row and the bedrock_mantle fallback. +func resolveReservedToolNamespaces(toolProvider schemas.ModelProvider, capModel string) map[string]bool { + names := schemas.ResolveModelCaps(toolProvider, capModel).ReservedToolNamespaces(reservedToolNamespaces[toolProvider]) + if len(names) == 0 { + return nil + } + reserved := make(map[string]bool, len(names)) + for _, name := range names { + if name != "" { + reserved[name] = true + } + } + return reserved } // dropReservedNamespaceTools returns a copy of tools without the namespace tools @@ -674,9 +694,10 @@ func ToOpenAIResponsesRequest(ctx *schemas.BifrostContext, bifrostReq *schemas.B // the hoist so additional_tools namespaces get the same treatment, and before // filterUnsupportedTools so a substituted web_search goes through its copy path. // Match on the base provider: a custom provider built on bedrock reports its own - // key, which the map does not know, so the reserved namespace would reach AWS. + // key, which neither the datasheet nor the fallback map knows, so the reserved + // namespace would reach AWS. toolProvider := schemas.ResolveBaseProvider(ctx, bifrostReq.Provider) - if reserved := reservedToolNamespaces[toolProvider]; len(reserved) > 0 && len(req.Tools) > 0 { + if reserved := resolveReservedToolNamespaces(toolProvider, capModel); len(reserved) > 0 && len(req.Tools) > 0 { substitute := toolProvider == schemas.BedrockMantle && caps.SupportsWebSearch(true) req.Tools = dropReservedNamespaceTools(req.Tools, reserved, substitute) } diff --git a/core/providers/openai/responses_test.go b/core/providers/openai/responses_test.go index ed77dd11fba..8fdcd4a4857 100644 --- a/core/providers/openai/responses_test.go +++ b/core/providers/openai/responses_test.go @@ -2924,3 +2924,105 @@ func TestToOpenAIResponsesRequest_DropsReservedNamespaceForBedrock(t *testing.T) }) } } + +// The reserved-namespace list is datasheet-first: a row's reserved_tool_namespaces +// replaces the hardcoded per-provider fallback for that (provider, model), and a +// provider with no fallback at all can still reserve names through a row. The row +// is looked up on the BASE provider so a custom provider wrapping Mantle reads +// the bedrock_mantle row. +func TestToOpenAIResponsesRequest_ReservedNamespacesFromDatasheet(t *testing.T) { + rows := map[schemas.ModelProvider]map[string][]string{ + schemas.BedrockMantle: {"openai.gpt-5.6-luna": {"only_this"}}, + schemas.XAI: {"grok-4.6": {"x_tools"}}, + } + schemas.SetCapabilityResolver(func(provider schemas.ModelProvider, model string) *schemas.ModelCapabilities { + reserved, ok := rows[provider][model] + if !ok { + return nil + } + return &schemas.ModelCapabilities{ReservedToolNamespaces: reserved} + }) + t.Cleanup(func() { schemas.SetCapabilityResolver(nil) }) + + namespace := func(name string) schemas.ResponsesTool { + return schemas.ResponsesTool{ + Type: schemas.ResponsesToolTypeNamespace, + Name: schemas.Ptr(name), + ResponsesToolNamespace: &schemas.ResponsesToolNamespace{Tools: []schemas.ResponsesTool{{ + Type: schemas.ResponsesToolTypeFunction, + Name: schemas.Ptr("run"), + ResponsesToolFunction: &schemas.ResponsesToolFunction{}, + }}}, + } + } + + tests := []struct { + name string + provider schemas.ModelProvider + baseProvider schemas.ModelProvider + model string + tools []schemas.ResponsesTool + wantNames []string + }{ + { + name: "mantle row replaces the hardcoded list, so web survives and only_this is dropped", + provider: schemas.BedrockMantle, + model: "openai.gpt-5.6-luna", + tools: []schemas.ResponsesTool{namespace("web"), namespace("only_this"), namespace("keep")}, + wantNames: []string{"web", "keep"}, + }, + { + name: "mantle model without a row keeps the hardcoded fallback", + provider: schemas.BedrockMantle, + model: "openai.gpt-5.6-terra", + tools: []schemas.ResponsesTool{namespace("web"), namespace("only_this"), namespace("keep")}, + wantNames: []string{"only_this", "keep", ""}, + }, + { + name: "a provider with no hardcoded entry reserves names through its row", + provider: schemas.XAI, + model: "grok-4.6", + tools: []schemas.ResponsesTool{namespace("x_tools"), namespace("keep")}, + wantNames: []string{"keep"}, + }, + { + name: "custom provider on a mantle base reads the bedrock_mantle row", + provider: schemas.ModelProvider("my-mantle"), + baseProvider: schemas.BedrockMantle, + model: "openai.gpt-5.6-luna", + tools: []schemas.ResponsesTool{namespace("web"), namespace("only_this")}, + wantNames: []string{"web"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bifrostReq := &schemas.BifrostResponsesRequest{ + Provider: tc.provider, + Model: tc.model, + Input: []schemas.ResponsesMessage{{ + Role: schemas.Ptr(schemas.ResponsesInputMessageRoleUser), + Content: &schemas.ResponsesMessageContent{ContentStr: schemas.Ptr("hi")}, + }}, + Params: &schemas.ResponsesParameters{Tools: tc.tools}, + } + var ctx *schemas.BifrostContext + if tc.baseProvider != "" { + ctx = schemas.NewBifrostContextWithValue(context.Background(), schemas.NoDeadline, + schemas.BifrostContextKeyBaseProviderType, tc.baseProvider) + } + result := ToOpenAIResponsesRequest(ctx, bifrostReq) + require.NotNil(t, result) + + gotNames := make([]string, 0, len(result.Tools)) + for _, tool := range result.Tools { + name := "" + if tool.Name != nil { + name = *tool.Name + } + gotNames = append(gotNames, name) + } + require.Equal(t, tc.wantNames, gotNames) + }) + } +} diff --git a/core/providers/utils/namespacetools.go b/core/providers/utils/namespacetools.go index 50c7518b429..8bf6f23cbfa 100644 --- a/core/providers/utils/namespacetools.go +++ b/core/providers/utils/namespacetools.go @@ -70,6 +70,77 @@ func DefaultNamespaceToolSupport(ctx *schemas.BifrostContext, baseProvider schem } } +// defaultToolNamespace is the namespace OpenAI-family models address top-level tools +// under. Codex >= 0.147 declares it explicitly on every request +// (openai/codex#37022, "Canonicalize default tools under the functions namespace") +// and normalizes a missing, empty or explicit "functions" namespace to the same tool +// identity. Bedrock Mantle reserves the name and rejects an explicit declaration: +// "Invalid Value: 'tools.namespace'. User-defined namespace 'functions' collides +// with an existing tool namespace." +const defaultToolNamespace = "functions" + +// namespaceDescriptionSeparator joins a namespace's description onto each hoisted +// member's description, so the context the namespace carried is not lost. +const namespaceDescriptionSeparator = "\n\n" + +// UnwrapDefaultNamespaceTools hoists the members of a namespace named "functions" +// to the top level, verbatim and unprefixed. It runs for EVERY provider, before the +// wire support check: on wires that accept namespaces it is a semantic no-op, on +// Bedrock Mantle it avoids the reserved-name 400, and on wires that flatten it +// keeps Codex's default tools from acquiring a "functions__" prefix. No reverse map +// is needed because the caller already treats the bare name and the explicit +// namespace as the same tool. +// +// Copy-on-write like the other helpers here; the same pointer comes back when no +// such namespace is present. A hoisted name that duplicates a top-level tool is a +// 400, since the upstream would reject it as well, less clearly. +func UnwrapDefaultNamespaceTools(req *schemas.BifrostResponsesRequest) (*schemas.BifrostResponsesRequest, *schemas.BifrostError) { + if req == nil || req.Params == nil { + return req, nil + } + found := false + for _, tool := range req.Params.Tools { + if isDefaultNamespaceTool(tool) { + found = true + break + } + } + if !found { + return req, nil + } + + unwrapped := make([]schemas.ResponsesTool, 0, len(req.Params.Tools)) + for _, tool := range req.Params.Tools { + if !isDefaultNamespaceTool(tool) { + unwrapped = append(unwrapped, tool) + continue + } + if tool.ResponsesToolNamespace != nil { + // Members are hoisted verbatim except for the description: the namespace's + // own description is context the member would otherwise lose, so it is + // prepended the same way FlattenResponsesNamespaceTools does it. + for _, nested := range tool.ResponsesToolNamespace.Tools { + hoisted := nested + hoisted.Description = joinNamespaceDescription(tool.Description, nested.Description) + unwrapped = append(unwrapped, hoisted) + } + } + } + if bifrostErr := checkUniqueToolNames(unwrapped, nil); bifrostErr != nil { + return nil, bifrostErr + } + + cp := *req + params := *req.Params + params.Tools = unwrapped + cp.Params = ¶ms + return &cp, nil +} + +func isDefaultNamespaceTool(tool schemas.ResponsesTool) bool { + return tool.Type == schemas.ResponsesToolTypeNamespace && tool.Name != nil && *tool.Name == defaultToolNamespace +} + // FlattenResponsesNamespaceTools rewrites a Responses request for a wire that does not // understand namespace tools. Every nested function tool is hoisted to the top level // under the name "__", so two namespaces that both define "js" @@ -115,6 +186,7 @@ func FlattenResponsesNamespaceTools(ctx *schemas.BifrostContext, req *schemas.Bi alias := namespaceToolAlias(namespace, *nested.Name, limit) hoisted := nested hoisted.Name = new(alias) + hoisted.Description = joinNamespaceDescription(tool.Description, nested.Description) flattened = append(flattened, hoisted) aliases[alias] = schemas.NamespaceToolAlias{Namespace: namespace, Name: *nested.Name} byFunction[*nested.Name] = append(byFunction[*nested.Name], alias) @@ -137,11 +209,69 @@ func FlattenResponsesNamespaceTools(ctx *schemas.BifrostContext, req *schemas.Bi return nil, bifrostErr } params.ToolChoice = toolChoice - cp.NamespaceToolAliases = aliases + // The response-side map also carries bare-name fallbacks; request-side + // re-aliasing above keeps using the qualified aliases only. + cp.NamespaceToolAliases = withBareNameFallbacks(aliases, byFunction, flattened) } return &cp, nil } +// joinNamespaceDescription prepends the namespace's description to a hoisted +// member's, blank-line separated, so the grouping context survives flattening. A +// missing side is simply omitted; both missing leaves the description nil. +func joinNamespaceDescription(namespace, member *string) *string { + nsDesc := "" + if namespace != nil { + nsDesc = *namespace + } + switch { + case nsDesc == "": + return member + case member == nil || *member == "": + return new(nsDesc) + default: + return new(nsDesc + namespaceDescriptionSeparator + *member) + } +} + +// withBareNameFallbacks returns the restore map extended with each nested function's +// bare name, when that name lives in exactly one namespace and is not itself a +// top-level tool. A model sometimes calls the short name even though the definition +// was prefixed; mapping it back keeps the caller's dispatch working. Only the map +// stored for the response side gets these entries; request-side lookups keep using +// the qualified aliases. +func withBareNameFallbacks(aliases map[string]schemas.NamespaceToolAlias, byFunction map[string][]string, tools []schemas.ResponsesTool) map[string]schemas.NamespaceToolAlias { + out := make(map[string]schemas.NamespaceToolAlias, len(aliases)+len(byFunction)) + for alias, target := range aliases { + out[alias] = target + } + for bare, candidates := range byFunction { + if len(candidates) != 1 { + continue + } + if _, taken := out[bare]; taken { + continue + } + if isTopLevelToolName(bare, tools, aliases) { + continue + } + out[bare] = aliases[candidates[0]] + } + return out +} + +func isTopLevelToolName(name string, tools []schemas.ResponsesTool, aliases map[string]schemas.NamespaceToolAlias) bool { + for _, tool := range tools { + if tool.Name == nil || *tool.Name != name { + continue + } + if _, isAlias := aliases[*tool.Name]; !isAlias { + return true + } + } + return false +} + // RestoreResponsesNamespaceToolCalls rewrites function_call items whose name is a // flattened alias back to the caller's shape: the bare function name plus the // namespace field, which is how OpenAI reports a namespaced call. aliases is the map diff --git a/core/providers/utils/utils_test.go b/core/providers/utils/utils_test.go index 5e4b135f6eb..4d10267ea81 100644 --- a/core/providers/utils/utils_test.go +++ b/core/providers/utils/utils_test.go @@ -2584,6 +2584,162 @@ func TestFlattenResponsesNamespaceTools_DropsNonFunctionNestedTools(t *testing.T } } +// Codex >= 0.147 wraps its default tools in a namespace literally named +// "functions" (openai/codex#37022), which it treats as identical to no namespace. +// Bedrock Mantle reserves that name and 400s. Unwrapping is identity-preserving, +// so it runs for every provider, before the support check. +func TestUnwrapDefaultNamespaceTools(t *testing.T) { + codexExec := schemas.ResponsesTool{ + Type: schemas.ResponsesToolTypeCustom, + Name: new("exec"), + ResponsesToolCustom: &schemas.ResponsesToolCustom{}, + } + functionsNS := namespaceTool("functions", codexExec, namespaceFunctionTool("wait")) + collab := namespaceTool("collaboration", namespaceFunctionTool("spawn")) + + t.Run("hoists nested tools verbatim and keeps other namespaces", func(t *testing.T) { + req := issue7048Request() + req.Params.Tools = []schemas.ResponsesTool{functionsNS, collab, namespaceFunctionTool("plain")} + + out, bifrostErr := UnwrapDefaultNamespaceTools(req) + if bifrostErr != nil { + t.Fatalf("unexpected error: %v", bifrostErr.Error.Message) + } + if out == req { + t.Fatal("an unwrapping attempt must dispatch a copy, not the shared request") + } + got := toolNames(out.Params.Tools) + if strings.Join(got, ",") != "exec,wait,collaboration,plain" { + t.Fatalf("tool names = %v, want [exec wait collaboration plain]", got) + } + if out.Params.Tools[0].Type != schemas.ResponsesToolTypeCustom { + t.Errorf("custom nested tool must keep its type, got %q", out.Params.Tools[0].Type) + } + if out.Params.Tools[2].Type != schemas.ResponsesToolTypeNamespace { + t.Errorf("a non-default namespace must be left intact, got %q", out.Params.Tools[2].Type) + } + if len(req.Params.Tools) != 3 || req.Params.Tools[0].Type != schemas.ResponsesToolTypeNamespace { + t.Fatal("the shared request was mutated") + } + }) + + t.Run("no functions namespace returns the same pointer", func(t *testing.T) { + req := issue7048Request() + req.Params.Tools = []schemas.ResponsesTool{collab} + out, bifrostErr := UnwrapDefaultNamespaceTools(req) + if bifrostErr != nil || out != req { + t.Fatal("a request without a functions namespace must pass through untouched") + } + }) + + t.Run("prepends the namespace description like flatten does", func(t *testing.T) { + described := functionsNS + described.Description = new("Default tools for this session") + req := issue7048Request() + req.Params.Tools = []schemas.ResponsesTool{described} + + out, bifrostErr := UnwrapDefaultNamespaceTools(req) + if bifrostErr != nil { + t.Fatalf("unexpected error: %v", bifrostErr.Error.Message) + } + wait := out.Params.Tools[1] + if wait.Description == nil || *wait.Description != "Default tools for this session\n\nRun JavaScript" { + t.Fatalf("hoisted member description = %v, want the namespace description prepended", wait.Description) + } + if orig := req.Params.Tools[0].ResponsesToolNamespace.Tools[1].Description; orig == nil || *orig != "Run JavaScript" { + t.Fatal("the shared request's nested tool description was mutated") + } + }) + + t.Run("duplicate against a top-level tool is a 400", func(t *testing.T) { + req := issue7048Request() + req.Params.Tools = []schemas.ResponsesTool{functionsNS, namespaceFunctionTool("wait")} + _, bifrostErr := UnwrapDefaultNamespaceTools(req) + if bifrostErr == nil || bifrostErr.StatusCode == nil || *bifrostErr.StatusCode != 400 { + t.Fatalf("expected a 400 for a name duplicated after unwrapping, got %+v", bifrostErr) + } + }) +} + +// The namespace's own description is context the nested tool loses when it is +// hoisted, so it is prepended, blank-line separated, the way LiteLLM does it. +func TestFlattenResponsesNamespaceTools_PrependsNamespaceDescription(t *testing.T) { + ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline) + described := namespaceTool("crm", namespaceFunctionTool("lookup")) + described.Description = new("CRM tools for customer lookup") + bare := namespaceFunctionTool("undescribed") + bare.Description = nil + describedOnlyNS := namespaceTool("billing", bare) + describedOnlyNS.Description = new("Billing tools") + req := issue7048Request() + req.Params.Tools = []schemas.ResponsesTool{ + described, + describedOnlyNS, + namespaceTool("nodesc", namespaceFunctionTool("run")), + } + + out, bifrostErr := FlattenResponsesNamespaceTools(ctx, req) + if bifrostErr != nil { + t.Fatalf("unexpected error: %v", bifrostErr.Error.Message) + } + want := map[string]string{ + "crm__lookup": "CRM tools for customer lookup\n\nRun JavaScript", + "billing__undescribed": "Billing tools", + "nodesc__run": "Run JavaScript", + } + for _, tool := range out.Params.Tools { + if tool.Description == nil { + t.Errorf("%s: description is nil", *tool.Name) + continue + } + if *tool.Description != want[*tool.Name] { + t.Errorf("%s: description = %q, want %q", *tool.Name, *tool.Description, want[*tool.Name]) + } + } + if *req.Params.Tools[0].ResponsesToolNamespace.Tools[0].Description != "Run JavaScript" { + t.Fatal("the shared request's nested tool description was mutated") + } +} + +// A model sometimes calls the bare nested name even though the definition was +// prefixed. When that bare name lives in exactly one namespace and is not a +// top-level tool, it is restored to that namespace; otherwise it is left alone. +func TestRestoreResponsesNamespaceToolCalls_BareNameFallback(t *testing.T) { + ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline) + req := issue7048Request() + req.Params.Tools = []schemas.ResponsesTool{ + namespaceTool("namespace_a", namespaceFunctionTool("js"), namespaceFunctionTool("unique_fn")), + namespaceTool("namespace_b", namespaceFunctionTool("js"), namespaceFunctionTool("shadowed")), + namespaceFunctionTool("shadowed"), + } + prepared, bifrostErr := FlattenResponsesNamespaceTools(ctx, req) + if bifrostErr != nil { + t.Fatalf("unexpected error: %v", bifrostErr.Error.Message) + } + call := func(name string) schemas.ResponsesMessage { + return schemas.ResponsesMessage{ + Type: new(schemas.ResponsesMessageTypeFunctionCall), + ResponsesToolMessage: &schemas.ResponsesToolMessage{CallID: new("c"), Name: new(name), Arguments: new("{}")}, + } + } + resp := &schemas.BifrostResponse{ResponsesResponse: &schemas.BifrostResponsesResponse{ + Output: []schemas.ResponsesMessage{call("unique_fn"), call("js"), call("shadowed")}, + }} + + RestoreResponsesNamespaceToolCalls(prepared.NamespaceToolAliases, resp) + + out := resp.ResponsesResponse.Output + if *out[0].Name != "unique_fn" || out[0].Namespace == nil || *out[0].Namespace != "namespace_a" { + t.Errorf("unique bare name: got name=%q namespace=%v, want unique_fn in namespace_a", *out[0].Name, out[0].Namespace) + } + if out[1].Namespace != nil { + t.Errorf("ambiguous bare name js must not be assigned a namespace, got %q", *out[1].Namespace) + } + if out[2].Namespace != nil { + t.Errorf("a bare name that is also a top-level tool must not be assigned a namespace, got %q", *out[2].Namespace) + } +} + func TestRestoreResponsesNamespaceToolCalls(t *testing.T) { aliasesFor := func(withAliases bool) map[string]schemas.NamespaceToolAlias { if !withAliases { diff --git a/core/schemas/modelcapabilities.go b/core/schemas/modelcapabilities.go index 75e15f00113..8c3a3881db3 100644 --- a/core/schemas/modelcapabilities.go +++ b/core/schemas/modelcapabilities.go @@ -237,6 +237,14 @@ type ModelCapabilities struct { // Perplexity: reasoning_effort is a required field (not optional). ReasoningRequired *bool `json:"reasoning_required,omitempty"` + // Namespace-tool names the provider keeps for its own server-side tools. A + // caller-defined namespace with one of these names is rejected upstream + // (Bedrock Mantle: "User-defined namespace 'web' collides with an existing + // tool namespace"), so the request builder drops it. A non-empty list replaces + // the hardcoded per-provider fallback in core/providers/openai outright; absent + // or empty means the fallback applies. + ReservedToolNamespaces []string `json:"reserved_tool_namespaces,omitempty"` + // ---- Aliasing & regional inference profiles ---- // Bedrock regional inference profile aliases that point to a canonical entry. diff --git a/core/schemas/modelcapabilities_test.go b/core/schemas/modelcapabilities_test.go index 1537fca5bcd..5d414c9b5f7 100644 --- a/core/schemas/modelcapabilities_test.go +++ b/core/schemas/modelcapabilities_test.go @@ -195,6 +195,35 @@ func TestModelCaps_ToolNameMaxLength(t *testing.T) { }) } +// ModelCaps.ReservedToolNamespaces: a non-empty row replaces the caller's +// hardcoded list outright; an absent or empty row hands the fallback back. +func TestModelCaps_ReservedToolNamespaces(t *testing.T) { + fallback := []string{"web", "python"} + + t.Run("RowReplacesFallback", func(t *testing.T) { + model := "mantle-model-reserved-row" + setCapabilityOverride(t, model, ModelCapabilities{ReservedToolNamespaces: []string{"only_this"}}) + assert.Equal(t, []string{"only_this"}, ResolveModelCaps(BedrockMantle, model).ReservedToolNamespaces(fallback)) + }) + + t.Run("EmptyRowFallsBack", func(t *testing.T) { + model := "mantle-model-empty-row" + setCapabilityOverride(t, model, ModelCapabilities{ReservedToolNamespaces: []string{}}) + assert.Equal(t, fallback, ResolveModelCaps(BedrockMantle, model).ReservedToolNamespaces(fallback)) + }) + + t.Run("AbsentRowFallsBack", func(t *testing.T) { + assert.Equal(t, fallback, ResolveModelCaps(BedrockMantle, "no-row").ReservedToolNamespaces(fallback)) + assert.Nil(t, ResolveModelCaps(BedrockMantle, "no-row").ReservedToolNamespaces(nil)) + }) + + t.Run("ZeroValue", func(t *testing.T) { + var caps ModelCaps + assert.Equal(t, fallback, caps.ReservedToolNamespaces(fallback)) + + }) +} + // A row's effort ladder is taken verbatim over the caller's name-based fallback // and the base set — narrowing included, which is what the per-level booleans // this replaced could not express. diff --git a/core/schemas/modelcaps.go b/core/schemas/modelcaps.go index 3d91b68e061..818b481677c 100644 --- a/core/schemas/modelcaps.go +++ b/core/schemas/modelcaps.go @@ -496,6 +496,17 @@ func (c ModelCaps) ToolNameMaxLength(fallback int) int { return fallback } +// ReservedToolNamespaces returns the namespace-tool names the provider reserves for +// its own server tools. A non-empty row replaces fallback outright, so a row can +// both add names and clear a hardcoded one; absent or empty returns fallback. +func (c ModelCaps) ReservedToolNamespaces(fallback []string) []string { + if c.record != nil && len(c.record.ReservedToolNamespaces) > 0 { + return c.record.ReservedToolNamespaces + + } + return fallback +} + // SupportsAdvisorTool reports whether the model accepts advisor_tool_result blocks. func (c ModelCaps) SupportsAdvisorTool(fallback bool) bool { if c.record != nil && c.record.SupportsAdvisorTool != nil { diff --git a/framework/go.mod b/framework/go.mod index fdc0c5559a1..2d3b8170b73 100644 --- a/framework/go.mod +++ b/framework/go.mod @@ -123,7 +123,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/framework/go.sum b/framework/go.sum index 28e73712b1a..d84074fe7b7 100644 --- a/framework/go.sum +++ b/framework/go.sum @@ -258,8 +258,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/compat/go.mod b/plugins/compat/go.mod index 526de4339c6..7b4b608287c 100644 --- a/plugins/compat/go.mod +++ b/plugins/compat/go.mod @@ -106,7 +106,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/plugins/compat/go.sum b/plugins/compat/go.sum index a8e34871fd0..d8c06196584 100644 --- a/plugins/compat/go.sum +++ b/plugins/compat/go.sum @@ -258,8 +258,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/governance/go.mod b/plugins/governance/go.mod index f5d63cf0cd4..7a19461b2b1 100644 --- a/plugins/governance/go.mod +++ b/plugins/governance/go.mod @@ -109,7 +109,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/plugins/governance/go.sum b/plugins/governance/go.sum index a8e34871fd0..d8c06196584 100644 --- a/plugins/governance/go.sum +++ b/plugins/governance/go.sum @@ -258,8 +258,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/jsonparser/go.mod b/plugins/jsonparser/go.mod index 47c5f5a8dff..dec1c2011a2 100644 --- a/plugins/jsonparser/go.mod +++ b/plugins/jsonparser/go.mod @@ -48,7 +48,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/rs/zerolog v1.34.0 // indirect diff --git a/plugins/jsonparser/go.sum b/plugins/jsonparser/go.sum index 5fb2cba7d1d..1a0db32335f 100644 --- a/plugins/jsonparser/go.sum +++ b/plugins/jsonparser/go.sum @@ -109,8 +109,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= github.com/maximhq/bifrost/core v1.8.6/go.mod h1:f3yJF3KD4br7TRaXgpVmOLXsciMD+YAm8+oAgBr/lCk= github.com/molecule-man/go-brrr v1.0.1 h1:cEjgx8hgNw6UGdhQ94SPDbPkKuRbkUcxBO3IzbGpA/o= diff --git a/plugins/logging/go.mod b/plugins/logging/go.mod index 0ef11d48fef..c38290e458b 100644 --- a/plugins/logging/go.mod +++ b/plugins/logging/go.mod @@ -107,7 +107,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/plugins/logging/go.sum b/plugins/logging/go.sum index a8e34871fd0..d8c06196584 100644 --- a/plugins/logging/go.sum +++ b/plugins/logging/go.sum @@ -258,8 +258,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/maxim/go.mod b/plugins/maxim/go.mod index 7271764e16c..9f377dabc2e 100644 --- a/plugins/maxim/go.mod +++ b/plugins/maxim/go.mod @@ -107,7 +107,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/plugins/maxim/go.sum b/plugins/maxim/go.sum index 8bf0074c30a..5dfabaac5d4 100644 --- a/plugins/maxim/go.sum +++ b/plugins/maxim/go.sum @@ -258,8 +258,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/mocker/go.mod b/plugins/mocker/go.mod index 2f482996dc5..274584d04a6 100644 --- a/plugins/mocker/go.mod +++ b/plugins/mocker/go.mod @@ -51,7 +51,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/rs/zerolog v1.34.0 // indirect diff --git a/plugins/mocker/go.sum b/plugins/mocker/go.sum index d446ca82c2f..722bfd1adc6 100644 --- a/plugins/mocker/go.sum +++ b/plugins/mocker/go.sum @@ -111,8 +111,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= github.com/maximhq/bifrost/core v1.8.6/go.mod h1:f3yJF3KD4br7TRaXgpVmOLXsciMD+YAm8+oAgBr/lCk= github.com/molecule-man/go-brrr v1.0.1 h1:cEjgx8hgNw6UGdhQ94SPDbPkKuRbkUcxBO3IzbGpA/o= diff --git a/plugins/modelcatalogresolver/go.mod b/plugins/modelcatalogresolver/go.mod index df85767b0e2..c76c587a685 100644 --- a/plugins/modelcatalogresolver/go.mod +++ b/plugins/modelcatalogresolver/go.mod @@ -106,7 +106,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/plugins/modelcatalogresolver/go.sum b/plugins/modelcatalogresolver/go.sum index a8e34871fd0..d8c06196584 100644 --- a/plugins/modelcatalogresolver/go.sum +++ b/plugins/modelcatalogresolver/go.sum @@ -258,8 +258,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/otel/go.mod b/plugins/otel/go.mod index e3d282b2810..aebf8fc78b8 100644 --- a/plugins/otel/go.mod +++ b/plugins/otel/go.mod @@ -117,7 +117,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/plugins/otel/go.sum b/plugins/otel/go.sum index 89850dc43ef..33d1224cf9d 100644 --- a/plugins/otel/go.sum +++ b/plugins/otel/go.sum @@ -262,8 +262,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/prompts/go.mod b/plugins/prompts/go.mod index a6b6f46225c..6ca2f763131 100644 --- a/plugins/prompts/go.mod +++ b/plugins/prompts/go.mod @@ -55,7 +55,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect diff --git a/plugins/prompts/go.sum b/plugins/prompts/go.sum index d41d39e0d61..c55fe17cba0 100644 --- a/plugins/prompts/go.sum +++ b/plugins/prompts/go.sum @@ -121,8 +121,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/routing/go.mod b/plugins/routing/go.mod index 38084995a2f..61c29673b9b 100644 --- a/plugins/routing/go.mod +++ b/plugins/routing/go.mod @@ -112,7 +112,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect diff --git a/plugins/routing/go.sum b/plugins/routing/go.sum index 9ba2b37e211..5a8279f54e4 100644 --- a/plugins/routing/go.sum +++ b/plugins/routing/go.sum @@ -264,8 +264,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/plugins/semanticcache/go.mod b/plugins/semanticcache/go.mod index 7dc12799401..9d1f5deca3a 100644 --- a/plugins/semanticcache/go.mod +++ b/plugins/semanticcache/go.mod @@ -80,7 +80,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/oklog/ulid v1.3.1 // indirect diff --git a/plugins/semanticcache/go.sum b/plugins/semanticcache/go.sum index 1364e3be18b..4bd6418afa4 100644 --- a/plugins/semanticcache/go.sum +++ b/plugins/semanticcache/go.sum @@ -179,8 +179,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= github.com/maximhq/bifrost/core v1.8.6/go.mod h1:f3yJF3KD4br7TRaXgpVmOLXsciMD+YAm8+oAgBr/lCk= github.com/maximhq/bifrost/framework v1.6.2 h1:JOXYTMob/b7mh3EWFW1RzV56fBC97w6EPV2eyF/um8k= diff --git a/plugins/telemetry/go.mod b/plugins/telemetry/go.mod index 0264840539c..b119ebc029a 100644 --- a/plugins/telemetry/go.mod +++ b/plugins/telemetry/go.mod @@ -109,7 +109,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/plugins/telemetry/go.sum b/plugins/telemetry/go.sum index fd382ef2faf..f5aacf94631 100644 --- a/plugins/telemetry/go.sum +++ b/plugins/telemetry/go.sum @@ -260,8 +260,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo= diff --git a/tests/cmd/e2eseed/go.mod b/tests/cmd/e2eseed/go.mod index cc815bfd2b1..6ad3b7750e1 100644 --- a/tests/cmd/e2eseed/go.mod +++ b/tests/cmd/e2eseed/go.mod @@ -84,7 +84,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/maximhq/bifrost/core v1.8.6 // indirect github.com/maximhq/bifrost/framework v1.3.16 // indirect diff --git a/tests/cmd/e2eseed/go.sum b/tests/cmd/e2eseed/go.sum index dbe18f65f4f..ba91f912de8 100644 --- a/tests/cmd/e2eseed/go.sum +++ b/tests/cmd/e2eseed/go.sum @@ -197,8 +197,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/molecule-man/go-brrr v1.0.1 h1:cEjgx8hgNw6UGdhQ94SPDbPkKuRbkUcxBO3IzbGpA/o= diff --git a/tests/cmd/seed/go.mod b/tests/cmd/seed/go.mod index 70e5c990be4..625f957958a 100644 --- a/tests/cmd/seed/go.mod +++ b/tests/cmd/seed/go.mod @@ -89,7 +89,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/paulmach/orb v0.11.1 // indirect diff --git a/tests/cmd/seed/go.sum b/tests/cmd/seed/go.sum index dbe18f65f4f..ba91f912de8 100644 --- a/tests/cmd/seed/go.sum +++ b/tests/cmd/seed/go.sum @@ -197,8 +197,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/molecule-man/go-brrr v1.0.1 h1:cEjgx8hgNw6UGdhQ94SPDbPkKuRbkUcxBO3IzbGpA/o= diff --git a/tests/cmd/seedvks/go.mod b/tests/cmd/seedvks/go.mod index 07c59a05698..932006e6c75 100644 --- a/tests/cmd/seedvks/go.mod +++ b/tests/cmd/seedvks/go.mod @@ -64,7 +64,7 @@ require ( github.com/mailru/easyjson v0.9.1 // indirect github.com/mark3labs/mcp-go v0.43.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/molecule-man/go-brrr v1.0.1 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/rs/zerolog v1.34.0 // indirect diff --git a/tests/cmd/seedvks/go.sum b/tests/cmd/seedvks/go.sum index bdf495f5c70..f2d65073184 100644 --- a/tests/cmd/seedvks/go.sum +++ b/tests/cmd/seedvks/go.sum @@ -121,8 +121,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/molecule-man/go-brrr v1.0.1 h1:cEjgx8hgNw6UGdhQ94SPDbPkKuRbkUcxBO3IzbGpA/o= diff --git a/tests/e2e/api/collections/provider-harness.json b/tests/e2e/api/collections/provider-harness.json index fc59c842e7e..7701ad0e575 100644 --- a/tests/e2e/api/collections/provider-harness.json +++ b/tests/e2e/api/collections/provider-harness.json @@ -147736,6 +147736,280 @@ } ] }, + { + "name": "80. Codex functions namespace unwrap and reserved namespace names on Mantle (#7048 / PR #7084)", + "description": "Regression coverage for PR #7084 (follows #7082 and #7039). Codex CLI >= 0.147 wraps its default function and custom tools in a namespace literally named `functions` on every request (openai/codex#37022). Bedrock Mantle reserves that name and rejects the whole request with \"Invalid Value: 'tools.namespace'. User-defined namespace 'functions' collides with an existing tool namespace.\" Core now unwraps that namespace to top-level tools, unprefixed, for every provider before dispatch: Codex treats the bare name and the explicit `functions` namespace as the same tool, so nothing needs mapping back, and on flattening wires the members must not acquire a `functions__` prefix. The same PR moved the reserved-namespace list (`web`, `image_gen`, `browser`, `python` on bedrock and bedrock_mantle) behind the datasheet row `reserved_tool_namespaces`, with the hardcoded list as fallback; the web row pins that fallback still drops Codex's client-side `web` namespace on Mantle and substitutes the hosted web_search tool, which folder 33 never exercised. The bedrock/ row pins the legacy in-provider Mantle route.", + "item": [ + { + "name": "bedrock_mantle/openai.gpt-5.6-sol /v1/responses Codex 0.147 functions namespace is unwrapped to top-level tools (raw_request) - #7048", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if ([401, 403, 429, 500, 502, 503, 504].indexOf(pm.response.code) !== -1) { return; }\n// 400 is deliberately NOT skipped: \"collides with an existing tool namespace\" is the failure signature.", + "pm.test('bedrock_mantle gpt-5.6-sol: explicit functions namespace is accepted - #7048', function () {", + " var text = pm.response.text() || '';", + " pm.expect(text, 'pre-fix reserved-namespace 400 resurfaced').to.not.include('collides with an existing tool namespace');", + " pm.expect(pm.response.code, 'request failed: ' + text.slice(0, 600)).to.be.below(400);", + "});", + "if (pm.response.code >= 400) { return; }", + "var body = pm.response.json() || {};", + "var ef = body.extra_fields || {};", + "var rr = ef.raw_request;", + "if (typeof rr === 'string') { try { rr = JSON.parse(rr); } catch (e) { rr = null; } }", + "var rrText = rr ? JSON.stringify(rr) : '';", + "pm.test('bedrock_mantle gpt-5.6-sol: raw_request captured', function () {", + " pm.expect(rr, 'raw_request missing - x-bf-send-back-raw-request not honored').to.be.an('object');", + "});", + "if (!rr || typeof rr !== 'object') { return; }", + "pm.test('bedrock_mantle gpt-5.6-sol: functions members hoisted to top level, unprefixed - #7048', function () {", + " pm.expect(rrText, 'the functions namespace container reached the provider').to.not.match(/\"name\"\\s*:\\s*\"functions\"/);", + " pm.expect(rrText, 'the member must keep its bare name').to.include('\"wait\"');", + " pm.expect(rrText, 'the default namespace must never become a prefix').to.not.include('functions__wait');", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-bf-send-back-raw-request", + "value": "true" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"model\": \"bedrock_mantle/openai.gpt-5.6-sol\",\n \"input\": \"Reply with the single word OK.\",\n \"max_output_tokens\": 32,\n \"tools\": [\n {\n \"type\": \"namespace\",\n \"name\": \"functions\",\n \"description\": \"\",\n \"tools\": [\n {\n \"type\": \"function\",\n \"name\": \"wait\",\n \"description\": \"Wait for a background task to finish.\",\n \"strict\": false,\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {},\n \"additionalProperties\": false\n }\n }\n ]\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/v1/responses", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "responses" + ] + } + }, + "response": [] + }, + { + "name": "bedrock/openai.gpt-5.6-sol /v1/responses Codex 0.147 functions namespace is unwrapped on the legacy Mantle route (raw_request) - #7048", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if ([401, 403, 429, 500, 502, 503, 504].indexOf(pm.response.code) !== -1) { return; }\n// 400 is deliberately NOT skipped: \"collides with an existing tool namespace\" is the failure signature.", + "pm.test('bedrock legacy Mantle route gpt-5.6-sol: explicit functions namespace is accepted - #7048', function () {", + " var text = pm.response.text() || '';", + " pm.expect(text, 'pre-fix reserved-namespace 400 resurfaced').to.not.include('collides with an existing tool namespace');", + " pm.expect(pm.response.code, 'request failed: ' + text.slice(0, 600)).to.be.below(400);", + "});", + "if (pm.response.code >= 400) { return; }", + "var body = pm.response.json() || {};", + "var ef = body.extra_fields || {};", + "var rr = ef.raw_request;", + "if (typeof rr === 'string') { try { rr = JSON.parse(rr); } catch (e) { rr = null; } }", + "var rrText = rr ? JSON.stringify(rr) : '';", + "pm.test('bedrock legacy Mantle route gpt-5.6-sol: raw_request captured', function () {", + " pm.expect(rr, 'raw_request missing - x-bf-send-back-raw-request not honored').to.be.an('object');", + "});", + "if (!rr || typeof rr !== 'object') { return; }", + "pm.test('bedrock legacy Mantle route gpt-5.6-sol: functions members hoisted to top level, unprefixed - #7048', function () {", + " pm.expect(rrText, 'the functions namespace container reached the provider').to.not.match(/\"name\"\\s*:\\s*\"functions\"/);", + " pm.expect(rrText, 'the member must keep its bare name').to.include('\"wait\"');", + " pm.expect(rrText, 'the default namespace must never become a prefix').to.not.include('functions__wait');", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-bf-send-back-raw-request", + "value": "true" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"model\": \"bedrock/openai.gpt-5.6-sol\",\n \"input\": \"Reply with the single word OK.\",\n \"max_output_tokens\": 32,\n \"tools\": [\n {\n \"type\": \"namespace\",\n \"name\": \"functions\",\n \"description\": \"\",\n \"tools\": [\n {\n \"type\": \"function\",\n \"name\": \"wait\",\n \"description\": \"Wait for a background task to finish.\",\n \"strict\": false,\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {},\n \"additionalProperties\": false\n }\n }\n ]\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/v1/responses", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "responses" + ] + } + }, + "response": [] + }, + { + "name": "anthropic/claude-haiku-4-5 /v1/responses Codex 0.147 functions namespace members stay unprefixed on a flattening wire (raw_request) - #7048", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if ([401, 403, 429, 500, 502, 503, 504].indexOf(pm.response.code) !== -1) { return; }\n// 400 is deliberately NOT skipped: \"collides with an existing tool namespace\" is the failure signature.", + "pm.test('anthropic haiku-4-5: explicit functions namespace is accepted - #7048', function () {", + " var text = pm.response.text() || '';", + " pm.expect(text, 'pre-fix reserved-namespace 400 resurfaced').to.not.include('collides with an existing tool namespace');", + " pm.expect(pm.response.code, 'request failed: ' + text.slice(0, 600)).to.be.below(400);", + "});", + "if (pm.response.code >= 400) { return; }", + "var body = pm.response.json() || {};", + "var ef = body.extra_fields || {};", + "var rr = ef.raw_request;", + "if (typeof rr === 'string') { try { rr = JSON.parse(rr); } catch (e) { rr = null; } }", + "var rrText = rr ? JSON.stringify(rr) : '';", + "pm.test('anthropic haiku-4-5: raw_request captured', function () {", + " pm.expect(rr, 'raw_request missing - x-bf-send-back-raw-request not honored').to.be.an('object');", + "});", + "if (!rr || typeof rr !== 'object') { return; }", + "pm.test('anthropic haiku-4-5: functions members hoisted to top level, unprefixed - #7048', function () {", + " pm.expect(rrText, 'the functions namespace container reached the provider').to.not.match(/\"name\"\\s*:\\s*\"functions\"/);", + " pm.expect(rrText, 'the member must keep its bare name').to.include('\"wait\"');", + " pm.expect(rrText, 'the default namespace must never become a prefix').to.not.include('functions__wait');", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-bf-send-back-raw-request", + "value": "true" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"model\": \"anthropic/claude-haiku-4-5\",\n \"input\": \"Reply with the single word OK.\",\n \"max_output_tokens\": 32,\n \"tools\": [\n {\n \"type\": \"namespace\",\n \"name\": \"functions\",\n \"description\": \"\",\n \"tools\": [\n {\n \"type\": \"function\",\n \"name\": \"wait\",\n \"description\": \"Wait for a background task to finish.\",\n \"strict\": false,\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {},\n \"additionalProperties\": false\n }\n }\n ]\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/v1/responses", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "responses" + ] + } + }, + "response": [] + }, + { + "name": "bedrock_mantle/openai.gpt-5.6-sol /v1/responses reserved web namespace dropped and hosted web_search substituted (raw_request) - #7039", + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "if ([401, 403, 429, 500, 502, 503, 504].indexOf(pm.response.code) !== -1) { return; }\n// 400 is deliberately NOT skipped: \"collides with an existing tool namespace\" is the failure signature.", + "pm.test('bedrock_mantle gpt-5.6-sol: reserved web namespace does not reach Mantle - #7039', function () {", + " var text = pm.response.text() || '';", + " pm.expect(text, 'reserved-namespace 400 resurfaced').to.not.include('collides with an existing tool namespace');", + " pm.expect(pm.response.code, 'request failed: ' + text.slice(0, 600)).to.be.below(400);", + "});", + "if (pm.response.code >= 400) { return; }", + "var body = pm.response.json() || {};", + "var ef = body.extra_fields || {};", + "var rr = ef.raw_request;", + "if (typeof rr === 'string') { try { rr = JSON.parse(rr); } catch (e) { rr = null; } }", + "var rrText = rr ? JSON.stringify(rr) : '';", + "pm.test('bedrock_mantle gpt-5.6-sol: raw_request captured', function () {", + " pm.expect(rr, 'raw_request missing - x-bf-send-back-raw-request not honored').to.be.an('object');", + "});", + "if (!rr || typeof rr !== 'object') { return; }", + "pm.test('bedrock_mantle gpt-5.6-sol: web namespace dropped and hosted web_search substituted - #7039', function () {", + " pm.expect(rrText, 'the reserved web namespace reached the provider').to.not.match(/\"name\"\\s*:\\s*\"web\"/);", + " pm.expect(rrText, 'expected the hosted web_search tool in place of the dropped namespace').to.include('\"type\":\"web_search\"');", + " pm.expect(rrText, 'substituted web_search must carry external_web_access=false').to.include('\"external_web_access\":false');", + "});" + ] + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "x-bf-send-back-raw-request", + "value": "true" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"model\": \"bedrock_mantle/openai.gpt-5.6-sol\",\n \"input\": \"Reply with the single word OK.\",\n \"max_output_tokens\": 32,\n \"tools\": [\n {\n \"type\": \"namespace\",\n \"name\": \"web\",\n \"tools\": [\n {\n \"type\": \"function\",\n \"name\": \"run\",\n \"description\": \"Search the web.\",\n \"strict\": false,\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {},\n \"additionalProperties\": false\n }\n }\n ]\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/v1/responses", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "v1", + "responses" + ] + } + }, + "response": [] + } + ] + }, { "name": "82. Flattened namespace tool names honour each wire tool-name limit (namespace-alias-length)", "description": "Companion to folder 79 (#7048). Flattening a Responses `namespace` tool renames each nested function to `__`. Each wire caps tool names by its own documented contract: OpenAI-compatible wires, Bedrock Converse and Fireworks at 64 characters of [A-Za-z0-9_-]; Anthropic at 128; Gemini and Vertex at 128 with '.' and ':' also allowed; providers that document no limit take the OpenAI-compatible 64. The pre-fix alias had no cap, so a Codex MCP namespace plus a tool name could exceed the wire limit and the provider answered 400 on a name the caller never sent. The alias is now capped per base provider (datasheet row tool_name_max_length overrides), deterministically: plain when it fits, otherwise an 8-hex hash prefix plus the function name. Both rows send the same 64-char namespace with two functions (a 98-char plain alias) and pin the outbound names through raw_request: on Anthropic the alias stays plain under 128; on DeepSeek, an Anthropic-wire provider with no documented limit, it takes the hashed form under 64. Run: make run-provider-harness-test PROVIDER= FEATURE=\"namespace-alias-length\" PARALLEL=0", diff --git a/transports/go.mod b/transports/go.mod index ad5d668abd2..7b3f6c5f279 100644 --- a/transports/go.mod +++ b/transports/go.mod @@ -152,7 +152,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/mailru/easyjson v0.9.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/maximhq/bifrost/plugins/mocker v1.6.2 // indirect github.com/maximhq/maxim-go v0.2.1 // indirect diff --git a/transports/go.sum b/transports/go.sum index e5a746aa30f..55e0b555fe8 100644 --- a/transports/go.sum +++ b/transports/go.sum @@ -313,8 +313,7 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/maximhq/bifrost/core v1.8.6 h1:T8Ob2qdC+bmDdSIi8GBBeZzPjKcf7K9/zjgqQWvlxTo=