diff --git a/apps/server/src/mcp/advertisedJsonSchema.test.ts b/apps/server/src/mcp/advertisedJsonSchema.test.ts index 09a90556a9dd0..3a96fe299b144 100644 --- a/apps/server/src/mcp/advertisedJsonSchema.test.ts +++ b/apps/server/src/mcp/advertisedJsonSchema.test.ts @@ -3,14 +3,18 @@ import * as Schema from "effect/Schema"; import { Tool } from "effect/ai"; import { advertisedJsonSchema } from "./advertisedJsonSchema.ts"; +import { ThreadToolkit } from "./toolkits/thread/tools.ts"; -it("drops JSON noise, merges tagged unions, and keeps null where it is required", () => { +it("drops JSON noise, merges tagged unions, and keeps null where it is required or described", () => { const Every = Schema.Struct({ ms: Schema.Int }); const tool = Tool.make("example", { parameters: Schema.Struct({ threadId: Schema.optional(Schema.NullOr(Schema.String)), beforeRunId: Schema.NullOr(Schema.String), limit: Schema.optional(Schema.Int), + after: Schema.optional( + Schema.Union([Schema.String, Schema.Null.annotate({ description: "Last." })]), + ), every: Schema.Union([Every, Schema.fromJsonString(Every)]), target: Schema.optional( Schema.NullOr( @@ -29,6 +33,7 @@ it("drops JSON noise, merges tagged unions, and keeps null where it is required" threadId: { type: "string" }, beforeRunId: { anyOf: [{ type: "string" }, { type: "null" }] }, limit: { type: "integer" }, + after: { anyOf: [{ type: "string" }, { type: "null", description: "Last." }] }, every: { type: "object", properties: { ms: { type: "integer" } }, required: ["ms"] }, target: { type: "object", @@ -43,3 +48,9 @@ it("drops JSON noise, merges tagged unions, and keeps null where it is required" required: ["beforeRunId", "every"], }); }); + +it("advertises null for t3_queue's beforeRunId, which moves a message to the end", () => { + expect(advertisedJsonSchema(ThreadToolkit.tools.t3_queue)).toMatchObject({ + properties: { beforeRunId: { anyOf: [{ type: "string" }, { type: "null" }] } }, + }); +}); diff --git a/apps/server/src/mcp/advertisedJsonSchema.ts b/apps/server/src/mcp/advertisedJsonSchema.ts index 10001307f2d6c..8c3a59d3c4241 100644 --- a/apps/server/src/mcp/advertisedJsonSchema.ts +++ b/apps/server/src/mcp/advertisedJsonSchema.ts @@ -97,10 +97,10 @@ const compact = (schema: Json, optional: boolean): Json => { }; /** - * The input schema T3 advertises for a tool. Drops JSON noise that every thread pays for: - * `additionalProperties: true`, safe-integer bounds, nested `anyOf`, JSON-string fallbacks, and - * the `null` branch of optional properties. A tagged union of objects becomes one object. - * Decoding still uses the full schema, so each tag's own required fields still apply. + * The input schema T3 advertises for a tool, without JSON noise every thread pays for. A tagged + * union of objects becomes one object; decoding still uses the full schema. An optional property + * loses its bare `null` branch: where `null` means more than "omitted", describe it + * (`Schema.Null.annotate({ description })`) and it stays. */ export const advertisedJsonSchema = (tool: Tool.Any) => compact(Tool.getJsonSchema(tool), false) as ReturnType; diff --git a/apps/server/src/mcp/toolkits/thread/tools.ts b/apps/server/src/mcp/toolkits/thread/tools.ts index 4c59ea4f2028f..c2a352b9b4b95 100644 --- a/apps/server/src/mcp/toolkits/thread/tools.ts +++ b/apps/server/src/mcp/toolkits/thread/tools.ts @@ -96,7 +96,9 @@ const QueueTool = Tool.make("t3_queue", { threadId: Schema.optional(ThreadId), queuedRunId: Schema.optional(RunId), text: Schema.optional(Schema.String.check(Schema.isMaxLength(100000))), - beforeRunId: Schema.optional(Schema.NullOr(RunId)), + beforeRunId: Schema.optional( + Schema.Union([RunId, Schema.Null.annotate({ description: "To the end." })]), + ), targetRunId: Schema.optional(RunId), cursor: Schema.optional(NonNegativeInt), limit: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 }))), diff --git a/apps/server/src/provider/agentContextBudget.json b/apps/server/src/provider/agentContextBudget.json index e7901a8656ebe..ff951d8d97ffe 100644 --- a/apps/server/src/provider/agentContextBudget.json +++ b/apps/server/src/provider/agentContextBudget.json @@ -1,5 +1,5 @@ { "claudeSystemAppendBytes": 5594, "codexAdditionalContextBytes": 7324, - "mcpToolListJsonBytes": 48094 + "mcpToolListJsonBytes": 48150 }