fix(agents): require raw for gateway config.apply (#566) (thanks @sircrumpet)
This commit is contained in:
@@ -81,6 +81,7 @@
|
|||||||
- Agents: scrub unsupported JSON Schema keywords from tool schemas for Cloud Code Assist API compatibility. (#567) — thanks @erikpr1994
|
- Agents: scrub unsupported JSON Schema keywords from tool schemas for Cloud Code Assist API compatibility. (#567) — thanks @erikpr1994
|
||||||
- Agents: sanitize Cloud Code Assist tool call IDs and detect format/quota errors for failover. (#544) — thanks @jeffersonwarrior
|
- Agents: sanitize Cloud Code Assist tool call IDs and detect format/quota errors for failover. (#544) — thanks @jeffersonwarrior
|
||||||
- Agents: simplify session tool schemas for Gemini compatibility. (#599) — thanks @mcinteerj
|
- Agents: simplify session tool schemas for Gemini compatibility. (#599) — thanks @mcinteerj
|
||||||
|
- Agents: require `raw` for gateway `config.apply` tool calls while keeping schema 2020-12 compatible. (#566) — thanks @sircrumpet
|
||||||
- Agents: add `session_status` agent tool for `/status`-equivalent status (incl. usage/cost) + per-session model overrides. — thanks @steipete
|
- Agents: add `session_status` agent tool for `/status`-equivalent status (incl. usage/cost) + per-session model overrides. — thanks @steipete
|
||||||
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
|
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
|
||||||
- Auto-reply: block reply ordering fix (duplicate PR superseded by #503). (#483) — thanks @AbhisekBasu1
|
- Auto-reply: block reply ordering fix (duplicate PR superseded by #503). (#483) — thanks @AbhisekBasu1
|
||||||
|
|||||||
@@ -31,6 +31,37 @@ describe("createClawdbotCodingTools", () => {
|
|||||||
expect(parameters.required ?? []).toContain("action");
|
expect(parameters.required ?? []).toContain("action");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("requires raw for gateway config.apply tool calls", () => {
|
||||||
|
const tools = createClawdbotCodingTools();
|
||||||
|
const gateway = tools.find((tool) => tool.name === "gateway");
|
||||||
|
expect(gateway).toBeDefined();
|
||||||
|
|
||||||
|
const parameters = gateway?.parameters as {
|
||||||
|
allOf?: Array<Record<string, unknown>>;
|
||||||
|
};
|
||||||
|
const conditional = parameters.allOf?.find(
|
||||||
|
(entry) => "if" in entry && "then" in entry,
|
||||||
|
) as
|
||||||
|
| { if?: Record<string, unknown>; then?: Record<string, unknown> }
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
expect(conditional).toBeDefined();
|
||||||
|
const thenRequired = conditional?.then?.required as string[] | undefined;
|
||||||
|
expect(thenRequired ?? []).toContain("raw");
|
||||||
|
|
||||||
|
const action = (
|
||||||
|
conditional?.if?.properties as Record<string, unknown> | undefined
|
||||||
|
)?.action as { const?: unknown; enum?: unknown[] } | undefined;
|
||||||
|
const values = new Set<string>();
|
||||||
|
if (typeof action?.const === "string") values.add(action.const);
|
||||||
|
if (Array.isArray(action?.enum)) {
|
||||||
|
for (const value of action.enum) {
|
||||||
|
if (typeof value === "string") values.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(values.has("config.apply")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("flattens anyOf-of-literals to enum for provider compatibility", () => {
|
it("flattens anyOf-of-literals to enum for provider compatibility", () => {
|
||||||
const tools = createClawdbotCodingTools();
|
const tools = createClawdbotCodingTools();
|
||||||
const browser = tools.find((tool) => tool.name === "browser");
|
const browser = tools.find((tool) => tool.name === "browser");
|
||||||
|
|||||||
@@ -5,44 +5,52 @@ import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js";
|
|||||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||||
import { callGatewayTool } from "./gateway.js";
|
import { callGatewayTool } from "./gateway.js";
|
||||||
|
|
||||||
const GatewayToolSchema = Type.Union([
|
const GATEWAY_ACTIONS = [
|
||||||
Type.Object({
|
"restart",
|
||||||
action: Type.Literal("restart"),
|
"config.get",
|
||||||
|
"config.schema",
|
||||||
|
"config.apply",
|
||||||
|
"update.run",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type GatewayAction = (typeof GATEWAY_ACTIONS)[number];
|
||||||
|
|
||||||
|
// NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...])
|
||||||
|
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
||||||
|
// The discriminator (action) determines which properties are relevant; runtime validates.
|
||||||
|
const GatewayToolSchema = Type.Object({
|
||||||
|
action: Type.Unsafe<GatewayAction>({
|
||||||
|
type: "string",
|
||||||
|
enum: [...GATEWAY_ACTIONS],
|
||||||
|
}),
|
||||||
|
// restart
|
||||||
delayMs: Type.Optional(Type.Number()),
|
delayMs: Type.Optional(Type.Number()),
|
||||||
reason: Type.Optional(Type.String()),
|
reason: Type.Optional(Type.String()),
|
||||||
}),
|
// config.get, config.schema, config.apply, update.run
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("config.get"),
|
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
gatewayUrl: Type.Optional(Type.String()),
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
gatewayToken: Type.Optional(Type.String()),
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
timeoutMs: Type.Optional(Type.Number()),
|
||||||
}),
|
// config.apply
|
||||||
Type.Object({
|
raw: Type.Optional(Type.String()),
|
||||||
action: Type.Literal("config.schema"),
|
// config.apply, update.run
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
|
||||||
}),
|
|
||||||
Type.Object({
|
|
||||||
action: Type.Literal("config.apply"),
|
|
||||||
raw: Type.String(),
|
|
||||||
sessionKey: Type.Optional(Type.String()),
|
sessionKey: Type.Optional(Type.String()),
|
||||||
note: Type.Optional(Type.String()),
|
note: Type.Optional(Type.String()),
|
||||||
restartDelayMs: Type.Optional(Type.Number()),
|
restartDelayMs: Type.Optional(Type.Number()),
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
});
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
// Keep top-level object schemas while enforcing conditional requirements.
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
(GatewayToolSchema as typeof GatewayToolSchema & { allOf?: unknown[] }).allOf =
|
||||||
}),
|
[
|
||||||
Type.Object({
|
{
|
||||||
action: Type.Literal("update.run"),
|
if: {
|
||||||
sessionKey: Type.Optional(Type.String()),
|
properties: {
|
||||||
note: Type.Optional(Type.String()),
|
action: { const: "config.apply" },
|
||||||
restartDelayMs: Type.Optional(Type.Number()),
|
},
|
||||||
timeoutMs: Type.Optional(Type.Number()),
|
required: ["action"],
|
||||||
gatewayUrl: Type.Optional(Type.String()),
|
},
|
||||||
gatewayToken: Type.Optional(Type.String()),
|
// biome-ignore lint/suspicious/noThenProperty: JSON Schema keyword.
|
||||||
}),
|
then: { required: ["raw"] },
|
||||||
]);
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function createGatewayTool(opts?: {
|
export function createGatewayTool(opts?: {
|
||||||
agentSessionKey?: string;
|
agentSessionKey?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user