c608051b79
Gemini's generateContent API rejects arktype's `{anyOf:[{enum:[...]}]}` string-enum
encoding, `$schema` metadata, and `anyOf` with sibling fields. Port the old
sanitizer back as an isolated module (action/mcp/geminiSanitizer.ts) and gate it
on `isGeminiRouted(ctx)` so non-gemini routes see the original schema. Wires
`resolvedModel` onto ToolContext so the sanitizer can see the upstream specifier.
Also bumps `openai/gpt-codex-mini` alias from the deprecated `codex-mini-latest`
to `gpt-5.1-codex-mini`, matching the openrouter resolve.
Adds a `filter` workflow_dispatch input + MATRIX_FILTER env that restricts the
models-live matrix to aliases matching a substring, so we can iterate on a
single provider (e.g. `filter=gemini`) without paying to run every model.
72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
import { encode as toonEncode } from "@toon-format/toon";
|
|
import type { FastMCP, Tool } from "fastmcp";
|
|
import { formatJsonValue, log } from "../utils/cli.ts";
|
|
import { isGeminiRouted, sanitizeToolForGemini } from "./geminiSanitizer.ts";
|
|
import type { ToolContext } from "./server.ts";
|
|
|
|
export const tool = <const params>(
|
|
toolDef: Tool<any, StandardSchemaV1<params>>
|
|
): Tool<any, StandardSchemaV1<params>> => toolDef;
|
|
|
|
export interface ToolResult {
|
|
content: {
|
|
type: "text";
|
|
text: string;
|
|
}[];
|
|
isError?: boolean;
|
|
}
|
|
|
|
export const handleToolSuccess = (data: Record<string, any> | string): ToolResult => {
|
|
const text = typeof data === "string" ? data : toonEncode(data);
|
|
return {
|
|
content: [{ type: "text", text }],
|
|
};
|
|
};
|
|
|
|
export const handleToolError = (error: unknown): ToolResult => {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text: `Error: ${errorMessage}`,
|
|
},
|
|
],
|
|
isError: true,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Helper to wrap a tool execute function with error handling.
|
|
* Captures ctx in closure so tools don't need to handle try/catch.
|
|
* @param fn - the function to execute
|
|
* @param toolName - optional tool name for error logging
|
|
*/
|
|
export const execute = <T, R extends Record<string, any> | string>(
|
|
fn: (params: T) => Promise<R>,
|
|
toolName?: string
|
|
) => {
|
|
const _fn = async (params: T): Promise<ToolResult> => {
|
|
try {
|
|
const result = await fn(params);
|
|
return handleToolSuccess(result);
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
const prefix = toolName ? `[${toolName}]` : "tool";
|
|
log.info(`${prefix} error: ${errorMessage}`);
|
|
log.debug(`${prefix} params: ${formatJsonValue(params)}`);
|
|
return handleToolError(error);
|
|
}
|
|
};
|
|
return _fn;
|
|
};
|
|
|
|
export const addTools = (ctx: ToolContext, server: FastMCP<any>, tools: Tool<any, any>[]) => {
|
|
const shouldSanitize = isGeminiRouted(ctx);
|
|
for (const tool of tools) {
|
|
server.addTool(shouldSanitize ? sanitizeToolForGemini(tool) : tool);
|
|
}
|
|
return server;
|
|
};
|