Use openapi-typescript for high-fidelity JSON Schema → TypeScript conversion in tool type signatures. Typecheck code against tool declarations using the TS compiler API before creating tasks, so type errors are caught early and returned to the agent without executing. Includes 24 tests for the typechecker.
Unified · Split
| @@ -9,6 +9,7 @@ · 8 unmodified lines | ||
| 9 | 9 | "@vercel/sandbox": "latest", |
| 10 | 10 | "convex": "latest", |
| 11 | 11 | "elysia": "^1.4.22", |
| 12 | + "openapi-typescript": "^7.10.1", | |
| 12 | 13 | "zod": "^4.3.6" |
| 13 | 14 | }, |
| 14 | 15 | "scripts": { |
| @@ -21,6 +22,6 @@ · 6 unmodified lines | ||
| 21 | 22 | }, |
| 22 | 23 | "devDependencies": { |
| 23 | 24 | "@types/bun": "latest", |
| 24 | − "typescript": "latest" | |
| 25 | + "typescript": "^5.9.3" | |
| 25 | 26 | } |
| 26 | 27 | } |
| @@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; · 1 unmodified line | ||
| 2 | 2 | import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js"; |
| 3 | 3 | import { z } from "zod"; |
| 4 | 4 | import { getTaskTerminalState } from "./service"; |
| 5 | +import { generateToolDeclarations, generateToolInventory, typecheckCode } from "./typechecker"; | |
| 5 | 6 | import type { LiveTaskEvent } from "./events"; |
| 6 | 7 | import type { AnonymousContext, CreateTaskInput, TaskRecord, ToolDescriptor } from "./types"; |
| 7 | 8 | |
| @@ -113,18 +114,9 @@ function waitForTerminalTask( · 105 unmodified lines | ||
| 113 | 114 | |
| 114 | 115 | function buildRunCodeDescription(tools?: ToolDescriptor[]): string { |
| 115 | 116 | const base = |
| 116 | − "Execute TypeScript code in a sandboxed runtime. The code has access to a `tools` object with typed methods for calling external services. Use `return` to return a value. Waits for completion and returns stdout/stderr."; | |
| 117 | + "Execute TypeScript code in a sandboxed runtime. The code has access to a `tools` object with typed methods for calling external services. Use `return` to return a value. Waits for completion and returns stdout/stderr. Code is typechecked before execution — type errors are returned without running."; | |
| 117 | 118 | |
| 118 | − if (!tools || tools.length === 0) return base; | |
| 119 | − | |
| 120 | − const toolLines = tools.map((t) => { | |
| 121 | − const args = t.argsType ?? "unknown"; | |
| 122 | − const returns = t.returnsType ?? "unknown"; | |
| 123 | − const approval = t.approval === "required" ? " [approval required]" : ""; | |
| 124 | − return ` - tools.${t.path}(${args}): ${returns}${approval} — ${t.description}`; | |
| 125 | − }); | |
| 126 | − | |
| 127 | − return `${base}\n\nAvailable tools in the sandbox:\n${toolLines.join("\n")}`; | |
| 119 | + return base + generateToolInventory(tools ?? []); | |
| 128 | 120 | } |
| 129 | 121 | |
| 130 | 122 | // --------------------------------------------------------------------------- |
| @@ -177,6 +169,36 @@ function createRunCodeTool( · 46 unmodified lines | ||
| 177 | 169 | }; |
| 178 | 170 | } |
| 179 | 171 | |
| 172 | + // Typecheck code before execution — get tool inventory for declarations | |
| 173 | + const toolsForContext = await service.listTools({ | |
| 174 | + workspaceId: context.workspaceId, | |
| 175 | + actorId: context.actorId, | |
| 176 | + clientId: context.clientId, | |
| 177 | + }); | |
| 178 | + const declarations = generateToolDeclarations(toolsForContext); | |
| 179 | + const typecheck = typecheckCode(input.code, declarations); | |
| 180 | + | |
| 181 | + if (!typecheck.ok) { | |
| 182 | + const errorText = [ | |
| 183 | + "TypeScript type errors in generated code:", | |
| 184 | + "", | |
| 185 | + ...typecheck.errors.map((e) => ` ${e}`), | |
| 186 | + "", | |
| 187 | + "Fix the type errors and try again.", | |
| 188 | + ].join("\n"); | |
| 189 | + | |
| 190 | + return { | |
| 191 | + content: [textContent(errorText)], | |
| 192 | + isError: true, | |
| 193 | + structuredContent: { | |
| 194 | + typecheckErrors: typecheck.errors, | |
| 195 | + workspaceId: context.workspaceId, | |
| 196 | + actorId: context.actorId, | |
| 197 | + sessionId: context.sessionId, | |
| 198 | + }, | |
| 199 | + }; | |
| 200 | + } | |
| 201 | + | |
| 180 | 202 | const created = await service.createTask({ |
| 181 | 203 | code: input.code, |
| 182 | 204 | timeoutMs: input.timeoutMs, |
| @@ -2,6 +2,7 @@ import SwaggerParser from "@apidevtools/swagger-parser"; · 1 unmodified line | ||
| 2 | 2 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; |
| 3 | 3 | import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; |
| 4 | 4 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; |
| 5 | +import { astToString, transformSchemaObject } from "openapi-typescript"; | |
| 5 | 6 | import type { ToolApprovalMode, ToolCredentialSpec, ToolDefinition } from "./types"; |
| 6 | 7 | |
| 7 | 8 | type JsonSchema = Record<string, unknown>; |
| @@ -65,7 +66,51 @@ function sanitizeSegment(value: string): string { · 57 unmodified lines | ||
| 65 | 66 | return cleaned.length > 0 ? cleaned : "default"; |
| 66 | 67 | } |
| 67 | 68 | |
| 68 | −function jsonSchemaTypeHint(schema: unknown, depth = 0): string { | |
| 69 | +// openapi-typescript context for standalone schema conversion | |
| 70 | +function makeTransformCtx() { | |
| 71 | + return { | |
| 72 | + additionalProperties: false, | |
| 73 | + alphabetize: false, | |
| 74 | + arrayLength: false, | |
| 75 | + defaultNonNullable: true, | |
| 76 | + discriminators: { refsHandled: [] as string[], objects: {} as Record<string, unknown> }, | |
| 77 | + emptyObjectsUnknown: false, | |
| 78 | + enum: false, | |
| 79 | + enumValues: false, | |
| 80 | + excludeDeprecated: false, | |
| 81 | + exportType: false, | |
| 82 | + immutable: false, | |
| 83 | + indentLv: 0, | |
| 84 | + pathParamsAsTypes: false, | |
| 85 | + postTransform: undefined, | |
| 86 | + propertiesRequiredByDefault: false, | |
| 87 | + redoc: undefined, | |
| 88 | + silent: true, | |
| 89 | + resolve(_ref: string) { return undefined as unknown; }, | |
| 90 | + }; | |
| 91 | +} | |
| 92 | + | |
| 93 | +/** | |
| 94 | + * Convert a JSON Schema to a TypeScript type string using openapi-typescript. | |
| 95 | + * Handles allOf, oneOf, enums, nullable, additionalProperties, etc. | |
| 96 | + * Falls back to the simple hand-rolled version on error. | |
| 97 | + */ | |
| 98 | +function jsonSchemaToTypeString(schema: unknown): string { | |
| 99 | + if (!schema || typeof schema !== "object") return "unknown"; | |
| 100 | + try { | |
| 101 | + const node = transformSchemaObject(schema as never, { | |
| 102 | + path: "#", | |
| 103 | + ctx: makeTransformCtx() as never, | |
| 104 | + }); | |
| 105 | + const result = astToString(node).trim(); | |
| 106 | + return result || "unknown"; | |
| 107 | + } catch { | |
| 108 | + return jsonSchemaTypeHintFallback(schema); | |
| 109 | + } | |
| 110 | +} | |
| 111 | + | |
| 112 | +/** Simple fallback for when openapi-typescript can't handle the schema */ | |
| 113 | +function jsonSchemaTypeHintFallback(schema: unknown, depth = 0): string { | |
| 69 | 114 | if (!schema || typeof schema !== "object") return "unknown"; |
| 70 | 115 | if (depth > 4) return "unknown"; |
| 71 | 116 | |
| @@ -77,12 +122,12 @@ function jsonSchemaTypeHint(schema: unknown, depth = 0): string { · 5 unmodified lines | ||
| 77 | 122 | |
| 78 | 123 | const oneOf = Array.isArray(shape.oneOf) ? shape.oneOf : undefined; |
| 79 | 124 | if (oneOf && oneOf.length > 0) { |
| 80 | − return oneOf.map((entry) => jsonSchemaTypeHint(entry, depth + 1)).join(" | "); | |
| 125 | + return oneOf.map((entry) => jsonSchemaTypeHintFallback(entry, depth + 1)).join(" | "); | |
| 81 | 126 | } |
| 82 | 127 | |
| 83 | 128 | const anyOf = Array.isArray(shape.anyOf) ? shape.anyOf : undefined; |
| 84 | 129 | if (anyOf && anyOf.length > 0) { |
| 85 | − return anyOf.map((entry) => jsonSchemaTypeHint(entry, depth + 1)).join(" | "); | |
| 130 | + return anyOf.map((entry) => jsonSchemaTypeHintFallback(entry, depth + 1)).join(" | "); | |
| 86 | 131 | } |
| 87 | 132 | |
| 88 | 133 | const type = typeof shape.type === "string" ? shape.type : undefined; |
| @@ -91,7 +136,7 @@ function jsonSchemaTypeHint(schema: unknown, depth = 0): string { · 2 unmodified lines | ||
| 91 | 136 | } |
| 92 | 137 | |
| 93 | 138 | if (type === "array") { |
| 94 | − return `${jsonSchemaTypeHint(shape.items, depth + 1)}[]`; | |
| 139 | + return `${jsonSchemaTypeHintFallback(shape.items, depth + 1)}[]`; | |
| 95 | 140 | } |
| 96 | 141 | |
| 97 | 142 | const props = toObject(shape.properties); |
| @@ -104,7 +149,7 @@ function jsonSchemaTypeHint(schema: unknown, depth = 0): string { · 6 unmodified lines | ||
| 104 | 149 | } |
| 105 | 150 | const inner = propEntries |
| 106 | 151 | .slice(0, 12) |
| 107 | − .map(([key, value]) => `${key}${required.has(key) ? "" : "?"}: ${jsonSchemaTypeHint(value, depth + 1)}`) | |
| 152 | + .map(([key, value]) => `${key}${required.has(key) ? "" : "?"}: ${jsonSchemaTypeHintFallback(value, depth + 1)}`) | |
| 108 | 153 | .join("; "); |
| 109 | 154 | return `{ ${inner} }`; |
| 110 | 155 | } |
| @@ -191,7 +236,7 @@ async function loadMcpTools(config: McpToolSourceConfig): Promise<ToolDefinition · 80 unmodified lines | ||
| 191 | 236 | approval: config.overrides?.[toolName]?.approval ?? config.defaultApproval ?? "auto", |
| 192 | 237 | description: String(tool.description ?? `MCP tool ${toolName}`), |
| 193 | 238 | metadata: { |
| 194 | − argsType: jsonSchemaTypeHint(inputSchema), | |
| 239 | + argsType: jsonSchemaToTypeString(inputSchema), | |
| 195 | 240 | returnsType: "unknown", |
| 196 | 241 | }, |
| 197 | 242 | run: async (input: unknown) => { |
| @@ -390,8 +435,8 @@ async function loadOpenApiTools(config: OpenApiToolSourceConfig): Promise<ToolDe · 192 unmodified lines | ||
| 390 | 435 | approval, |
| 391 | 436 | description: String(operation.summary ?? operation.description ?? `${method.toUpperCase()} ${pathTemplate}`), |
| 392 | 437 | metadata: { |
| 393 | − argsType: jsonSchemaTypeHint(combinedSchema), | |
| 394 | − returnsType: jsonSchemaTypeHint(responseSchema), | |
| 438 | + argsType: jsonSchemaToTypeString(combinedSchema), | |
| 439 | + returnsType: jsonSchemaToTypeString(responseSchema), | |
| 395 | 440 | }, |
| 396 | 441 | credential: credentialSpec, |
| 397 | 442 | run: async (input: unknown, context) => { |
| @@ -0,0 +1,250 @@ | ||
| 1 | +import { test, expect, describe } from "bun:test"; | |
| 2 | +import { generateToolDeclarations, generateToolInventory, typecheckCode, type TypecheckResult } from "./typechecker"; | |
| 3 | +import type { ToolDescriptor } from "./types"; | |
| 4 | + | |
| 5 | +// --------------------------------------------------------------------------- | |
| 6 | +// Test fixtures | |
| 7 | +// --------------------------------------------------------------------------- | |
| 8 | + | |
| 9 | +const MATH_TOOL: ToolDescriptor = { | |
| 10 | + path: "math.add", | |
| 11 | + description: "Add two numbers", | |
| 12 | + approval: "auto", | |
| 13 | + argsType: "{ a: number; b: number }", | |
| 14 | + returnsType: "{ result: number }", | |
| 15 | +}; | |
| 16 | + | |
| 17 | +const ADMIN_TOOL: ToolDescriptor = { | |
| 18 | + path: "admin.send_announcement", | |
| 19 | + description: "Send an announcement", | |
| 20 | + approval: "required", | |
| 21 | + argsType: "{ message: string; channel?: string }", | |
| 22 | + returnsType: "{ sent: boolean }", | |
| 23 | +}; | |
| 24 | + | |
| 25 | +const FLAT_TOOL: ToolDescriptor = { | |
| 26 | + path: "get_time", | |
| 27 | + description: "Get current time", | |
| 28 | + approval: "auto", | |
| 29 | + argsType: "{}", | |
| 30 | + returnsType: "{ iso: string; unix: number }", | |
| 31 | +}; | |
| 32 | + | |
| 33 | +const ALL_TOOLS = [MATH_TOOL, ADMIN_TOOL, FLAT_TOOL]; | |
| 34 | + | |
| 35 | +// --------------------------------------------------------------------------- | |
| 36 | +// generateToolDeclarations | |
| 37 | +// --------------------------------------------------------------------------- | |
| 38 | + | |
| 39 | +describe("generateToolDeclarations", () => { | |
| 40 | + test("empty tools produces empty tools object", () => { | |
| 41 | + const result = generateToolDeclarations([]); | |
| 42 | + expect(result).toBe("declare const tools: {\n\n};"); | |
| 43 | + }); | |
| 44 | + | |
| 45 | + test("flat tool (no dots in path beyond single segment)", () => { | |
| 46 | + const result = generateToolDeclarations([FLAT_TOOL]); | |
| 47 | + expect(result).toContain("get_time(input: {}): Promise<{ iso: string; unix: number }>;"); | |
| 48 | + }); | |
| 49 | + | |
| 50 | + test("nested tool (math.add)", () => { | |
| 51 | + const result = generateToolDeclarations([MATH_TOOL]); | |
| 52 | + expect(result).toContain("math: {"); | |
| 53 | + expect(result).toContain("add(input: { a: number; b: number }): Promise<{ result: number }>;"); | |
| 54 | + }); | |
| 55 | + | |
| 56 | + test("multiple tools produce correct nesting", () => { | |
| 57 | + const result = generateToolDeclarations(ALL_TOOLS); | |
| 58 | + // math namespace | |
| 59 | + expect(result).toContain("math: {"); | |
| 60 | + expect(result).toContain("add(input: { a: number; b: number }): Promise<{ result: number }>;"); | |
| 61 | + // admin namespace | |
| 62 | + expect(result).toContain("admin: {"); | |
| 63 | + expect(result).toContain("send_announcement(input: { message: string; channel?: string }): Promise<{ sent: boolean }>;"); | |
| 64 | + // flat | |
| 65 | + expect(result).toContain("get_time(input: {}): Promise<{ iso: string; unix: number }>;"); | |
| 66 | + }); | |
| 67 | + | |
| 68 | + test("missing argsType defaults to Record<string, unknown>", () => { | |
| 69 | + const tool: ToolDescriptor = { | |
| 70 | + path: "foo.bar", | |
| 71 | + description: "test", | |
| 72 | + approval: "auto", | |
| 73 | + }; | |
| 74 | + const result = generateToolDeclarations([tool]); | |
| 75 | + expect(result).toContain("bar(input: Record<string, unknown>): Promise<unknown>;"); | |
| 76 | + }); | |
| 77 | + | |
| 78 | + test("missing returnsType defaults to unknown", () => { | |
| 79 | + const tool: ToolDescriptor = { | |
| 80 | + path: "baz", | |
| 81 | + description: "test", | |
| 82 | + approval: "auto", | |
| 83 | + argsType: "{ x: number }", | |
| 84 | + }; | |
| 85 | + const result = generateToolDeclarations([tool]); | |
| 86 | + expect(result).toContain("baz(input: { x: number }): Promise<unknown>;"); | |
| 87 | + }); | |
| 88 | +}); | |
| 89 | + | |
| 90 | +// --------------------------------------------------------------------------- | |
| 91 | +// generateToolInventory | |
| 92 | +// --------------------------------------------------------------------------- | |
| 93 | + | |
| 94 | +describe("generateToolInventory", () => { | |
| 95 | + test("empty tools returns empty string", () => { | |
| 96 | + expect(generateToolInventory([])).toBe(""); | |
| 97 | + }); | |
| 98 | + | |
| 99 | + test("includes tool paths and type signatures", () => { | |
| 100 | + const result = generateToolInventory(ALL_TOOLS); | |
| 101 | + expect(result).toContain("tools.math.add(input: { a: number; b: number }): Promise<{ result: number }>"); | |
| 102 | + expect(result).toContain("tools.admin.send_announcement(input: { message: string; channel?: string }): Promise<{ sent: boolean }>"); | |
| 103 | + expect(result).toContain("tools.get_time(input: {}): Promise<{ iso: string; unix: number }>"); | |
| 104 | + }); | |
| 105 | + | |
| 106 | + test("shows [approval required] for required-approval tools", () => { | |
| 107 | + const result = generateToolInventory(ALL_TOOLS); | |
| 108 | + expect(result).toContain("[approval required]"); | |
| 109 | + // math.add is auto — should NOT have the tag | |
| 110 | + const mathLine = result.split("\n").find((l) => l.includes("tools.math.add")); | |
| 111 | + expect(mathLine).not.toContain("[approval required]"); | |
| 112 | + // admin.send_announcement is required — should have the tag | |
| 113 | + const adminLine = result.split("\n").find((l) => l.includes("tools.admin.send_announcement")); | |
| 114 | + expect(adminLine).toContain("[approval required]"); | |
| 115 | + }); | |
| 116 | + | |
| 117 | + test("includes descriptions", () => { | |
| 118 | + const result = generateToolInventory(ALL_TOOLS); | |
| 119 | + expect(result).toContain("Add two numbers"); | |
| 120 | + expect(result).toContain("Send an announcement"); | |
| 121 | + expect(result).toContain("Get current time"); | |
| 122 | + }); | |
| 123 | +}); | |
| 124 | + | |
| 125 | +// --------------------------------------------------------------------------- | |
| 126 | +// typecheckCode | |
| 127 | +// --------------------------------------------------------------------------- | |
| 128 | + | |
| 129 | +describe("typecheckCode", () => { | |
| 130 | + const declarations = generateToolDeclarations(ALL_TOOLS); | |
| 131 | + | |
| 132 | + test("valid code passes", () => { | |
| 133 | + const result = typecheckCode("return 40 + 2;", declarations); | |
| 134 | + expect(result.ok).toBe(true); | |
| 135 | + expect(result.errors).toHaveLength(0); | |
| 136 | + }); | |
| 137 | + | |
| 138 | + test("valid tool call passes", () => { | |
| 139 | + const result = typecheckCode( | |
| 140 | + 'const sum = await tools.math.add({ a: 1, b: 2 }); return sum.result;', | |
| 141 | + declarations, | |
| 142 | + ); | |
| 143 | + expect(result.ok).toBe(true); | |
| 144 | + expect(result.errors).toHaveLength(0); | |
| 145 | + }); | |
| 146 | + | |
| 147 | + test("valid async code with await", () => { | |
| 148 | + const result = typecheckCode( | |
| 149 | + `const time = await tools.get_time({}); | |
| 150 | + console.log(time.iso); | |
| 151 | + return time.unix;`, | |
| 152 | + declarations, | |
| 153 | + ); | |
| 154 | + expect(result.ok).toBe(true); | |
| 155 | + }); | |
| 156 | + | |
| 157 | + test("console.log is available", () => { | |
| 158 | + const result = typecheckCode('console.log("hello"); return 1;', declarations); | |
| 159 | + expect(result.ok).toBe(true); | |
| 160 | + }); | |
| 161 | + | |
| 162 | + test("setTimeout is available", () => { | |
| 163 | + const result = typecheckCode( | |
| 164 | + 'const id = setTimeout(() => {}, 100); clearTimeout(id); return 1;', | |
| 165 | + declarations, | |
| 166 | + ); | |
| 167 | + expect(result.ok).toBe(true); | |
| 168 | + }); | |
| 169 | + | |
| 170 | + test("wrong argument types produce errors", () => { | |
| 171 | + const result = typecheckCode( | |
| 172 | + 'const sum = await tools.math.add({ a: "not a number", b: 2 });', | |
| 173 | + declarations, | |
| 174 | + ); | |
| 175 | + expect(result.ok).toBe(false); | |
| 176 | + expect(result.errors.length).toBeGreaterThan(0); | |
| 177 | + expect(result.errors.some((e) => e.includes("string") || e.includes("number"))).toBe(true); | |
| 178 | + }); | |
| 179 | + | |
| 180 | + test("missing required argument produces errors", () => { | |
| 181 | + const result = typecheckCode( | |
| 182 | + "const sum = await tools.math.add({ a: 1 });", | |
| 183 | + declarations, | |
| 184 | + ); | |
| 185 | + expect(result.ok).toBe(false); | |
| 186 | + expect(result.errors.some((e) => e.includes("b"))).toBe(true); | |
| 187 | + }); | |
| 188 | + | |
| 189 | + test("non-existent tool produces errors", () => { | |
| 190 | + const result = typecheckCode( | |
| 191 | + "const result = await tools.nonexistent.doStuff({});", | |
| 192 | + declarations, | |
| 193 | + ); | |
| 194 | + expect(result.ok).toBe(false); | |
| 195 | + expect(result.errors.some((e) => e.includes("nonexistent"))).toBe(true); | |
| 196 | + }); | |
| 197 | + | |
| 198 | + test("accessing wrong property on result produces errors", () => { | |
| 199 | + const result = typecheckCode( | |
| 200 | + "const sum = await tools.math.add({ a: 1, b: 2 }); return sum.nonexistent;", | |
| 201 | + declarations, | |
| 202 | + ); | |
| 203 | + expect(result.ok).toBe(false); | |
| 204 | + expect(result.errors.some((e) => e.includes("nonexistent"))).toBe(true); | |
| 205 | + }); | |
| 206 | + | |
| 207 | + test("type errors have adjusted line numbers", () => { | |
| 208 | + const code = `const x = 1; | |
| 209 | +const y = "hello"; | |
| 210 | +const z: number = y;`; | |
| 211 | + const result = typecheckCode(code, declarations); | |
| 212 | + expect(result.ok).toBe(false); | |
| 213 | + // The error should reference line 3 of the user code, not the wrapper | |
| 214 | + expect(result.errors.some((e) => e.startsWith("Line 3:"))).toBe(true); | |
| 215 | + }); | |
| 216 | + | |
| 217 | + test("empty declarations (no tools) still works for plain code", () => { | |
| 218 | + const emptyDecl = generateToolDeclarations([]); | |
| 219 | + const result = typecheckCode("return 42;", emptyDecl); | |
| 220 | + expect(result.ok).toBe(true); | |
| 221 | + }); | |
| 222 | + | |
| 223 | + test("complex valid code with multiple tool calls", () => { | |
| 224 | + const code = ` | |
| 225 | +const sum = await tools.math.add({ a: 10, b: 20 }); | |
| 226 | +const announcement = await tools.admin.send_announcement({ message: "Sum is " + sum.result }); | |
| 227 | +const time = await tools.get_time({}); | |
| 228 | +console.log(time.iso); | |
| 229 | +return { sum: sum.result, sent: announcement.sent, time: time.unix }; | |
| 230 | +`; | |
| 231 | + const result = typecheckCode(code, declarations); | |
| 232 | + expect(result.ok).toBe(true); | |
| 233 | + }); | |
| 234 | + | |
| 235 | + test("optional parameter can be omitted", () => { | |
| 236 | + const result = typecheckCode( | |
| 237 | + 'const r = await tools.admin.send_announcement({ message: "hi" }); return r.sent;', | |
| 238 | + declarations, | |
| 239 | + ); | |
| 240 | + expect(result.ok).toBe(true); | |
| 241 | + }); | |
| 242 | + | |
| 243 | + test("optional parameter can be provided", () => { | |
| 244 | + const result = typecheckCode( | |
| 245 | + 'const r = await tools.admin.send_announcement({ message: "hi", channel: "general" }); return r.sent;', | |
| 246 | + declarations, | |
| 247 | + ); | |
| 248 | + expect(result.ok).toBe(true); | |
| 249 | + }); | |
| 250 | +}); | |
| @@ -0,0 +1,174 @@ | ||
| 1 | +/** | |
| 2 | + * TypeScript typechecker for LLM-generated code. | |
| 3 | + * | |
| 4 | + * Generates `declare const tools: { ... }` from ToolDescriptor metadata, | |
| 5 | + * then validates code against those declarations using the TypeScript compiler API. | |
| 6 | + * | |
| 7 | + * This runs in the executor so all MCP clients benefit from typechecking | |
| 8 | + * without needing their own TypeScript setup. | |
| 9 | + */ | |
| 10 | + | |
| 11 | +import type { ToolDescriptor } from "./types"; | |
| 12 | + | |
| 13 | +// --------------------------------------------------------------------------- | |
| 14 | +// Tool declarations generation | |
| 15 | +// --------------------------------------------------------------------------- | |
| 16 | + | |
| 17 | +/** | |
| 18 | + * Build a `declare const tools: { ... }` block from flat tool descriptors. | |
| 19 | + * | |
| 20 | + * Tool paths like "math.add" or "admin.send_announcement" are split on "." | |
| 21 | + * and nested into a type tree. | |
| 22 | + */ | |
| 23 | +export function generateToolDeclarations(tools: ToolDescriptor[]): string { | |
| 24 | + // Build a nested tree from flat tool paths | |
| 25 | + interface TreeNode { | |
| 26 | + children: Map<string, TreeNode>; | |
| 27 | + tool?: ToolDescriptor; | |
| 28 | + } | |
| 29 | + | |
| 30 | + const root: TreeNode = { children: new Map() }; | |
| 31 | + | |
| 32 | + for (const tool of tools) { | |
| 33 | + const segments = tool.path.split("."); | |
| 34 | + let node = root; | |
| 35 | + for (let i = 0; i < segments.length; i++) { | |
| 36 | + const seg = segments[i]; | |
| 37 | + if (!node.children.has(seg)) { | |
| 38 | + node.children.set(seg, { children: new Map() }); | |
| 39 | + } | |
| 40 | + node = node.children.get(seg)!; | |
| 41 | + if (i === segments.length - 1) { | |
| 42 | + node.tool = tool; | |
| 43 | + } | |
| 44 | + } | |
| 45 | + } | |
| 46 | + | |
| 47 | + function renderNode(node: TreeNode, indent: number): string { | |
| 48 | + const pad = " ".repeat(indent); | |
| 49 | + const lines: string[] = []; | |
| 50 | + | |
| 51 | + for (const [key, child] of node.children) { | |
| 52 | + if (child.tool) { | |
| 53 | + const args = child.tool.argsType || "Record<string, unknown>"; | |
| 54 | + const returns = child.tool.returnsType || "unknown"; | |
| 55 | + lines.push(`${pad}${key}(input: ${args}): Promise<${returns}>;`); | |
| 56 | + } else { | |
| 57 | + lines.push(`${pad}${key}: {`); | |
| 58 | + lines.push(renderNode(child, indent + 1)); | |
| 59 | + lines.push(`${pad}};`); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + | |
| 63 | + return lines.join("\n"); | |
| 64 | + } | |
| 65 | + | |
| 66 | + return `declare const tools: {\n${renderNode(root, 1)}\n};`; | |
| 67 | +} | |
| 68 | + | |
| 69 | +/** | |
| 70 | + * Generate the tool inventory text for the MCP run_code description. | |
| 71 | + * Includes full type signatures so the LLM can write correct code. | |
| 72 | + */ | |
| 73 | +export function generateToolInventory(tools: ToolDescriptor[]): string { | |
| 74 | + if (!tools || tools.length === 0) return ""; | |
| 75 | + | |
| 76 | + const lines = tools.map((t) => { | |
| 77 | + const args = t.argsType || "Record<string, unknown>"; | |
| 78 | + const returns = t.returnsType || "unknown"; | |
| 79 | + const approval = t.approval === "required" ? " [approval required]" : ""; | |
| 80 | + return ` tools.${t.path}(input: ${args}): Promise<${returns}>${approval}\n ${t.description}`; | |
| 81 | + }); | |
| 82 | + | |
| 83 | + return `\nAvailable tools in the sandbox:\n${lines.join("\n\n")}`; | |
| 84 | +} | |
| 85 | + | |
| 86 | +// --------------------------------------------------------------------------- | |
| 87 | +// TypeScript typechecking | |
| 88 | +// --------------------------------------------------------------------------- | |
| 89 | + | |
| 90 | +export interface TypecheckResult { | |
| 91 | + readonly ok: boolean; | |
| 92 | + readonly errors: readonly string[]; | |
| 93 | +} | |
| 94 | + | |
| 95 | +/** | |
| 96 | + * Typecheck LLM-generated code against tool declarations. | |
| 97 | + * | |
| 98 | + * Uses the TypeScript compiler API. Returns errors with line numbers | |
| 99 | + * adjusted to match the original code (not the wrapper). | |
| 100 | + */ | |
| 101 | +export function typecheckCode( | |
| 102 | + code: string, | |
| 103 | + toolDeclarations: string, | |
| 104 | +): TypecheckResult { | |
| 105 | + let ts: typeof import("typescript"); | |
| 106 | + try { | |
| 107 | + ts = require("typescript"); | |
| 108 | + } catch { | |
| 109 | + // TypeScript not available — skip typechecking | |
| 110 | + return { ok: true, errors: [] }; | |
| 111 | + } | |
| 112 | + | |
| 113 | + // Wrap the code in an async function with the tools declaration. | |
| 114 | + // We declare sandbox globals (console, setTimeout, etc.) ourselves since | |
| 115 | + // `types: []` prevents @types/node from loading. | |
| 116 | + const wrappedCode = [ | |
| 117 | + toolDeclarations, | |
| 118 | + "declare const console: { log(...args: any[]): void; info(...args: any[]): void; warn(...args: any[]): void; error(...args: any[]): void; };", | |
| 119 | + "declare function setTimeout(fn: () => void, ms: number): number;", | |
| 120 | + "declare function clearTimeout(id: number): void;", | |
| 121 | + "async function __generated() {", | |
| 122 | + code, | |
| 123 | + "}", | |
| 124 | + ].join("\n"); | |
| 125 | + | |
| 126 | + const sourceFile = ts.createSourceFile( | |
| 127 | + "generated.ts", | |
| 128 | + wrappedCode, | |
| 129 | + ts.ScriptTarget.ESNext, | |
| 130 | + true, | |
| 131 | + ts.ScriptKind.TS, | |
| 132 | + ); | |
| 133 | + | |
| 134 | + const compilerOptions: import("typescript").CompilerOptions = { | |
| 135 | + target: ts.ScriptTarget.ESNext, | |
| 136 | + module: ts.ModuleKind.ESNext, | |
| 137 | + strict: true, | |
| 138 | + noEmit: true, | |
| 139 | + lib: ["lib.es2022.d.ts"], | |
| 140 | + types: [], // prevent automatic @types/* (e.g. @types/node) from conflicting with our sandbox declarations | |
| 141 | + }; | |
| 142 | + | |
| 143 | + const host = ts.createCompilerHost(compilerOptions); | |
| 144 | + const originalGetSourceFile = host.getSourceFile.bind(host); | |
| 145 | + host.getSourceFile = (fileName, languageVersion) => { | |
| 146 | + if (fileName === "generated.ts") return sourceFile; | |
| 147 | + return originalGetSourceFile(fileName, languageVersion); | |
| 148 | + }; | |
| 149 | + | |
| 150 | + const program = ts.createProgram(["generated.ts"], compilerOptions, host); | |
| 151 | + const diagnostics = program.getSemanticDiagnostics(sourceFile); | |
| 152 | + | |
| 153 | + if (diagnostics.length === 0) { | |
| 154 | + return { ok: true, errors: [] }; | |
| 155 | + } | |
| 156 | + | |
| 157 | + // Count header lines so we can adjust line numbers | |
| 158 | + const headerLineCount = | |
| 159 | + toolDeclarations.split("\n").length + 4; // +4 for console, setTimeout, clearTimeout, function header | |
| 160 | + | |
| 161 | + const errors = diagnostics.map((d) => { | |
| 162 | + const message = ts.flattenDiagnosticMessageText(d.messageText, "\n"); | |
| 163 | + if (d.start !== undefined && d.file) { | |
| 164 | + const { line } = d.file.getLineAndCharacterOfPosition(d.start); | |
| 165 | + const adjustedLine = line + 1 - headerLineCount; | |
| 166 | + if (adjustedLine > 0) { | |
| 167 | + return `Line ${adjustedLine}: ${message}`; | |
| 168 | + } | |
| 169 | + } | |
| 170 | + return message; | |
| 171 | + }); | |
| 172 | + | |
| 173 | + return { ok: false, errors }; | |
| 174 | +} | |
| @@ -23,11 +23,12 @@ · 22 unmodified lines | ||
| 23 | 23 | "@vercel/sandbox": "latest", |
| 24 | 24 | "convex": "latest", |
| 25 | 25 | "elysia": "^1.4.22", |
| 26 | + "openapi-typescript": "^7.10.1", | |
| 26 | 27 | "zod": "^4.3.6", |
| 27 | 28 | }, |
| 28 | 29 | "devDependencies": { |
| 29 | 30 | "@types/bun": "latest", |
| 30 | − "typescript": "latest", | |
| 31 | + "typescript": "^5.9.3", | |
| 31 | 32 | }, |
| 32 | 33 | }, |
| 33 | 34 | "apps/web": { |
| @@ -486,6 +487,12 @@ · 452 unmodified lines | ||
| 486 | 487 | |
| 487 | 488 | "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], |
| 488 | 489 | |
| 490 | + "@redocly/ajv": ["@redocly/ajv@8.17.3", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-NQsbJbB/GV7JVO88ebFkMndrnuGp/dTm5/2NISeg+JGcLzTfGBJZ01+V5zD8nKBOpi/dLLNFT+Ql6IcUk8ehng=="], | |
| 491 | + | |
| 492 | + "@redocly/config": ["@redocly/config@0.22.2", "", {}, "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ=="], | |
| 493 | + | |
| 494 | + "@redocly/openapi-core": ["@redocly/openapi-core@1.34.6", "", { "dependencies": { "@redocly/ajv": "^8.11.2", "@redocly/config": "^0.22.0", "colorette": "^1.2.0", "https-proxy-agent": "^7.0.5", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "minimatch": "^5.0.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw=="], | |
| 495 | + | |
| 489 | 496 | "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], |
| 490 | 497 | |
| 491 | 498 | "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], |
| @@ -630,6 +637,8 @@ · 138 unmodified lines | ||
| 630 | 637 | |
| 631 | 638 | "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], |
| 632 | 639 | |
| 640 | + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], | |
| 641 | + | |
| 633 | 642 | "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], |
| 634 | 643 | |
| 635 | 644 | "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], |
| @@ -708,6 +717,8 @@ · 72 unmodified lines | ||
| 708 | 717 | |
| 709 | 718 | "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], |
| 710 | 719 | |
| 720 | + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], | |
| 721 | + | |
| 711 | 722 | "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], |
| 712 | 723 | |
| 713 | 724 | "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], |
| @@ -728,6 +739,8 @@ · 14 unmodified lines | ||
| 728 | 739 | |
| 729 | 740 | "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], |
| 730 | 741 | |
| 742 | + "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], | |
| 743 | + | |
| 731 | 744 | "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], |
| 732 | 745 | |
| 733 | 746 | "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], |
| @@ -1020,6 +1033,8 @@ · 286 unmodified lines | ||
| 1020 | 1033 | |
| 1021 | 1034 | "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], |
| 1022 | 1035 | |
| 1036 | + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], | |
| 1037 | + | |
| 1023 | 1038 | "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], |
| 1024 | 1039 | |
| 1025 | 1040 | "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], |
| @@ -1118,6 +1133,8 @@ · 92 unmodified lines | ||
| 1118 | 1133 | |
| 1119 | 1134 | "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], |
| 1120 | 1135 | |
| 1136 | + "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], | |
| 1137 | + | |
| 1121 | 1138 | "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], |
| 1122 | 1139 | |
| 1123 | 1140 | "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], |
| @@ -1276,6 +1293,8 @@ · 152 unmodified lines | ||
| 1276 | 1293 | |
| 1277 | 1294 | "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], |
| 1278 | 1295 | |
| 1296 | + "openapi-typescript": ["openapi-typescript@7.10.1", "", { "dependencies": { "@redocly/openapi-core": "^1.34.5", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw=="], | |
| 1297 | + | |
| 1279 | 1298 | "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], |
| 1280 | 1299 | |
| 1281 | 1300 | "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], |
| @@ -1294,7 +1313,7 @@ · 12 unmodified lines | ||
| 1294 | 1313 | |
| 1295 | 1314 | "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], |
| 1296 | 1315 | |
| 1297 | − "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], | |
| 1316 | + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], | |
| 1298 | 1317 | |
| 1299 | 1318 | "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], |
| 1300 | 1319 | |
| @@ -1316,6 +1335,8 @@ · 15 unmodified lines | ||
| 1316 | 1335 | |
| 1317 | 1336 | "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], |
| 1318 | 1337 | |
| 1338 | + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], | |
| 1339 | + | |
| 1319 | 1340 | "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], |
| 1320 | 1341 | |
| 1321 | 1342 | "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], |
| @@ -1482,7 +1503,7 @@ · 160 unmodified lines | ||
| 1482 | 1503 | |
| 1483 | 1504 | "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], |
| 1484 | 1505 | |
| 1485 | − "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], | |
| 1506 | + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], | |
| 1486 | 1507 | |
| 1487 | 1508 | "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], |
| 1488 | 1509 | |
| @@ -1528,7 +1549,7 @@ · 39 unmodified lines | ||
| 1528 | 1549 | |
| 1529 | 1550 | "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], |
| 1530 | 1551 | |
| 1531 | − "type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="], | |
| 1552 | + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], | |
| 1532 | 1553 | |
| 1533 | 1554 | "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], |
| 1534 | 1555 | |
| @@ -1606,6 +1627,8 @@ · 71 unmodified lines | ||
| 1606 | 1627 | |
| 1607 | 1628 | "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], |
| 1608 | 1629 | |
| 1630 | + "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], | |
| 1631 | + | |
| 1609 | 1632 | "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], |
| 1610 | 1633 | |
| 1611 | 1634 | "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], |
| @@ -1636,6 +1659,8 @@ · 24 unmodified lines | ||
| 1636 | 1659 | |
| 1637 | 1660 | "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], |
| 1638 | 1661 | |
| 1662 | + "@redocly/openapi-core/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], | |
| 1663 | + | |
| 1639 | 1664 | "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], |
| 1640 | 1665 | |
| 1641 | 1666 | "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], |
| @@ -1660,6 +1685,8 @@ · 18 unmodified lines | ||
| 1660 | 1685 | |
| 1661 | 1686 | "bun-types/@types/node": ["@types/node@25.2.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg=="], |
| 1662 | 1687 | |
| 1688 | + "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], | |
| 1689 | + | |
| 1663 | 1690 | "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], |
| 1664 | 1691 | |
| 1665 | 1692 | "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], |
| @@ -1668,6 +1695,8 @@ · 2 unmodified lines | ||
| 1668 | 1695 | |
| 1669 | 1696 | "convex/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], |
| 1670 | 1697 | |
| 1698 | + "cosmiconfig/parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], | |
| 1699 | + | |
| 1671 | 1700 | "eslint/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], |
| 1672 | 1701 | |
| 1673 | 1702 | "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], |
| @@ -1692,6 +1721,8 @@ · 18 unmodified lines | ||
| 1692 | 1721 | |
| 1693 | 1722 | "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], |
| 1694 | 1723 | |
| 1724 | + "msw/type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="], | |
| 1725 | + | |
| 1695 | 1726 | "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], |
| 1696 | 1727 | |
| 1697 | 1728 | "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], |
| @@ -1734,6 +1765,8 @@ · 36 unmodified lines | ||
| 1734 | 1765 | |
| 1735 | 1766 | "@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], |
| 1736 | 1767 | |
| 1768 | + "@redocly/openapi-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], | |
| 1769 | + | |
| 1737 | 1770 | "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], |
| 1738 | 1771 | |
| 1739 | 1772 | "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], |