mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix(agents): avoid xAI web_search tool-name collisions
This commit is contained in:
@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Models/openai-completions streaming compatibility: force `compat.supportsUsageInStreaming=false` for non-native OpenAI-compatible endpoints during model normalization, preventing usage-only stream chunks from triggering `choices[0]` parser crashes in provider streams. (#8714) Thanks @nonanon1.
|
||||
- Tools/xAI native web-search collision guard: drop OpenClaw `web_search` from tool registration when routing to xAI/Grok model providers (including OpenRouter `x-ai/*`) to avoid duplicate tool-name request failures against provider-native `web_search`. (#14749) Thanks @realsamrat.
|
||||
- TUI/token copy-safety rendering: treat long credential-like mixed alphanumeric tokens (including quoted forms) as copy-sensitive in render sanitization so formatter hard-wrap guards no longer inject visible spaces into auth-style values before display. (#26710) Thanks @jasonthane.
|
||||
- WhatsApp/self-chat response prefix fallback: stop forcing `"[openclaw]"` as the implicit outbound response prefix when no identity name or response prefix is configured, so blank/default prefix settings no longer inject branding text unexpectedly in self-chat flows. (#27962) Thanks @ecanmor.
|
||||
- Memory/QMD search result decoding: accept `qmd search` hits that only include `file` URIs (for example `qmd://collection/path.md`) without `docid`, resolve them through managed collection roots, and keep multi-collection results keyed by file fallback so valid QMD hits no longer collapse to empty `memory_search` output. (#28181) Thanks @0x76696265.
|
||||
|
||||
42
src/agents/pi-tools.model-provider-collision.test.ts
Normal file
42
src/agents/pi-tools.model-provider-collision.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { __testing } from "./pi-tools.js";
|
||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||
|
||||
const baseTools = [
|
||||
{ name: "read" },
|
||||
{ name: "web_search" },
|
||||
{ name: "exec" },
|
||||
] as unknown as AnyAgentTool[];
|
||||
|
||||
function toolNames(tools: AnyAgentTool[]): string[] {
|
||||
return tools.map((tool) => tool.name);
|
||||
}
|
||||
|
||||
describe("applyModelProviderToolPolicy", () => {
|
||||
it("keeps web_search for non-xAI models", () => {
|
||||
const filtered = __testing.applyModelProviderToolPolicy(baseTools, {
|
||||
modelProvider: "openai",
|
||||
modelId: "gpt-4o-mini",
|
||||
});
|
||||
|
||||
expect(toolNames(filtered)).toEqual(["read", "web_search", "exec"]);
|
||||
});
|
||||
|
||||
it("removes web_search for OpenRouter xAI model ids", () => {
|
||||
const filtered = __testing.applyModelProviderToolPolicy(baseTools, {
|
||||
modelProvider: "openrouter",
|
||||
modelId: "x-ai/grok-4.1-fast",
|
||||
});
|
||||
|
||||
expect(toolNames(filtered)).toEqual(["read", "exec"]);
|
||||
});
|
||||
|
||||
it("removes web_search for direct xAI providers", () => {
|
||||
const filtered = __testing.applyModelProviderToolPolicy(baseTools, {
|
||||
modelProvider: "x-ai",
|
||||
modelId: "grok-4.1",
|
||||
});
|
||||
|
||||
expect(toolNames(filtered)).toEqual(["read", "exec"]);
|
||||
});
|
||||
});
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
import { cleanToolSchemaForGemini, normalizeToolParameters } from "./pi-tools.schema.js";
|
||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||
import type { SandboxContext } from "./sandbox.js";
|
||||
import { isXaiProvider } from "./schema/clean-for-xai.js";
|
||||
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
||||
import { createToolFsPolicy, resolveToolFsConfig } from "./tool-fs-policy.js";
|
||||
import {
|
||||
@@ -65,6 +66,7 @@ function isOpenAIProvider(provider?: string) {
|
||||
const TOOL_DENY_BY_MESSAGE_PROVIDER: Readonly<Record<string, readonly string[]>> = {
|
||||
voice: ["tts"],
|
||||
};
|
||||
const TOOL_DENY_FOR_XAI_PROVIDERS = new Set(["web_search"]);
|
||||
|
||||
function normalizeMessageProvider(messageProvider?: string): string | undefined {
|
||||
const normalized = messageProvider?.trim().toLowerCase();
|
||||
@@ -87,6 +89,18 @@ function applyMessageProviderToolPolicy(
|
||||
return tools.filter((tool) => !deniedSet.has(tool.name));
|
||||
}
|
||||
|
||||
function applyModelProviderToolPolicy(
|
||||
tools: AnyAgentTool[],
|
||||
params?: { modelProvider?: string; modelId?: string },
|
||||
): AnyAgentTool[] {
|
||||
if (!isXaiProvider(params?.modelProvider, params?.modelId)) {
|
||||
return tools;
|
||||
}
|
||||
// xAI/Grok providers expose a native web_search tool; sending OpenClaw's
|
||||
// web_search alongside it causes duplicate-name request failures.
|
||||
return tools.filter((tool) => !TOOL_DENY_FOR_XAI_PROVIDERS.has(tool.name));
|
||||
}
|
||||
|
||||
function isApplyPatchAllowedForModel(params: {
|
||||
modelProvider?: string;
|
||||
modelId?: string;
|
||||
@@ -177,6 +191,7 @@ export const __testing = {
|
||||
patchToolSchemaForClaudeCompatibility,
|
||||
wrapToolParamNormalization,
|
||||
assertRequiredParams,
|
||||
applyModelProviderToolPolicy,
|
||||
} as const;
|
||||
|
||||
export function createOpenClawCodingTools(options?: {
|
||||
@@ -501,9 +516,13 @@ export function createOpenClawCodingTools(options?: {
|
||||
}),
|
||||
];
|
||||
const toolsForMessageProvider = applyMessageProviderToolPolicy(tools, options?.messageProvider);
|
||||
const toolsForModelProvider = applyModelProviderToolPolicy(toolsForMessageProvider, {
|
||||
modelProvider: options?.modelProvider,
|
||||
modelId: options?.modelId,
|
||||
});
|
||||
// Security: treat unknown/undefined as unauthorized (opt-in, not opt-out)
|
||||
const senderIsOwner = options?.senderIsOwner === true;
|
||||
const toolsByAuthorization = applyOwnerOnlyToolPolicy(toolsForMessageProvider, senderIsOwner);
|
||||
const toolsByAuthorization = applyOwnerOnlyToolPolicy(toolsForModelProvider, senderIsOwner);
|
||||
const subagentFiltered = applyToolPolicyPipeline({
|
||||
tools: toolsByAuthorization,
|
||||
toolMeta: (tool) => getPluginToolMeta(tool),
|
||||
|
||||
Reference in New Issue
Block a user