Merge pull request #599 from mcinteerj/fix/gemini-tool-schemas

fix: simplify tool schemas for Gemini compatibility
This commit is contained in:
Peter Steinberger
2026-01-09 19:19:01 +00:00
committed by GitHub
6 changed files with 52 additions and 24 deletions

View File

@@ -23,6 +23,43 @@ vi.mock("../config/config.js", async (importOriginal) => {
import { createClawdbotTools } from "./clawdbot-tools.js";
describe("sessions tools", () => {
it("uses number (not integer) in tool schemas for Gemini compatibility", () => {
const tools = createClawdbotTools();
const byName = (name: string) => {
const tool = tools.find((candidate) => candidate.name === name);
expect(tool).toBeDefined();
if (!tool) throw new Error(`missing ${name} tool`);
return tool;
};
const schemaProp = (toolName: string, prop: string) => {
const tool = byName(toolName);
const schema = tool.parameters as {
anyOf?: unknown;
oneOf?: unknown;
properties?: Record<string, unknown>;
};
expect(schema.anyOf).toBeUndefined();
expect(schema.oneOf).toBeUndefined();
const properties = schema.properties ?? {};
const value = properties[prop] as { type?: unknown } | undefined;
expect(value).toBeDefined();
if (!value) throw new Error(`missing ${toolName} schema prop: ${prop}`);
return value;
};
expect(schemaProp("sessions_history", "limit").type).toBe("number");
expect(schemaProp("sessions_list", "limit").type).toBe("number");
expect(schemaProp("sessions_list", "activeMinutes").type).toBe("number");
expect(schemaProp("sessions_list", "messageLimit").type).toBe("number");
expect(schemaProp("sessions_send", "timeoutSeconds").type).toBe("number");
expect(schemaProp("sessions_spawn", "runTimeoutSeconds").type).toBe(
"number",
);
expect(schemaProp("sessions_spawn", "timeoutSeconds").type).toBe("number");
});
it("sessions_list filters kinds and includes messages", async () => {
callGatewayMock.mockReset();
callGatewayMock.mockImplementation(async (opts: unknown) => {

View File

@@ -18,7 +18,7 @@ import {
const SessionsHistoryToolSchema = Type.Object({
sessionKey: Type.String(),
limit: Type.Optional(Type.Integer({ minimum: 1 })),
limit: Type.Optional(Type.Number({ minimum: 1 })),
includeTools: Type.Optional(Type.Boolean()),
});

View File

@@ -46,9 +46,9 @@ type SessionListRow = {
const SessionsListToolSchema = Type.Object({
kinds: Type.Optional(Type.Array(Type.String())),
limit: Type.Optional(Type.Integer({ minimum: 1 })),
activeMinutes: Type.Optional(Type.Integer({ minimum: 1 })),
messageLimit: Type.Optional(Type.Integer({ minimum: 0 })),
limit: Type.Optional(Type.Number({ minimum: 1 })),
activeMinutes: Type.Optional(Type.Number({ minimum: 1 })),
messageLimit: Type.Optional(Type.Number({ minimum: 0 })),
});
function resolveSandboxSessionToolsVisibility(

View File

@@ -30,25 +30,15 @@ import {
resolvePingPongTurns,
} from "./sessions-send-helpers.js";
const SessionsSendToolSchema = Type.Union([
Type.Object(
{
sessionKey: Type.String(),
message: Type.String(),
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
const SessionsSendToolSchema = Type.Object({
sessionKey: Type.Optional(Type.String()),
label: Type.Optional(
Type.String({ minLength: 1, maxLength: SESSION_LABEL_MAX_LENGTH }),
),
Type.Object(
{
label: Type.String({ minLength: 1, maxLength: SESSION_LABEL_MAX_LENGTH }),
agentId: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),
message: Type.String(),
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
},
{ additionalProperties: false },
),
]);
agentId: Type.Optional(Type.String({ minLength: 1, maxLength: 64 })),
message: Type.String(),
timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),
});
export function createSessionsSendTool(opts?: {
agentSessionKey?: string;

View File

@@ -25,9 +25,9 @@ const SessionsSpawnToolSchema = Type.Object({
label: Type.Optional(Type.String()),
agentId: Type.Optional(Type.String()),
model: Type.Optional(Type.String()),
runTimeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
runTimeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),
// Back-compat alias. Prefer runTimeoutSeconds.
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
timeoutSeconds: Type.Optional(Type.Number({ minimum: 0 })),
cleanup: Type.Optional(
Type.Union([Type.Literal("delete"), Type.Literal("keep")]),
),