- Bump version in package.json - Add tests for completed MCP return values in server.test.ts - Introduce new source templates for Neon MCP and Neon API in source-templates.ts - Refactor OpenAPI response handling in openapi-tools.ts and related tests - Improve result preview formatting in executor-mcp
Unified · Split
| @@ -1,6 +1,6 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "executor", | 2 | "name": "executor", |
| 3 | "version": "1.1.5", | 3 | "version": "1.1.6", |
| 4 | "description": "Local AI executor with a CLI, local API server, and web UI.", | 4 | "description": "Local AI executor with a CLI, local API server, and web UI.", |
| 5 | "keywords": [ | 5 | "keywords": [ |
| 6 | "executor", | 6 | "executor", |
| @@ -625,6 +625,39 @@ describe("local-executor-server", () => { · 624 unmodified lines | |||
| 625 | }), | 625 | }), |
| 626 | ); | 626 | ); |
| 627 | 627 | ||
| 628 | it.scoped("includes completed MCP return values in text content", () => | ||
| 629 | Effect.gen(function* () { | ||
| 630 | const server = yield* makeServer; | ||
| 631 | const mcp = yield* makeExecutorMcpClient({ | ||
| 632 | baseUrl: server.baseUrl, | ||
| 633 | }); | ||
| 634 | |||
| 635 | const executed = yield* Effect.promise( | ||
| 636 | () => | ||
| 637 | mcp.client.callTool({ | ||
| 638 | name: "execute", | ||
| 639 | arguments: { | ||
| 640 | code: "return await tools.math.add({ a: 20, b: 22 });", | ||
| 641 | }, | ||
| 642 | }) as Promise<{ | ||
| 643 | content?: Array<{ | ||
| 644 | type?: string; | ||
| 645 | text?: string; | ||
| 646 | }>; | ||
| 647 | structuredContent?: { | ||
| 648 | status?: string; | ||
| 649 | result?: unknown; | ||
| 650 | }; | ||
| 651 | }>, | ||
| 652 | ); | ||
| 653 | |||
| 654 | expect(executed.structuredContent?.status).toBe("completed"); | ||
| 655 | expect(executed.structuredContent?.result).toEqual({ sum: 42 }); | ||
| 656 | expect(executed.content?.find((item) => item.type === "text")?.text).toContain("Result:"); | ||
| 657 | expect(executed.content?.find((item) => item.type === "text")?.text).toContain('"sum": 42'); | ||
| 658 | }), | ||
| 659 | ); | ||
| 660 | |||
| 628 | it.scoped("serves only execute over MCP when elicitation is supported", () => | 661 | it.scoped("serves only execute over MCP when elicitation is supported", () => |
| 629 | Effect.gen(function* () { | 662 | Effect.gen(function* () { |
| 630 | const server = yield* createLocalExecutorServer({ | 663 | const server = yield* createLocalExecutorServer({ |
| @@ -665,6 +698,10 @@ describe("local-executor-server", () => { · 34 unmodified lines | |||
| 665 | code: 'return await tools.demo.gated_echo({ value: "from-mcp" });', | 698 | code: 'return await tools.demo.gated_echo({ value: "from-mcp" });', |
| 666 | }, | 699 | }, |
| 667 | }) as Promise<{ | 700 | }) as Promise<{ |
| 701 | content?: Array<{ | ||
| 702 | type?: string; | ||
| 703 | text?: string; | ||
| 704 | }>; | ||
| 668 | structuredContent?: { | 705 | structuredContent?: { |
| 669 | status?: string; | 706 | status?: string; |
| 670 | result?: unknown; | 707 | result?: unknown; |
| @@ -674,6 +711,7 @@ describe("local-executor-server", () => { · 3 unmodified lines | |||
| 674 | 711 | ||
| 675 | expect(executed.structuredContent?.status).toBe("completed"); | 712 | expect(executed.structuredContent?.status).toBe("completed"); |
| 676 | expect(executed.structuredContent?.result).toBe("approved:from-mcp"); | 713 | expect(executed.structuredContent?.result).toBe("approved:from-mcp"); |
| 714 | expect(executed.content?.find((item) => item.type === "text")?.text).toContain("approved:from-mcp"); | ||
| 677 | }), | 715 | }), |
| 678 | ); | 716 | ); |
| 679 | 717 | ||
| @@ -734,6 +772,10 @@ describe("local-executor-server", () => { · 54 unmodified lines | |||
| 734 | }, | 772 | }, |
| 735 | }, | 773 | }, |
| 736 | }) as Promise<{ | 774 | }) as Promise<{ |
| 775 | content?: Array<{ | ||
| 776 | type?: string; | ||
| 777 | text?: string; | ||
| 778 | }>; | ||
| 737 | structuredContent?: { | 779 | structuredContent?: { |
| 738 | status?: string; | 780 | status?: string; |
| 739 | result?: unknown; | 781 | result?: unknown; |
| @@ -743,6 +785,7 @@ describe("local-executor-server", () => { · 3 unmodified lines | |||
| 743 | 785 | ||
| 744 | expect(resumed.structuredContent?.status).toBe("completed"); | 786 | expect(resumed.structuredContent?.status).toBe("completed"); |
| 745 | expect(resumed.structuredContent?.result).toBe("approved:manual-mcp"); | 787 | expect(resumed.structuredContent?.result).toBe("approved:manual-mcp"); |
| 788 | expect(resumed.content?.find((item) => item.type === "text")?.text).toContain("approved:manual-mcp"); | ||
| 746 | }), | 789 | }), |
| 747 | ); | 790 | ); |
| 748 | 791 | ||
| @@ -33,6 +33,21 @@ export const sourceTemplates: ReadonlyArray<SourceTemplate> = [ · 32 unmodified lines | |||
| 33 | kind: "mcp", | 33 | kind: "mcp", |
| 34 | endpoint: "https://mcp.axiom.co/mcp", | 34 | endpoint: "https://mcp.axiom.co/mcp", |
| 35 | }, | 35 | }, |
| 36 | { | ||
| 37 | id: "neon-mcp", | ||
| 38 | name: "Neon MCP", | ||
| 39 | summary: "Manage Postgres databases, branches, and queries via MCP.", | ||
| 40 | kind: "mcp", | ||
| 41 | endpoint: "https://mcp.neon.tech/mcp", | ||
| 42 | }, | ||
| 43 | { | ||
| 44 | id: "neon-api", | ||
| 45 | name: "Neon API", | ||
| 46 | summary: "Projects, branches, endpoints, databases, and API keys.", | ||
| 47 | kind: "openapi", | ||
| 48 | endpoint: "https://console.neon.tech/api/v2", | ||
| 49 | specUrl: "https://neon.com/api_spec/release/v2.json", | ||
| 50 | }, | ||
| 36 | { | 51 | { |
| 37 | id: "github-rest", | 52 | id: "github-rest", |
| 38 | name: "GitHub REST API", | 53 | name: "GitHub REST API", |
| @@ -305,13 +305,8 @@ describe("openapi-tools", () => { · 304 unmodified lines | |||
| 305 | ); | 305 | ); |
| 306 | 306 | ||
| 307 | expect(result).toEqual({ | 307 | expect(result).toEqual({ |
| 308 | ok: true, | 308 | full_name: "octocat/hello-world", |
| 309 | status: 200, | 309 | include: null, |
| 310 | headers: expect.any(Object), | ||
| 311 | body: { | ||
| 312 | full_name: "octocat/hello-world", | ||
| 313 | include: null, | ||
| 314 | }, | ||
| 315 | }); | 310 | }); |
| 316 | expect(server.requests).toHaveLength(1); | 311 | expect(server.requests).toHaveLength(1); |
| 317 | expect(server.requests[0]?.path).toBe("/repos/octocat/hello-world"); | 312 | expect(server.requests[0]?.path).toBe("/repos/octocat/hello-world"); |
| @@ -340,13 +335,8 @@ describe("openapi-tools", () => { · 22 unmodified lines | |||
| 340 | ); | 335 | ); |
| 341 | 336 | ||
| 342 | expect(getResult).toEqual({ | 337 | expect(getResult).toEqual({ |
| 343 | ok: true, | 338 | full_name: "octocat/hello-world", |
| 344 | status: 200, | 339 | include: "all", |
| 345 | headers: expect.any(Object), | ||
| 346 | body: { | ||
| 347 | full_name: "octocat/hello-world", | ||
| 348 | include: "all", | ||
| 349 | }, | ||
| 350 | }); | 340 | }); |
| 351 | 341 | ||
| 352 | const postResult = yield* Effect.promise(() => | 342 | const postResult = yield* Effect.promise(() => |
| @@ -358,15 +348,10 @@ describe("openapi-tools", () => { · 5 unmodified lines | |||
| 358 | ); | 348 | ); |
| 359 | 349 | ||
| 360 | expect(postResult).toEqual({ | 350 | expect(postResult).toEqual({ |
| 361 | ok: true, | 351 | created: true, |
| 362 | status: 201, | 352 | owner: "octocat", |
| 363 | headers: expect.any(Object), | 353 | repo: "hello-world", |
| 364 | body: { | 354 | body: { title: "Bug report" }, |
| 365 | created: true, | ||
| 366 | owner: "octocat", | ||
| 367 | repo: "hello-world", | ||
| 368 | body: { title: "Bug report" }, | ||
| 369 | }, | ||
| 370 | }); | 355 | }); |
| 371 | 356 | ||
| 372 | const missingBody = yield* Effect.either( | 357 | const missingBody = yield* Effect.either( |
| @@ -398,6 +383,50 @@ describe("openapi-tools", () => { · 25 unmodified lines | |||
| 398 | }), | 383 | }), |
| 399 | ); | 384 | ); |
| 400 | 385 | ||
| 386 | it.effect("throws on non-2xx OpenAPI responses", () => | ||
| 387 | Effect.gen(function* () { | ||
| 388 | const manifest = yield* extractOpenApiManifest("generated", generatedOpenApiSpec); | ||
| 389 | const httpClientLayer = Layer.succeed( | ||
| 390 | HttpClient.HttpClient, | ||
| 391 | HttpClient.make((request) => | ||
| 392 | Effect.sync(() => | ||
| 393 | HttpClientResponse.fromWeb( | ||
| 394 | request, | ||
| 395 | new Response( | ||
| 396 | JSON.stringify({ error: "missing" }), | ||
| 397 | { | ||
| 398 | status: 404, | ||
| 399 | headers: { | ||
| 400 | "content-type": "application/json", | ||
| 401 | }, | ||
| 402 | }, | ||
| 403 | ), | ||
| 404 | ), | ||
| 405 | ), | ||
| 406 | ), | ||
| 407 | ); | ||
| 408 | const tools = createOpenApiToolsFromManifest({ | ||
| 409 | manifest, | ||
| 410 | baseUrl: "https://example.com", | ||
| 411 | namespace: "source.generated", | ||
| 412 | httpClientLayer, | ||
| 413 | }); | ||
| 414 | const getRepo = resolveToolExecutor(tools, "source.generated.repos.getRepo"); | ||
| 415 | const failure = yield* Effect.either( | ||
| 416 | Effect.tryPromise({ | ||
| 417 | try: () => getRepo({ owner: "octocat", repo: "hello-world" }), | ||
| 418 | catch: (error: unknown) => | ||
| 419 | error instanceof Error ? error : new Error(String(error)), | ||
| 420 | }), | ||
| 421 | ); | ||
| 422 | |||
| 423 | assertTrue(failure._tag === "Left"); | ||
| 424 | if (failure._tag === "Left" && failure.left instanceof Error) { | ||
| 425 | assertInclude(failure.left.message, "HTTP 404"); | ||
| 426 | } | ||
| 427 | }), | ||
| 428 | ); | ||
| 429 | |||
| 401 | it.effect("preserves base-path URLs when invoking tools", () => | 430 | it.effect("preserves base-path URLs when invoking tools", () => |
| 402 | Effect.gen(function* () { | 431 | Effect.gen(function* () { |
| 403 | const manifest = yield* extractOpenApiManifest("generated", generatedOpenApiSpec); | 432 | const manifest = yield* extractOpenApiManifest("generated", generatedOpenApiSpec); |
| @@ -438,13 +467,8 @@ describe("openapi-tools", () => { · 34 unmodified lines | |||
| 438 | ); | 467 | ); |
| 439 | 468 | ||
| 440 | expect(result).toEqual({ | 469 | expect(result).toEqual({ |
| 441 | ok: true, | 470 | full_name: "octocat/hello-world", |
| 442 | status: 200, | 471 | include: null, |
| 443 | headers: expect.any(Object), | ||
| 444 | body: { | ||
| 445 | full_name: "octocat/hello-world", | ||
| 446 | include: null, | ||
| 447 | }, | ||
| 448 | }); | 472 | }); |
| 449 | 473 | ||
| 450 | expect(capturedUrl).toBe("https://example.com/api/v3/repos/octocat/hello-world"); | 474 | expect(capturedUrl).toBe("https://example.com/api/v3/repos/octocat/hello-world"); |
| @@ -488,13 +512,8 @@ describe("openapi-tools", () => { · 37 unmodified lines | |||
| 488 | ); | 512 | ); |
| 489 | 513 | ||
| 490 | expect(result).toEqual({ | 514 | expect(result).toEqual({ |
| 491 | ok: true, | 515 | full_name: "octocat/hello-world", |
| 492 | status: 200, | 516 | include: null, |
| 493 | headers: expect.any(Object), | ||
| 494 | body: { | ||
| 495 | full_name: "octocat/hello-world", | ||
| 496 | include: null, | ||
| 497 | }, | ||
| 498 | }); | 517 | }); |
| 499 | expect(server.requests).toHaveLength(1); | 518 | expect(server.requests).toHaveLength(1); |
| 500 | expect(server.requests[0]?.path).toBe("/repos/octocat/hello-world"); | 519 | expect(server.requests[0]?.path).toBe("/repos/octocat/hello-world"); |
| @@ -40,44 +40,6 @@ export class OpenApiToolInvocationError extends Data.TaggedError( · 39 unmodified lines | |||
| 40 | details: string | null; | 40 | details: string | null; |
| 41 | }> {} | 41 | }> {} |
| 42 | 42 | ||
| 43 | const BLOCKED_RESPONSE_HEADER_NAMES = new Set([ | ||
| 44 | "authorization", | ||
| 45 | "authentication-info", | ||
| 46 | "cookie", | ||
| 47 | "proxy-authenticate", | ||
| 48 | "proxy-authorization", | ||
| 49 | "set-cookie", | ||
| 50 | "set-cookie2", | ||
| 51 | "www-authenticate", | ||
| 52 | "x-api-key", | ||
| 53 | "x-auth-token", | ||
| 54 | "x-csrf-token", | ||
| 55 | ]); | ||
| 56 | |||
| 57 | const NOISY_RESPONSE_HEADER_NAMES = new Set([ | ||
| 58 | "alt-svc", | ||
| 59 | "cf-ray", | ||
| 60 | "server", | ||
| 61 | "traceparent", | ||
| 62 | "tracestate", | ||
| 63 | "via", | ||
| 64 | "x-cache", | ||
| 65 | "x-cache-hits", | ||
| 66 | "x-powered-by", | ||
| 67 | "x-request-id", | ||
| 68 | "x-runtime", | ||
| 69 | "x-served-by", | ||
| 70 | "x-trace-id", | ||
| 71 | ]); | ||
| 72 | |||
| 73 | const NOISY_RESPONSE_HEADER_PREFIXES = [ | ||
| 74 | "cf-", | ||
| 75 | "trace-", | ||
| 76 | "x-amz-cf-", | ||
| 77 | "x-b3-", | ||
| 78 | "x-cloud-trace-", | ||
| 79 | ]; | ||
| 80 | |||
| 81 | const isRecord = (value: unknown): value is Record<string, unknown> => | 43 | const isRecord = (value: unknown): value is Record<string, unknown> => |
| 82 | typeof value === "object" && value !== null && !Array.isArray(value); | 44 | typeof value === "object" && value !== null && !Array.isArray(value); |
| 83 | 45 | ||
| @@ -244,27 +206,7 @@ const normalizeHttpUrl = (value: string): string => { · 160 unmodified lines | |||
| 244 | } | 206 | } |
| 245 | }; | 207 | }; |
| 246 | 208 | ||
| 247 | const shouldDropResponseHeader = (headerName: string): boolean => | ||
| 248 | BLOCKED_RESPONSE_HEADER_NAMES.has(headerName) | ||
| 249 | || NOISY_RESPONSE_HEADER_NAMES.has(headerName) | ||
| 250 | || NOISY_RESPONSE_HEADER_PREFIXES.some((prefix) => headerName.startsWith(prefix)); | ||
| 251 | |||
| 252 | const sanitizeResponseHeaders = ( | ||
| 253 | headers: Readonly<Record<string, string>>, | ||
| 254 | ): Record<string, string> => { | ||
| 255 | const sanitized: Record<string, string> = {}; | ||
| 256 | |||
| 257 | for (const [rawName, rawValue] of Object.entries(headers)) { | ||
| 258 | const name = rawName.trim().toLowerCase(); | ||
| 259 | if (name.length === 0 || shouldDropResponseHeader(name)) { | ||
| 260 | continue; | ||
| 261 | } | ||
| 262 | 209 | ||
| 263 | sanitized[name] = rawValue.length > 4000 ? `${rawValue.slice(0, 4000)}...` : rawValue; | ||
| 264 | } | ||
| 265 | |||
| 266 | return sanitized; | ||
| 267 | }; | ||
| 268 | 210 | ||
| 269 | const decodeHttpClientResponseBody = ( | 211 | const decodeHttpClientResponseBody = ( |
| 270 | response: Awaited<ReturnType<HttpClient.HttpClient["execute"]>> extends Effect.Effect< | 212 | response: Awaited<ReturnType<HttpClient.HttpClient["execute"]>> extends Effect.Effect< |
| @@ -292,6 +234,28 @@ const decodeHttpClientResponseBody = ( · 21 unmodified lines | |||
| 292 | ); | 234 | ); |
| 293 | }; | 235 | }; |
| 294 | 236 | ||
| 237 | const summarizeHttpResponseBody = (body: unknown): string | null => { | ||
| 238 | if (body === null || body === undefined) { | ||
| 239 | return null; | ||
| 240 | } | ||
| 241 | |||
| 242 | if (typeof body === "string") { | ||
| 243 | const trimmed = body.trim(); | ||
| 244 | if (trimmed.length === 0) { | ||
| 245 | return null; | ||
| 246 | } | ||
| 247 | |||
| 248 | return trimmed.length > 500 ? `${trimmed.slice(0, 500)}...` : trimmed; | ||
| 249 | } | ||
| 250 | |||
| 251 | try { | ||
| 252 | const serialized = JSON.stringify(body); | ||
| 253 | return serialized.length > 500 ? `${serialized.slice(0, 500)}...` : serialized; | ||
| 254 | } catch { | ||
| 255 | return String(body); | ||
| 256 | } | ||
| 257 | }; | ||
| 258 | |||
| 295 | const inputSchemaFromTypingJson = (inputSchemaJson: string | undefined) => { | 259 | const inputSchemaFromTypingJson = (inputSchemaJson: string | undefined) => { |
| 296 | if (!inputSchemaJson) { | 260 | if (!inputSchemaJson) { |
| 297 | return unknownInputSchema; | 261 | return unknownInputSchema; |
| @@ -491,12 +455,15 @@ export const createOpenApiToolsFromManifest = ( · 193 unmodified lines | |||
| 491 | ); | 455 | ); |
| 492 | const body = yield* decodeHttpClientResponseBody(response); | 456 | const body = yield* decodeHttpClientResponseBody(response); |
| 493 | 457 | ||
| 494 | return { | 458 | if (response.status < 200 || response.status >= 300) { |
| 495 | ok: response.status >= 200 && response.status < 300, | 459 | throw new OpenApiToolInvocationError({ |
| 496 | status: response.status, | 460 | operation: "http_response", |
| 497 | headers: sanitizeResponseHeaders(response.headers), | 461 | message: `OpenAPI request failed with HTTP ${response.status}`, |
| 498 | body, | 462 | details: summarizeHttpResponseBody(body), |
| 499 | }; | 463 | }); |
| 464 | } | ||
| 465 | |||
| 466 | return body; | ||
| 500 | }).pipe( | 467 | }).pipe( |
| 501 | Effect.provide(httpClientLayer), | 468 | Effect.provide(httpClientLayer), |
| 502 | ), | 469 | ), |
| @@ -1136,15 +1136,11 @@ describe("workspace-execution-environment", () => { · 1135 unmodified lines | |||
| 1136 | repo: "ai", | 1136 | repo: "ai", |
| 1137 | }, | 1137 | }, |
| 1138 | })) as { | 1138 | })) as { |
| 1139 | status: number; | 1139 | ok: boolean; |
| 1140 | body: { | 1140 | full_name: string; |
| 1141 | ok: boolean; | ||
| 1142 | full_name: string; | ||
| 1143 | }; | ||
| 1144 | }; | 1141 | }; |
| 1145 | 1142 | ||
| 1146 | expect(invoked.status).toBe(200); | 1143 | expect(invoked).toEqual({ |
| 1147 | expect(invoked.body).toEqual({ | ||
| 1148 | ok: true, | 1144 | ok: true, |
| 1149 | full_name: "vercel/ai", | 1145 | full_name: "vercel/ai", |
| 1150 | }); | 1146 | }); |
| @@ -1383,15 +1379,11 @@ describe("workspace-execution-environment", () => { · 232 unmodified lines | |||
| 1383 | }, | 1379 | }, |
| 1384 | }, | 1380 | }, |
| 1385 | })) as { | 1381 | })) as { |
| 1386 | status: number; | 1382 | ok: boolean; |
| 1387 | body: { | 1383 | full_name: string; |
| 1388 | ok: boolean; | ||
| 1389 | full_name: string; | ||
| 1390 | }; | ||
| 1391 | }; | 1384 | }; |
| 1392 | 1385 | ||
| 1393 | expect(invoked.status).toBe(200); | 1386 | expect(invoked).toEqual({ |
| 1394 | expect(invoked.body).toEqual({ | ||
| 1395 | ok: true, | 1387 | ok: true, |
| 1396 | full_name: "vercel/ai", | 1388 | full_name: "vercel/ai", |
| 1397 | }); | 1389 | }); |
| @@ -77,6 +77,24 @@ const parseJsonValue = (value: string | null): unknown => { · 76 unmodified lines | |||
| 77 | } | 77 | } |
| 78 | }; | 78 | }; |
| 79 | 79 | ||
| 80 | const maxResultPreviewChars = 30_000; | ||
| 81 | |||
| 82 | const truncateText = (value: string, maxLength: number): string => | ||
| 83 | value.length > maxLength | ||
| 84 | ? `${value.slice(0, maxLength)}\n... [result preview truncated ${value.length - maxLength} chars]` | ||
| 85 | : value; | ||
| 86 | |||
| 87 | const formatResultPreview = (resultJson: string): string => { | ||
| 88 | try { | ||
| 89 | const parsed = JSON.parse(resultJson) as unknown; | ||
| 90 | const serialized = typeof parsed === "string" ? parsed : JSON.stringify(parsed, null, 2) ?? String(parsed); | ||
| 91 | return truncateText(serialized, maxResultPreviewChars); | ||
| 92 | } catch { | ||
| 93 | return truncateText(resultJson, maxResultPreviewChars); | ||
| 94 | } | ||
| 95 | }; | ||
| 96 | |||
| 97 | |||
| 80 | const parseInteractionPayload = ( | 98 | const parseInteractionPayload = ( |
| 81 | interaction: ExecutionInteraction, | 99 | interaction: ExecutionInteraction, |
| 82 | ): ParsedInteractionPayload | null => { | 100 | ): ParsedInteractionPayload | null => { |
| @@ -205,8 +223,13 @@ const loadExecuteDescription = (runtime: SqlControlPlaneRuntime): Promise<string · 122 unmodified lines | |||
| 205 | 223 | ||
| 206 | const summarizeExecution = (execution: ExecutionEnvelope["execution"]): string => { | 224 | const summarizeExecution = (execution: ExecutionEnvelope["execution"]): string => { |
| 207 | switch (execution.status) { | 225 | switch (execution.status) { |
| 208 | case "completed": | 226 | case "completed": { |
| 209 | return `Execution ${execution.id} completed.`; | 227 | if (execution.resultJson === null) { |
| 228 | return `Execution ${execution.id} completed.`; | ||
| 229 | } | ||
| 230 | |||
| 231 | return `Execution ${execution.id} completed.\nResult:\n${formatResultPreview(execution.resultJson)}`; | ||
| 232 | } | ||
| 210 | case "failed": | 233 | case "failed": |
| 211 | return execution.errorText | 234 | return execution.errorText |
| 212 | ? `Execution ${execution.id} failed: ${execution.errorText}` | 235 | ? `Execution ${execution.id} failed: ${execution.errorText}` |
| @@ -816,13 +816,8 @@ describe("source runtime", () => { · 815 unmodified lines | |||
| 816 | }, | 816 | }, |
| 817 | result: { | 817 | result: { |
| 818 | ok: true, | 818 | ok: true, |
| 819 | status: 200, | 819 | path: "/repos/vercel/ai", |
| 820 | headers: expect.any(Object), | 820 | authorization: "Bearer ghp_from_keychain", |
| 821 | body: { | ||
| 822 | ok: true, | ||
| 823 | path: "/repos/vercel/ai", | ||
| 824 | authorization: "Bearer ghp_from_keychain", | ||
| 825 | }, | ||
| 826 | }, | 821 | }, |
| 827 | }); | 822 | }); |
| 828 | expect(yield* discoveryBacked.executeDescription).toBe( | 823 | expect(yield* discoveryBacked.executeDescription).toBe( |