Unified · Split
| @@ -1,6 +1,6 @@ | |||
| 1 | -- Extraction used to persist an NDJSON operation's PER-LINE response schema | 1 | -- Extraction used to persist an NDJSON operation's PER-LINE response schema |
| 2 | -- as the tool's output schema, while the invoke path returns an ARRAY of | 2 | -- as the tool's output schema, while the invoke path returns an ARRAY of |
| 3 | -- parsed lines — so describe previews promised a single object invocations | 3 | -- parsed lines, so describe previews promised a single object invocations |
| 4 | -- never returned. The producer now wraps those schemas in an array. Persisted | 4 | -- never returned. The producer now wraps those schemas in an array. Persisted |
| 5 | -- `tool` rows can't be recognized as NDJSON-derived from their schema alone, | 5 | -- `tool` rows can't be recognized as NDJSON-derived from their schema alone, |
| 6 | -- but the stored operation bindings kept the response content type, so the | 6 | -- but the stored operation bindings kept the response content type, so the |
| @@ -4,7 +4,7 @@ · 3 unmodified lines | |||
| 4 | // NDJSON endpoints (Vercel's getRuntimeLogs is the motivating real case) are | 4 | // NDJSON endpoints (Vercel's getRuntimeLogs is the motivating real case) are |
| 5 | // spec'd with a PER-LINE schema under `application/stream+json`: the schema | 5 | // spec'd with a PER-LINE schema under `application/stream+json`: the schema |
| 6 | // describes one log line, the body is many of them. Executor's invoke path | 6 | // describes one log line, the body is many of them. Executor's invoke path |
| 7 | // already understands this — it collects the stream and returns an ARRAY of | 7 | // already understands this: it collects the stream and returns an ARRAY of |
| 8 | // parsed rows. But extraction stores the per-line schema as the operation's | 8 | // parsed rows. But extraction stores the per-line schema as the operation's |
| 9 | // outputSchema unchanged, so `tools.schema` / `tools.describe.tool()` advertise | 9 | // outputSchema unchanged, so `tools.schema` / `tools.describe.tool()` advertise |
| 10 | // `data: { level; message; ... }` (a single object) while the runtime value is | 10 | // `data: { level; message; ... }` (a single object) while the runtime value is |
| @@ -31,7 +31,7 @@ const api = composePluginApi([openApiHttpPlugin()] as const); · 20 unmodified lines | |||
| 31 | 31 | ||
| 32 | const unique = (prefix: string) => `${prefix}_${randomBytes(4).toString("hex")}`; | 32 | const unique = (prefix: string) => `${prefix}_${randomBytes(4).toString("hex")}`; |
| 33 | 33 | ||
| 34 | // Three real-shaped runtime-log lines, one JSON document per line — exactly | 34 | // Three real-shaped runtime-log lines, one JSON document per line, exactly |
| 35 | // what Vercel's runtime-logs endpoint sends over application/stream+json. | 35 | // what Vercel's runtime-logs endpoint sends over application/stream+json. |
| 36 | const LOG_LINES = [ | 36 | const LOG_LINES = [ |
| 37 | { level: "info", message: "build started", rowId: "r1", timestampInMs: 1_700_000_000_000 }, | 37 | { level: "info", message: "build started", rowId: "r1", timestampInMs: 1_700_000_000_000 }, |
| @@ -63,7 +63,7 @@ const serveNdjsonLogs = Effect.acquireRelease( · 25 unmodified lines | |||
| 63 | (server) => Effect.sync(server.close), | 63 | (server) => Effect.sync(server.close), |
| 64 | ); | 64 | ); |
| 65 | 65 | ||
| 66 | // The response declares the schema of ONE line under application/stream+json — | 66 | // The response declares the schema of ONE line under application/stream+json, |
| 67 | // the same convention Vercel's spec uses for getRuntimeLogs. | 67 | // the same convention Vercel's spec uses for getRuntimeLogs. |
| 68 | const runtimeLogsSpec = (baseUrl: string): string => | 68 | const runtimeLogsSpec = (baseUrl: string): string => |
| 69 | JSON.stringify({ | 69 | JSON.stringify({ |
| @@ -219,7 +219,7 @@ scenario( · 149 unmodified lines | |||
| 219 | 219 | ||
| 220 | // THE CONTRACT UNDER TEST: the advertised type must describe that | 220 | // THE CONTRACT UNDER TEST: the advertised type must describe that |
| 221 | // array. Today extraction stores the per-line schema unwrapped, so | 221 | // array. Today extraction stores the per-line schema unwrapped, so |
| 222 | // the typedef claims `data` is a single log-line object — the exact | 222 | // the typedef claims `data` is a single log-line object, the exact |
| 223 | // mismatch that pushes agents into JSON.stringify shape-probing. | 223 | // mismatch that pushes agents into JSON.stringify shape-probing. |
| 224 | const advertised = schema.outputTypeScript ?? ""; | 224 | const advertised = schema.outputTypeScript ?? ""; |
| 225 | expect( | 225 | expect( |
| @@ -229,7 +229,7 @@ scenario( · 3 unmodified lines | |||
| 229 | ` runtime: Array<{ level; message; rowId; timestampInMs }> (${rows.length} rows)`, | 229 | ` runtime: Array<{ level; message; rowId; timestampInMs }> (${rows.length} rows)`, |
| 230 | ).toBe(true); | 230 | ).toBe(true); |
| 231 | 231 | ||
| 232 | // And the sandbox's describe.tool must tell the same story — it is | 232 | // And the sandbox's describe.tool must tell the same story: it is |
| 233 | // the surface agents actually consult before writing code. | 233 | // the surface agents actually consult before writing code. |
| 234 | const sandboxAdvertised = String(invoked.outputTypeScript ?? ""); | 234 | const sandboxAdvertised = String(invoked.outputTypeScript ?? ""); |
| 235 | expect( | 235 | expect( |
| @@ -238,7 +238,7 @@ scenario( · 2 unmodified lines | |||
| 238 | ` advertised: ${sandboxAdvertised}`, | 238 | ` advertised: ${sandboxAdvertised}`, |
| 239 | ).toBe(true); | 239 | ).toBe(true); |
| 240 | }), | 240 | }), |
| 241 | // Selfhost shares one workspace identity — leaked connections fail | 241 | // Selfhost shares one workspace identity; leaked connections fail |
| 242 | // other scenarios' zero-state assertions. | 242 | // other scenarios' zero-state assertions. |
| 243 | Effect.gen(function* () { | 243 | Effect.gen(function* () { |
| 244 | yield* executeJson(session, removeConnectionsCode(slug)).pipe(Effect.ignore); | 244 | yield* executeJson(session, removeConnectionsCode(slug)).pipe(Effect.ignore); |
| @@ -93,7 +93,7 @@ describe("OpenAPI extract response bodies", () => { · 92 unmodified lines | |||
| 93 | // NDJSON endpoints (e.g. Vercel's runtime-logs) declare the schema of | 93 | // NDJSON endpoints (e.g. Vercel's runtime-logs) declare the schema of |
| 94 | // ONE line under application/stream+json; the invoke path returns an | 94 | // ONE line under application/stream+json; the invoke path returns an |
| 95 | // array of parsed lines, so the advertised output schema must be that | 95 | // array of parsed lines, so the advertised output schema must be that |
| 96 | // array — not the per-line object. | 96 | // array, not the per-line object. |
| 97 | const lineSchema = { | 97 | const lineSchema = { |
| 98 | type: "object", | 98 | type: "object", |
| 99 | properties: { level: { type: "string" }, message: { type: "string" } }, | 99 | properties: { level: { type: "string" }, message: { type: "string" } }, |
| @@ -265,7 +265,7 @@ const extractResponseBody = ( · 264 unmodified lines | |||
| 265 | * Derive an operation's output schema from its response body. NDJSON bodies | 265 | * Derive an operation's output schema from its response body. NDJSON bodies |
| 266 | * (`application/stream+json` and friends) are spec'd per LINE but returned by | 266 | * (`application/stream+json` and friends) are spec'd per LINE but returned by |
| 267 | * the invoke path as an array of parsed lines, so the advertised schema wraps | 267 | * the invoke path as an array of parsed lines, so the advertised schema wraps |
| 268 | * the line schema in an array — otherwise describe previews promise a single | 268 | * the line schema in an array; otherwise describe previews promise a single |
| 269 | * object that invocations never return. Used by both the whole-tree extract | 269 | * object that invocations never return. Used by both the whole-tree extract |
| 270 | * and the serve path's stored-binding rebuild so the two stay in lockstep. | 270 | * and the serve path's stored-binding rebuild so the two stay in lockstep. |
| 271 | */ | 271 | */ |
| @@ -3,7 +3,7 @@ · 2 unmodified lines | |||
| 3 | // | 3 | // |
| 4 | // Extraction used to persist an NDJSON operation's PER-LINE response schema as | 4 | // Extraction used to persist an NDJSON operation's PER-LINE response schema as |
| 5 | // the tool's output schema, while the invoke path returns an ARRAY of parsed | 5 | // the tool's output schema, while the invoke path returns an ARRAY of parsed |
| 6 | // lines — so describe previews promised a single object that invocations never | 6 | // lines, so describe previews promised a single object that invocations never |
| 7 | // returned. The producer now wraps those schemas in an array; the per-line | 7 | // returned. The producer now wraps those schemas in an array; the per-line |
| 8 | // shape can't be recognized from a persisted `tool` row alone (it's just a | 8 | // shape can't be recognized from a persisted `tool` row alone (it's just a |
| 9 | // JSON schema), but the stored operation bindings kept the response content | 9 | // JSON schema), but the stored operation bindings kept the response content |
| @@ -52,7 +52,7 @@ const tableExists = (client: SqliteDataMigrationClient, table: string) => · 42 unmodified lines | |||
| 52 | /** Stale-mark every connection whose integration has at least one stored | 52 | /** Stale-mark every connection whose integration has at least one stored |
| 53 | * operation binding with an NDJSON response, so the next read rebuilds its | 53 | * operation binding with an NDJSON response, so the next read rebuilds its |
| 54 | * tool rows (with array-wrapped output schemas). Returns the number of | 54 | * tool rows (with array-wrapped output schemas). Returns the number of |
| 55 | * connections marked. Fresh databases may lack either table — nothing to | 55 | * connections marked. Fresh databases may lack either table; nothing to |
| 56 | * migrate. */ | 56 | * migrate. */ |
| 57 | export const runSqliteNdjsonOutputMigration = ( | 57 | export const runSqliteNdjsonOutputMigration = ( |
| 58 | client: SqliteDataMigrationClient, | 58 | client: SqliteDataMigrationClient, |
| @@ -138,7 +138,7 @@ export const normalizeMediaType = (mediaType: string | null | undefined): string · 137 unmodified lines | |||
| 138 | 138 | ||
| 139 | /** Media types whose bodies are newline-delimited JSON documents. The invoke | 139 | /** Media types whose bodies are newline-delimited JSON documents. The invoke |
| 140 | * path collects these streams and returns an ARRAY of parsed lines, so every | 140 | * path collects these streams and returns an ARRAY of parsed lines, so every |
| 141 | * schema surface must describe that array — the spec convention is to declare | 141 | * schema surface must describe that array: the spec convention is to declare |
| 142 | * the schema of ONE line (e.g. Vercel's runtime-logs endpoint). */ | 142 | * the schema of ONE line (e.g. Vercel's runtime-logs endpoint). */ |
| 143 | export const NDJSON_MEDIA_TYPES: ReadonlySet<string> = new Set([ | 143 | export const NDJSON_MEDIA_TYPES: ReadonlySet<string> = new Set([ |
| 144 | "application/stream+json", | 144 | "application/stream+json", |
| @@ -279,7 +279,7 @@ describe("OpenAPI streaming responses", () => { · 278 unmodified lines | |||
| 279 | // The spec declares the schema of ONE line (Vercel's runtime-logs | 279 | // The spec declares the schema of ONE line (Vercel's runtime-logs |
| 280 | // convention); the runtime returns an array of parsed lines. The | 280 | // convention); the runtime returns an array of parsed lines. The |
| 281 | // advertised output schema must describe that array on BOTH serve | 281 | // advertised output schema must describe that array on BOTH serve |
| 282 | // paths — the add-time compile and the stored-binding rebuild — or | 282 | // paths (the add-time compile and the stored-binding rebuild) or |
| 283 | // agents write code against a shape that never comes back. | 283 | // agents write code against a shape that never comes back. |
| 284 | const lineSchema = { | 284 | const lineSchema = { |
| 285 | type: "object", | 285 | type: "object", |