File size: 6,555 Bytes
e249c6d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | /**
* Applies runtime-plan or provider fallback tool schema policy. The helpers
* normalize tool schemas, preserve owner metadata across cloned definitions,
* and emit provider diagnostics.
*/
import type { TSchema } from "typebox";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { ProviderRuntimePluginHandle } from "../../plugins/provider-hook-runtime.js";
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
import { copyAgentToolMetadata } from "../agent-tool-metadata.js";
import {
logProviderToolSchemaDiagnostics,
normalizeProviderToolSchemas,
} from "../embedded-agent-runner/tool-schema-runtime.js";
import type { AgentTool } from "../runtime/index.js";
import {
filterProviderNormalizableTools,
type RuntimeToolSchemaDiagnostic,
} from "../tool-schema-projection.js";
import type { AnyAgentTool } from "../tools/common.js";
import type { AgentRuntimePlan } from "./types.js";
type AgentRuntimeToolPolicyParams<TSchemaType extends TSchema = TSchema, TResult = unknown> = {
runtimePlan?: AgentRuntimePlan;
tools: AgentTool<TSchemaType, TResult>[];
provider: string;
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
modelId?: string;
modelApi?: string | null;
model?: ProviderRuntimeModel;
runtimeHandle?: ProviderRuntimePluginHandle;
allowProviderRuntimePluginLoad?: boolean;
/**
* Invoked on every normalization, including with an empty list, so
* consumers can observe the all-clear and retire stale quarantine state.
*/
onPreNormalizationSchemaDiagnostics?: (
diagnostics: readonly RuntimeToolSchemaDiagnostic[],
tools: readonly AgentTool<TSchemaType, TResult>[],
) => void;
};
/** Builds the provider/runtime context passed into runtime-plan tool hooks. */
function runtimePlanToolContext(params: {
workspaceDir?: string;
modelApi?: string | null;
model?: ProviderRuntimeModel;
}) {
return {
workspaceDir: params.workspaceDir,
modelApi: params.modelApi ?? undefined,
model: params.model,
};
}
// Normalizers may return cloned tool definitions. Preserve owner and private
// execution metadata so downstream dispatch keeps the same policy and result contract.
function copyRuntimeToolMetadata(source: AgentTool, target: AgentTool): void {
if (source === target) {
return;
}
const { catalogMode, outputSchema, hideFromChannelProgress } = source as AnyAgentTool;
Object.assign(target, {
...(catalogMode ? { catalogMode } : {}),
...(outputSchema !== undefined ? { outputSchema } : {}),
...(hideFromChannelProgress === true ? { hideFromChannelProgress } : {}),
});
copyAgentToolMetadata(source as never, target as never);
}
// Duplicate names cannot be matched by map lookup alone, so same-index matches
// take precedence and unique-name fallback covers cloned arrays.
function preserveRuntimeToolMetadata<TSchemaType extends TSchema = TSchema, TResult = unknown>(
sourceTools: AgentTool<TSchemaType, TResult>[],
normalizedTools: AgentTool<TSchemaType, TResult>[],
): AgentTool<TSchemaType, TResult>[] {
const sourcesByUniqueName = new Map<string, AgentTool<TSchemaType, TResult>>();
const duplicateNames = new Set<string>();
for (const source of sourceTools) {
const name = source.name;
if (sourcesByUniqueName.has(name)) {
duplicateNames.add(name);
sourcesByUniqueName.delete(name);
continue;
}
if (!duplicateNames.has(name)) {
sourcesByUniqueName.set(name, source);
}
}
for (const [index, target] of normalizedTools.entries()) {
const indexedSource = sourceTools[index];
const source =
indexedSource?.name === target.name ? indexedSource : sourcesByUniqueName.get(target.name);
if (source) {
copyRuntimeToolMetadata(source, target);
}
}
return normalizedTools;
}
/** Normalizes tool schemas through a runtime plan or provider fallback policy. */
export function normalizeAgentRuntimeTools<
TSchemaType extends TSchema = TSchema,
TResult = unknown,
>(
params: Omit<AgentRuntimeToolPolicyParams<TSchemaType, TResult>, "tools"> & {
tools: readonly AgentTool<TSchemaType, TResult>[];
},
): AgentTool<TSchemaType, TResult>[] {
const planContext = runtimePlanToolContext(params);
const normalizableToolProjection = filterProviderNormalizableTools(params.tools);
params.onPreNormalizationSchemaDiagnostics?.(
normalizableToolProjection.diagnostics,
params.tools,
);
// Projection owns this fresh array, so normalizers can mutate it without changing raw input.
const normalizableTools = normalizableToolProjection.tools;
const planNormalized = params.runtimePlan?.tools.normalize(normalizableTools, planContext);
// Empty fallback input cannot gain provider-specific schema changes. Avoid loading a provider
// runtime just to return the same empty list; runtime plans still receive their normal callback.
const normalized =
planNormalized ??
(normalizableTools.length === 0
? normalizableTools
: normalizeProviderToolSchemas({
tools: normalizableTools,
provider: params.provider,
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env ?? process.env,
modelId: params.modelId,
modelApi: params.modelApi,
model: params.model,
runtimeHandle: params.runtimeHandle,
allowRuntimePluginLoad: params.allowProviderRuntimePluginLoad,
}));
// Provider collection views wrap the results of map, including host-owned tool wrappers.
// Own the assembly array so later wrapping keeps its metadata; retain fenced tool elements.
const normalizedTools = Array.isArray(normalized) ? Array.from(normalized) : normalizableTools;
return preserveRuntimeToolMetadata(normalizableTools, normalizedTools);
}
/** Emits runtime-plan or provider fallback diagnostics for normalized tools. */
export function logAgentRuntimeToolDiagnostics(params: AgentRuntimeToolPolicyParams): void {
const planContext = runtimePlanToolContext(params);
if (params.runtimePlan) {
params.runtimePlan.tools.logDiagnostics(params.tools, planContext);
return;
}
logProviderToolSchemaDiagnostics({
tools: params.tools,
provider: params.provider,
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env ?? process.env,
modelId: params.modelId,
modelApi: params.modelApi,
model: params.model,
runtimeHandle: params.runtimeHandle,
});
}
|