History

Add TypeScript typechecking for LLM-generated code before execution

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.

Rhys Sullivan committed 9 months ago

Commit f153aeb1 parent 91abe7a6 files · +549 −24Browse files

Unified · Split

6 files
  1. executor/apps/server/package.json +2 −1
  2. executor/apps/server/src/mcp-server.ts +33 −11
  3. executor/apps/server/src/tool-sources.ts +53 −8
  4. executor/apps/server/src/typechecker.test.ts +250 −0
  5. executor/apps/server/src/typechecker.ts +174 −0
  6. executor/bun.lock +37 −4
executor/apps/server/package.json+2−1
@@ -9,6 +9,7 @@ · 8 unmodified lines
99 "@vercel/sandbox": "latest",
1010 "convex": "latest",
1111 "elysia": "^1.4.22",
12+ "openapi-typescript": "^7.10.1",
1213 "zod": "^4.3.6"
1314 },
1415 "scripts": {
@@ -21,6 +22,6 @@ · 6 unmodified lines
2122 },
2223 "devDependencies": {
2324 "@types/bun": "latest",
24− "typescript": "latest"
25+ "typescript": "^5.9.3"
2526 }
2627 }
executor/apps/server/src/mcp-server.ts+33−11
@@ -2,6 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; · 1 unmodified line
22 import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
33 import { z } from "zod";
44 import { getTaskTerminalState } from "./service";
5+import { generateToolDeclarations, generateToolInventory, typecheckCode } from "./typechecker";
56 import type { LiveTaskEvent } from "./events";
67 import type { AnonymousContext, CreateTaskInput, TaskRecord, ToolDescriptor } from "./types";
78
@@ -113,18 +114,9 @@ function waitForTerminalTask( · 105 unmodified lines
113114
114115 function buildRunCodeDescription(tools?: ToolDescriptor[]): string {
115116 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.";
117118
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 ?? []);
128120 }
129121
130122 // ---------------------------------------------------------------------------
@@ -177,6 +169,36 @@ function createRunCodeTool( · 46 unmodified lines
177169 };
178170 }
179171
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+
180202 const created = await service.createTask({
181203 code: input.code,
182204 timeoutMs: input.timeoutMs,
executor/apps/server/src/tool-sources.ts+53−8
@@ -2,6 +2,7 @@ import SwaggerParser from "@apidevtools/swagger-parser"; · 1 unmodified line
22 import { Client } from "@modelcontextprotocol/sdk/client/index.js";
33 import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
44 import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5+import { astToString, transformSchemaObject } from "openapi-typescript";
56 import type { ToolApprovalMode, ToolCredentialSpec, ToolDefinition } from "./types";
67
78 type JsonSchema = Record<string, unknown>;
@@ -65,7 +66,51 @@ function sanitizeSegment(value: string): string { · 57 unmodified lines
6566 return cleaned.length > 0 ? cleaned : "default";
6667 }
6768
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 {
69114 if (!schema || typeof schema !== "object") return "unknown";
70115 if (depth > 4) return "unknown";
71116
@@ -77,12 +122,12 @@ function jsonSchemaTypeHint(schema: unknown, depth = 0): string { · 5 unmodified lines
77122
78123 const oneOf = Array.isArray(shape.oneOf) ? shape.oneOf : undefined;
79124 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(" | ");
81126 }
82127
83128 const anyOf = Array.isArray(shape.anyOf) ? shape.anyOf : undefined;
84129 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(" | ");
86131 }
87132
88133 const type = typeof shape.type === "string" ? shape.type : undefined;
@@ -91,7 +136,7 @@ function jsonSchemaTypeHint(schema: unknown, depth = 0): string { · 2 unmodified lines
91136 }
92137
93138 if (type === "array") {
94− return `${jsonSchemaTypeHint(shape.items, depth + 1)}[]`;
139+ return `${jsonSchemaTypeHintFallback(shape.items, depth + 1)}[]`;
95140 }
96141
97142 const props = toObject(shape.properties);
@@ -104,7 +149,7 @@ function jsonSchemaTypeHint(schema: unknown, depth = 0): string { · 6 unmodified lines
104149 }
105150 const inner = propEntries
106151 .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)}`)
108153 .join("; ");
109154 return `{ ${inner} }`;
110155 }
@@ -191,7 +236,7 @@ async function loadMcpTools(config: McpToolSourceConfig): Promise<ToolDefinition · 80 unmodified lines
191236 approval: config.overrides?.[toolName]?.approval ?? config.defaultApproval ?? "auto",
192237 description: String(tool.description ?? `MCP tool ${toolName}`),
193238 metadata: {
194− argsType: jsonSchemaTypeHint(inputSchema),
239+ argsType: jsonSchemaToTypeString(inputSchema),
195240 returnsType: "unknown",
196241 },
197242 run: async (input: unknown) => {
@@ -390,8 +435,8 @@ async function loadOpenApiTools(config: OpenApiToolSourceConfig): Promise<ToolDe · 192 unmodified lines
390435 approval,
391436 description: String(operation.summary ?? operation.description ?? `${method.toUpperCase()} ${pathTemplate}`),
392437 metadata: {
393− argsType: jsonSchemaTypeHint(combinedSchema),
394− returnsType: jsonSchemaTypeHint(responseSchema),
438+ argsType: jsonSchemaToTypeString(combinedSchema),
439+ returnsType: jsonSchemaToTypeString(responseSchema),
395440 },
396441 credential: credentialSpec,
397442 run: async (input: unknown, context) => {
executor/apps/server/src/typechecker.test.ts+250−0
@@ -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+});
executor/apps/server/src/typechecker.ts+174−0
@@ -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+}
executor/bun.lock+37−4
@@ -23,11 +23,12 @@ · 22 unmodified lines
2323 "@vercel/sandbox": "latest",
2424 "convex": "latest",
2525 "elysia": "^1.4.22",
26+ "openapi-typescript": "^7.10.1",
2627 "zod": "^4.3.6",
2728 },
2829 "devDependencies": {
2930 "@types/bun": "latest",
30− "typescript": "latest",
31+ "typescript": "^5.9.3",
3132 },
3233 },
3334 "apps/web": {
@@ -486,6 +487,12 @@ · 452 unmodified lines
486487
487488 "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
488489
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+
489496 "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
490497
491498 "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
@@ -630,6 +637,8 @@ · 138 unmodified lines
630637
631638 "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
632639
640+ "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
641+
633642 "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
634643
635644 "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -708,6 +717,8 @@ · 72 unmodified lines
708717
709718 "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
710719
720+ "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="],
721+
711722 "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
712723
713724 "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
728739
729740 "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
730741
742+ "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="],
743+
731744 "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
732745
733746 "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
@@ -1020,6 +1033,8 @@ · 286 unmodified lines
10201033
10211034 "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
10221035
1036+ "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="],
1037+
10231038 "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
10241039
10251040 "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
11181133
11191134 "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
11201135
1136+ "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="],
1137+
11211138 "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
11221139
11231140 "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
12761293
12771294 "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
12781295
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+
12791298 "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=="],
12801299
12811300 "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
12941313
12951314 "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
12961315
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=="],
12981317
12991318 "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
13001319
@@ -1316,6 +1335,8 @@ · 15 unmodified lines
13161335
13171336 "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
13181337
1338+ "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="],
1339+
13191340 "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
13201341
13211342 "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
14821503
14831504 "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=="],
14841505
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=="],
14861507
14871508 "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
14881509
@@ -1528,7 +1549,7 @@ · 39 unmodified lines
15281549
15291550 "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
15301551
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=="],
15321553
15331554 "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=="],
15341555
@@ -1606,6 +1627,8 @@ · 71 unmodified lines
16061627
16071628 "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
16081629
1630+ "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="],
1631+
16091632 "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=="],
16101633
16111634 "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
@@ -1636,6 +1659,8 @@ · 24 unmodified lines
16361659
16371660 "@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=="],
16381661
1662+ "@redocly/openapi-core/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
1663+
16391664 "@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=="],
16401665
16411666 "@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
16601685
16611686 "bun-types/@types/node": ["@types/node@25.2.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg=="],
16621687
1688+ "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
1689+
16631690 "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=="],
16641691
16651692 "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -1668,6 +1695,8 @@ · 2 unmodified lines
16681695
16691696 "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=="],
16701697
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+
16711700 "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=="],
16721701
16731702 "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -1692,6 +1721,8 @@ · 18 unmodified lines
16921721
16931722 "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
16941723
1724+ "msw/type-fest": ["type-fest@5.4.3", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA=="],
1725+
16951726 "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=="],
16961727
16971728 "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
@@ -1734,6 +1765,8 @@ · 36 unmodified lines
17341765
17351766 "@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
17361767
1768+ "@redocly/openapi-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
1769+
17371770 "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
17381771
17391772 "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],