docs: document agent tool adapters

This commit is contained in:
Peter Steinberger
2026-06-04 05:41:50 -04:00
parent 634174f050
commit 60e0d2a7b9
14 changed files with 85 additions and 10 deletions
@@ -1,3 +1,8 @@
/**
* Regression tests for adapter interaction with after_tool_call hooks.
* Ensures embedded run subscription handling remains the single after-hook
* execution path.
*/
import type { AgentTool } from "openclaw/plugin-sdk/agent-core";
import { Type } from "typebox";
import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -1,3 +1,8 @@
/**
* Logging tests for tool adapter failures.
* Verifies retryable parameter errors expose useful context while intentional
* hook blocks and exec secrets stay out of raw logs.
*/
import type { AgentTool } from "openclaw/plugin-sdk/agent-core";
import { Type } from "typebox";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
@@ -1,3 +1,8 @@
/**
* Unit coverage for adapting runtime and client-hosted tools.
* Exercises result coercion, error wrapping, client delegation, and conflict
* detection at the ToolDefinition boundary.
*/
import type { AgentTool } from "openclaw/plugin-sdk/agent-core";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
+10 -2
View File
@@ -1,3 +1,8 @@
/**
* Adapts runtime AgentTool objects into session ToolDefinition entries.
* Owns hook execution, client-tool delegation, result coercion, and safe
* logging for failed tool calls.
*/
import { createHash } from "node:crypto";
import { logDebug, logError } from "../logger.js";
import { redactToolDetail } from "../logging/redact.js";
@@ -304,6 +309,7 @@ function finalizeToolParamsBeforeExecute(params: {
export const CLIENT_TOOL_NAME_CONFLICT_PREFIX = "client tool name conflict:";
/** Find client-hosted tool names that collide with runtime or sibling tools. */
export function findClientToolNameConflicts(params: {
tools: ClientToolDefinition[];
existingToolNames?: Iterable<string>;
@@ -338,14 +344,17 @@ export function findClientToolNameConflicts(params: {
return Array.from(conflicts);
}
/** Build a recognizable error for rejecting conflicting client tool names. */
export function createClientToolNameConflictError(conflicts: string[]): Error {
return new Error(`${CLIENT_TOOL_NAME_CONFLICT_PREFIX} ${conflicts.join(", ")}`);
}
/** Detect client tool conflict errors without depending on object identity. */
export function isClientToolNameConflictError(err: unknown): err is Error {
return err instanceof Error && err.message.startsWith(CLIENT_TOOL_NAME_CONFLICT_PREFIX);
}
/** Convert executable agent tools into session definitions with hook handling. */
export function toToolDefinitions(
tools: AnyAgentTool[],
hookContext?: HookContext,
@@ -477,8 +486,7 @@ function coerceParamsRecord(value: unknown): Record<string, unknown> {
return {};
}
// Convert client tools (OpenResponses hosted tools) to ToolDefinition format
// These tools are intercepted to return a "pending" result instead of executing
/** Convert client-hosted tools into pending session definitions. */
export function toClientToolDefinitions(
tools: ClientToolDefinition[],
onClientToolCall?: ClientToolCallRecorder,
@@ -1,6 +1,10 @@
/**
* Fixtures for embedded agent tool-handler state tests.
* Keeps large mutable handler state construction centralized so assertions can
* focus on the field under test.
*/
import { createEmbeddedRunReplayState } from "./embedded-agent-runner/replay-state.js";
// Shared fixture for tests that exercise embedded agent tool handler state.
/** Build the minimal mutable state object expected by tool handler tests. */
export function createBaseToolHandlerState() {
return {
@@ -1,3 +1,8 @@
/**
* Normalizes model-facing tool parameter schemas across provider quirks.
* Handles local JSON Schema refs, OpenAPI nullable syntax, top-level unions,
* and provider-specific unsupported keyword stripping.
*/
import { isRecord as isSchemaRecord } from "@openclaw/normalization-core/record-coerce";
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
import { uniqueValues } from "@openclaw/normalization-core/string-normalization";
@@ -589,6 +594,7 @@ function inlineLocalSchemaRefsWithDefs(
return result;
}
/** Inline local $ref pointers so providers receive self-contained tool schemas. */
export function inlineLocalToolSchemaRefs(schema: unknown): TSchema {
if (!schema || typeof schema !== "object") {
return schema as TSchema;
@@ -881,6 +887,7 @@ function normalizeToolParameterSchemaUncached(
return applyProviderCleaning(flattenedSchema);
}
/** Return a provider-compatible JSON schema for a model-facing tool. */
export function normalizeToolParameterSchema(
schema: unknown,
options?: ToolParameterSchemaOptions,
@@ -1,3 +1,7 @@
/**
* Tests cron-aware deferred follow-up guidance in exec/process descriptions.
* Protects the model-facing text selected after tool filtering.
*/
import { describe, expect, it } from "vitest";
import { applyDeferredFollowupToolDescriptions } from "./agent-tools.deferred-followup.js";
import type { AnyAgentTool } from "./agent-tools.types.js";
+5 -2
View File
@@ -1,8 +1,11 @@
/**
* Adjusts exec/process tool descriptions for long-running follow-up behavior.
* Cron-aware runs can point models at scheduled follow-ups; cronless runs keep
* guidance constrained to process polling and wake handling.
*/
import type { AnyAgentTool } from "./agent-tools.types.js";
import { describeExecTool, describeProcessTool } from "./bash-tools.descriptions.js";
// Updates exec/process tool descriptions with deferred-followup guidance based
// on the tools available in the current run.
/** Return tools with exec/process descriptions adjusted for cron availability. */
export function applyDeferredFollowupToolDescriptions(
tools: AnyAgentTool[],
@@ -1,3 +1,8 @@
/**
* Tests message-provider tool filtering.
* Voice-like transports should not expose text-to-speech when that surface is
* unsafe or redundant for the active channel.
*/
import { describe, expect, it } from "vitest";
import { filterToolNamesByMessageProvider } from "./agent-tools.message-provider-policy.js";
@@ -1,8 +1,10 @@
/**
* Message-provider tool filtering.
* Channels can restrict tool names after runtime assembly when the active
* transport cannot safely render or execute a class of tools.
*/
import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
// Message providers can narrow the tool surface when a channel cannot safely
// render or execute a tool class. The policy is name-based because channel
// delivery happens after tools are already assembled.
const TOOL_DENY_BY_MESSAGE_PROVIDER: Readonly<Record<string, readonly string[]>> = {
"discord-voice": ["tts"],
voice: ["tts"],
+4
View File
@@ -1,3 +1,7 @@
/**
* Tests required parameter validation for model-facing tools.
* Covers retry guidance and path-only XML suffix cleanup for file operations.
*/
import { describe, expect, it, vi } from "vitest";
import {
assertRequiredParams,
+7 -2
View File
@@ -1,7 +1,10 @@
/**
* Shared validation for model-supplied tool parameters.
* Converts malformed file-tool arguments into retryable errors and fixes the
* specific XML suffix corruption seen in path arguments.
*/
import type { AnyAgentTool } from "./agent-tools.types.js";
// Shared tool parameter validation helpers for file-edit style tools. They turn
// model-facing malformed arguments into actionable retry guidance.
export type RequiredParamGroup = {
keys: readonly string[];
allowEmpty?: boolean;
@@ -84,6 +87,7 @@ function hasValidEditReplacements(record: Record<string, unknown>): boolean {
);
}
/** Required parameter groups for file-style tools that need retry guidance. */
export const REQUIRED_PARAM_GROUPS = {
read: [{ keys: ["path"], label: "path" }],
write: [
@@ -140,6 +144,7 @@ function resolveMalformedXmlArgValuePathKeys(
return [...keys];
}
/** Throw actionable retry guidance when required tool params are missing. */
export function assertRequiredParams(
record: Record<string, unknown> | undefined,
groups: readonly RequiredParamGroup[],
+5
View File
@@ -1,3 +1,8 @@
/**
* Tests layered tool policy resolution.
* Covers wildcard matching, sub-agent inheritance, provider overrides, and
* trusted group context checks.
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
+13
View File
@@ -1,3 +1,8 @@
/**
* Resolves sandbox tool policies for agents, providers, sub-agents, and group
* sessions. Keeps runtime tool filtering tied to canonical config, session
* provenance, and inherited sub-agent capabilities.
*/
import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
import {
normalizeLowercaseStringOrEmpty,
@@ -97,6 +102,7 @@ function mergeConfiguredSubagentAllow(
return allow && alsoAllow ? uniqueStrings([...allow, ...alsoAllow]) : allow;
}
/** Resolve depth-based default deny rules plus configured sub-agent overrides. */
export function resolveSubagentToolPolicy(cfg?: OpenClawConfig, depth?: number): SandboxToolPolicy {
const configured = cfg?.tools?.subagents?.tools;
const maxSpawnDepth =
@@ -116,6 +122,7 @@ export function resolveSubagentToolPolicy(cfg?: OpenClawConfig, depth?: number):
return { allow: mergedAllow, deny };
}
/** Resolve sub-agent tool policy from stored session capabilities. */
export function resolveSubagentToolPolicyForSession(
cfg: OpenClawConfig | undefined,
sessionKey: string,
@@ -147,6 +154,7 @@ export function resolveSubagentToolPolicyForSession(
return { allow: mergedAllow, deny };
}
/** Resolve the tool policy inherited from a parent sub-agent session. */
export function resolveInheritedToolPolicyForSession(
cfg: OpenClawConfig | undefined,
sessionKey: string | undefined | null,
@@ -171,6 +179,7 @@ export function resolveInheritedToolPolicyForSession(
};
}
/** Filter runtime tools by sandbox allow/deny policy. */
export function filterToolsByPolicy(tools: AnyAgentTool[], policy?: SandboxToolPolicy) {
if (!policy) {
return tools;
@@ -339,6 +348,7 @@ function resolveTrustedGroupIdFromContexts(params: {
return { groupId: null, dropped: true };
}
/** Validate caller-supplied group ids against server-derived session context. */
export function resolveTrustedGroupId(params: {
groupId?: string | null;
sessionKey?: string | null;
@@ -354,6 +364,7 @@ export function resolveTrustedGroupId(params: {
});
}
/** Resolve model/provider-scoped tool policy from canonical provider keys. */
export function resolveProviderToolPolicy(params: {
byProvider?: Record<string, ToolPolicyConfig>;
modelProvider?: string;
@@ -435,6 +446,7 @@ function formatToolListForWarning(toolNames: string[]): string {
return toolNames.map((toolName) => `"${toolName}"`).join(", ");
}
/** Resolve the layered global, provider, agent, and profile tool policies. */
export function resolveEffectiveToolPolicy(params: {
config?: OpenClawConfig;
sessionKey?: string;
@@ -523,6 +535,7 @@ export function resolveEffectiveToolPolicy(params: {
};
}
/** Resolve group-scoped tool policy after validating session provenance. */
export function resolveGroupToolPolicy(params: {
config?: OpenClawConfig;
sessionKey?: string;