intercept and sanitize gemini schema
This commit is contained in:
+7
-1
@@ -173,8 +173,9 @@ export const gemini = agent({
|
|||||||
args: [cliPath, "--yolo", "--output-format=stream-json", "-p", sessionPrompt],
|
args: [cliPath, "--yolo", "--output-format=stream-json", "-p", sessionPrompt],
|
||||||
env: createAgentEnv({
|
env: createAgentEnv({
|
||||||
GEMINI_API_KEY: apiKey,
|
GEMINI_API_KEY: apiKey,
|
||||||
|
GEMINI_CLI_DISABLE_SCHEMA_VALIDATION:
|
||||||
|
process.env.GEMINI_CLI_DISABLE_SCHEMA_VALIDATION || "1",
|
||||||
}),
|
}),
|
||||||
timeout: 600000, // 10 minutes
|
|
||||||
onStdout: async (chunk) => {
|
onStdout: async (chunk) => {
|
||||||
const text = chunk.toString();
|
const text = chunk.toString();
|
||||||
finalOutput += text;
|
finalOutput += text;
|
||||||
@@ -257,6 +258,11 @@ function configureGeminiMcpServers({ mcpServers, cliPath }: ConfigureMcpServersP
|
|||||||
const addResult = spawnSync("node", [cliPath, ...addArgs], {
|
const addResult = spawnSync("node", [cliPath, ...addArgs], {
|
||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
GEMINI_CLI_DISABLE_SCHEMA_VALIDATION:
|
||||||
|
process.env.GEMINI_CLI_DISABLE_SCHEMA_VALIDATION || "1",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (addResult.status !== 0) {
|
if (addResult.status !== 0) {
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
summarize https://github.com/pullfrogai/scratch/issues/56, its status, and pertinent discussion on the issue
|
Use the debug_shell_command MCP tool to run git status and show me the output.
|
||||||
@@ -279,6 +279,7 @@ async function startMcpServer(ctx: MainContext): Promise<void> {
|
|||||||
process.env.GITHUB_REPOSITORY = githubRepository;
|
process.env.GITHUB_REPOSITORY = githubRepository;
|
||||||
process.env.PULLFROG_MODES = JSON.stringify(allModes);
|
process.env.PULLFROG_MODES = JSON.stringify(allModes);
|
||||||
process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload);
|
process.env.PULLFROG_PAYLOAD = JSON.stringify(ctx.payload);
|
||||||
|
process.env.PULLFROG_AGENT = ctx.agentName;
|
||||||
|
|
||||||
// GITHUB_RUN_ID is already set in GitHub Actions, no need to set it here
|
// GITHUB_RUN_ID is already set in GitHub Actions, no need to set it here
|
||||||
|
|
||||||
|
|||||||
+83
-1
@@ -44,9 +44,91 @@ export interface McpContext extends RepoContext {
|
|||||||
|
|
||||||
export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => toolDef;
|
export const tool = <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => toolDef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize JSON schema to remove problematic fields that Gemini CLI can't handle
|
||||||
|
* - Removes $schema field (causes "no schema with key or ref" errors)
|
||||||
|
* - Converts $defs to definitions (draft-07 compatibility)
|
||||||
|
* - Removes any draft-2020-12 specific features
|
||||||
|
*/
|
||||||
|
function sanitizeSchema(schema: any): any {
|
||||||
|
if (!schema || typeof schema !== "object") {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(schema)) {
|
||||||
|
return schema.map(sanitizeSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitized: any = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(schema)) {
|
||||||
|
// skip $schema field entirely
|
||||||
|
if (key === "$schema") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert $defs to definitions for draft-07 compatibility
|
||||||
|
if (key === "$defs") {
|
||||||
|
sanitized.definitions = sanitizeSchema(value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively sanitize nested objects
|
||||||
|
sanitized[key] = sanitizeSchema(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a StandardSchemaV1 to intercept toJsonSchema() calls and sanitize the output
|
||||||
|
*/
|
||||||
|
function wrapSchema(schema: StandardSchemaV1<any>): StandardSchemaV1<any> {
|
||||||
|
const originalToJsonSchema = (schema as any).toJsonSchema?.bind(schema);
|
||||||
|
|
||||||
|
if (!originalToJsonSchema) {
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a proxy that intercepts toJsonSchema calls
|
||||||
|
return new Proxy(schema, {
|
||||||
|
get(target, prop) {
|
||||||
|
if (prop === "toJsonSchema") {
|
||||||
|
return () => {
|
||||||
|
const originalSchema = originalToJsonSchema();
|
||||||
|
return sanitizeSchema(originalSchema);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (target as any)[prop];
|
||||||
|
},
|
||||||
|
}) as StandardSchemaV1<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform tool to sanitize its parameter schema for Gemini CLI compatibility
|
||||||
|
*/
|
||||||
|
function sanitizeTool<T extends Tool<any, any>>(tool: T): T {
|
||||||
|
if (!tool.parameters) {
|
||||||
|
return tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap the schema object to intercept toJsonSchema() calls
|
||||||
|
const wrappedSchema = wrapSchema(tool.parameters);
|
||||||
|
|
||||||
|
// create a new tool with wrapped schema
|
||||||
|
return {
|
||||||
|
...tool,
|
||||||
|
parameters: wrappedSchema,
|
||||||
|
} as T;
|
||||||
|
}
|
||||||
|
|
||||||
export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
|
export const addTools = (server: FastMCP, tools: Tool<any, any>[]) => {
|
||||||
|
// only sanitize schemas for gemini agent (it has issues with draft-2020-12 schemas)
|
||||||
|
const shouldSanitize = process.env.PULLFROG_AGENT === "gemini";
|
||||||
|
|
||||||
for (const tool of tools) {
|
for (const tool of tools) {
|
||||||
server.addTool(tool);
|
const processedTool = shouldSanitize ? sanitizeTool(tool) : tool;
|
||||||
|
server.addTool(processedTool);
|
||||||
}
|
}
|
||||||
return server;
|
return server;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user