Unified · Split
| @@ -81,9 +81,9 @@ const readBindingStringArray = (source: Source, key: string): Array<string> | un · 80 unmodified lines | ||
| 81 | 81 | : undefined; |
| 82 | 82 | }; |
| 83 | 83 | |
| 84 | −const readBindingTransport = (source: Source): "auto" | "streamable-http" | "sse" | undefined => { | |
| 84 | +const readBindingTransport = (source: Source): "auto" | "streamable-http" | "sse" | "stdio" | undefined => { | |
| 85 | 85 | const candidate = source.binding.transport; |
| 86 | − return candidate === "auto" || candidate === "streamable-http" || candidate === "sse" | |
| 86 | + return candidate === "auto" || candidate === "streamable-http" || candidate === "sse" || candidate === "stdio" | |
| 87 | 87 | ? candidate |
| 88 | 88 | : undefined; |
| 89 | 89 | }; |
| @@ -106,6 +106,10 @@ const refreshPayloadFromSource = (source: Source): ConnectSourcePayload | null = · 16 unmodified lines | ||
| 106 | 106 | ...(readBindingTransport(source) ? { transport: readBindingTransport(source) } : {}), |
| 107 | 107 | queryParams: readBindingStringMap(source, "queryParams"), |
| 108 | 108 | headers: readBindingStringMap(source, "headers"), |
| 109 | + command: readBindingString(source, "command"), | |
| 110 | + args: readBindingStringArray(source, "args"), | |
| 111 | + env: readBindingStringMap(source, "env"), | |
| 112 | + cwd: readBindingString(source, "cwd"), | |
| 109 | 113 | }; |
| 110 | 114 | case "openapi": { |
| 111 | 115 | const specUrl = readBindingString(source, "specUrl"); |
| @@ -364,7 +368,12 @@ function ModelView(props: { · 252 unmodified lines | ||
| 364 | 368 | |
| 365 | 369 | const filteredTools = props.bundle.tools.filter((tool) => { |
| 366 | 370 | if (terms.length === 0) return true; |
| 367 | − const corpus = [tool.path, tool.method ?? ""] | |
| 371 | + const corpus = [ | |
| 372 | + tool.path, | |
| 373 | + tool.method ?? "", | |
| 374 | + tool.inputTypePreview ?? "", | |
| 375 | + tool.outputTypePreview ?? "", | |
| 376 | + ] | |
| 368 | 377 | .join(" ") |
| 369 | 378 | .toLowerCase(); |
| 370 | 379 | return terms.every((t) => corpus.includes(t)); |
| @@ -733,6 +742,14 @@ function ToolDetailPanel(props: { detail: SourceInspectionToolDetail }) { · 362 unmodified lines | ||
| 733 | 742 | </span> |
| 734 | 743 | )} |
| 735 | 744 | </div> |
| 745 | + <div className="mt-2 rounded-md border border-border bg-card/70 px-2.5 py-2"> | |
| 746 | + <div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60"> | |
| 747 | + TypeScript Signature | |
| 748 | + </div> | |
| 749 | + <pre className="overflow-x-auto whitespace-pre-wrap font-mono text-[12px] text-foreground"> | |
| 750 | + {detail.contract.callDeclaration} | |
| 751 | + </pre> | |
| 752 | + </div> | |
| 736 | 753 | </div> |
| 737 | 754 | </div> |
| 738 | 755 | </div> |
| @@ -740,6 +757,66 @@ function ToolDetailPanel(props: { detail: SourceInspectionToolDetail }) { · 1 unmodified line | ||
| 740 | 757 | {/* Body */} |
| 741 | 758 | <div className="flex-1 overflow-y-auto"> |
| 742 | 759 | <div className="px-5 py-4 space-y-4"> |
| 760 | + <section className="overflow-hidden rounded-lg border border-border bg-card/60"> | |
| 761 | + <div className="border-b border-border px-3 py-2 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70"> | |
| 762 | + Contract | |
| 763 | + </div> | |
| 764 | + <div className="grid grid-cols-1 gap-3 p-3 xl:grid-cols-2"> | |
| 765 | + <DocumentPanel | |
| 766 | + title="Input Type" | |
| 767 | + body={detail.contract.input.typeDeclaration} | |
| 768 | + lang="typescript" | |
| 769 | + empty="No input type." | |
| 770 | + /> | |
| 771 | + <DocumentPanel | |
| 772 | + title="Output Type" | |
| 773 | + body={detail.contract.output.typeDeclaration} | |
| 774 | + lang="typescript" | |
| 775 | + empty="No output type." | |
| 776 | + /> | |
| 777 | + </div> | |
| 778 | + </section> | |
| 779 | + | |
| 780 | + <section className="overflow-hidden rounded-lg border border-border bg-card/60"> | |
| 781 | + <div className="border-b border-border px-3 py-2 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70"> | |
| 782 | + Schemas | |
| 783 | + </div> | |
| 784 | + <div className="grid grid-cols-1 gap-3 p-3 xl:grid-cols-2"> | |
| 785 | + <DocumentPanel | |
| 786 | + title="Input Schema" | |
| 787 | + body={detail.contract.input.schemaJson} | |
| 788 | + lang="json" | |
| 789 | + empty="No input schema." | |
| 790 | + compact | |
| 791 | + /> | |
| 792 | + <DocumentPanel | |
| 793 | + title="Output Schema" | |
| 794 | + body={detail.contract.output.schemaJson} | |
| 795 | + lang="json" | |
| 796 | + empty="No output schema." | |
| 797 | + compact | |
| 798 | + /> | |
| 799 | + {detail.contract.input.exampleJson && ( | |
| 800 | + <DocumentPanel | |
| 801 | + title="Example Request" | |
| 802 | + body={detail.contract.input.exampleJson} | |
| 803 | + lang="json" | |
| 804 | + empty="" | |
| 805 | + compact | |
| 806 | + /> | |
| 807 | + )} | |
| 808 | + {detail.contract.output.exampleJson && ( | |
| 809 | + <DocumentPanel | |
| 810 | + title="Example Response" | |
| 811 | + body={detail.contract.output.exampleJson} | |
| 812 | + lang="json" | |
| 813 | + empty="" | |
| 814 | + compact | |
| 815 | + /> | |
| 816 | + )} | |
| 817 | + </div> | |
| 818 | + </section> | |
| 819 | + | |
| 743 | 820 | {detail.sections.map((section, index) => { |
| 744 | 821 | if (section.kind === "facts") { |
| 745 | 822 | return ( |
| @@ -885,6 +962,30 @@ function DiscoveryView(props: { · 139 unmodified lines | ||
| 885 | 962 | {item.description} |
| 886 | 963 | </p> |
| 887 | 964 | )} |
| 965 | + {(item.inputTypePreview || item.outputTypePreview) && ( | |
| 966 | + <div className="mt-2 grid grid-cols-1 gap-2 md:grid-cols-2"> | |
| 967 | + {item.inputTypePreview && ( | |
| 968 | + <div className="rounded-md border border-border/70 bg-background/70 px-2.5 py-2"> | |
| 969 | + <div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/55"> | |
| 970 | + Input | |
| 971 | + </div> | |
| 972 | + <div className="font-mono text-[11px] text-foreground/85 line-clamp-3"> | |
| 973 | + {item.inputTypePreview} | |
| 974 | + </div> | |
| 975 | + </div> | |
| 976 | + )} | |
| 977 | + {item.outputTypePreview && ( | |
| 978 | + <div className="rounded-md border border-border/70 bg-background/70 px-2.5 py-2"> | |
| 979 | + <div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground/55"> | |
| 980 | + Output | |
| 981 | + </div> | |
| 982 | + <div className="font-mono text-[11px] text-foreground/85 line-clamp-3"> | |
| 983 | + {item.outputTypePreview} | |
| 984 | + </div> | |
| 985 | + </div> | |
| 986 | + )} | |
| 987 | + </div> | |
| 988 | + )} | |
| 888 | 989 | </button> |
| 889 | 990 | ))} |
| 890 | 991 | </div> |
| @@ -27,12 +27,41 @@ const searchableTextForTool = (tool: ToolDescriptor): string => · 26 unmodified lines | ||
| 27 | 27 | tool.path, |
| 28 | 28 | tool.sourceKey, |
| 29 | 29 | tool.description ?? "", |
| 30 | − tool.inputTypePreview ?? "", | |
| 31 | − tool.outputTypePreview ?? "", | |
| 30 | + tool.contract?.inputTypePreview ?? "", | |
| 31 | + tool.contract?.outputTypePreview ?? "", | |
| 32 | 32 | ] |
| 33 | 33 | .join(" ") |
| 34 | 34 | .toLowerCase(); |
| 35 | 35 | |
| 36 | +const descriptorContract = ( | |
| 37 | + descriptor: ToolDescriptor, | |
| 38 | + includeSchemas: boolean, | |
| 39 | +): ToolDescriptor["contract"] | undefined => { | |
| 40 | + const contract = descriptor.contract; | |
| 41 | + if (!contract) { | |
| 42 | + return undefined; | |
| 43 | + } | |
| 44 | + | |
| 45 | + if (includeSchemas) { | |
| 46 | + return contract; | |
| 47 | + } | |
| 48 | + | |
| 49 | + return { | |
| 50 | + ...(contract.inputTypePreview !== undefined | |
| 51 | + ? { inputTypePreview: contract.inputTypePreview } | |
| 52 | + : {}), | |
| 53 | + ...(contract.outputTypePreview !== undefined | |
| 54 | + ? { outputTypePreview: contract.outputTypePreview } | |
| 55 | + : {}), | |
| 56 | + ...(contract.exampleInput !== undefined | |
| 57 | + ? { exampleInput: contract.exampleInput } | |
| 58 | + : {}), | |
| 59 | + ...(contract.exampleOutput !== undefined | |
| 60 | + ? { exampleOutput: contract.exampleOutput } | |
| 61 | + : {}), | |
| 62 | + }; | |
| 63 | +}; | |
| 64 | + | |
| 36 | 65 | const projectDescriptor = (input: { |
| 37 | 66 | descriptor: ToolDescriptor; |
| 38 | 67 | includeSchemas: boolean; |
| @@ -45,8 +74,9 @@ const projectDescriptor = (input: { · 6 unmodified lines | ||
| 45 | 74 | |
| 46 | 75 | return { |
| 47 | 76 | ...descriptor, |
| 48 | − inputSchema: undefined, | |
| 49 | − outputSchema: undefined, | |
| 77 | + ...(descriptorContract(descriptor, false) | |
| 78 | + ? { contract: descriptorContract(descriptor, false) } | |
| 79 | + : {}), | |
| 50 | 80 | }; |
| 51 | 81 | }; |
| 52 | 82 | |
| @@ -383,13 +413,8 @@ export function createDiscoveryPrimitivesFromToolCatalog(input: { · 330 unmodified lines | ||
| 383 | 413 | score: hit.score, |
| 384 | 414 | description: descriptor.description, |
| 385 | 415 | interaction: descriptor.interaction ?? "auto", |
| 386 | − inputTypePreview: descriptor.inputTypePreview, | |
| 387 | − outputTypePreview: descriptor.outputTypePreview, | |
| 388 | − ...(includeSchemas | |
| 389 | − ? { | |
| 390 | − inputSchema: descriptor.inputSchema, | |
| 391 | − outputSchema: descriptor.outputSchema, | |
| 392 | − } | |
| 416 | + ...(descriptorContract(descriptor, includeSchemas) | |
| 417 | + ? { contract: descriptorContract(descriptor, includeSchemas) } | |
| 393 | 418 | : {}), |
| 394 | 419 | }; |
| 395 | 420 | }) |
| @@ -451,7 +476,7 @@ export function buildDynamicExecuteDescription(input: { · 55 unmodified lines | ||
| 451 | 476 | "Workflow:", |
| 452 | 477 | '1) const matches = await tools.discover({ query: "<intent>", limit: 12 });', |
| 453 | 478 | "2) const details = await tools.describe.tool({ path, includeSchemas: true });", |
| 454 | − "3) Read details.inputSchema/details.outputSchema when you need the projected shape.", | |
| 479 | + "3) Read details.contract?.inputSchema/details.contract?.outputSchema when you need the projected shape.", | |
| 455 | 480 | "4) Call selected tools.<path>(input).", |
| 456 | 481 | "Do not use fetch; use tools.* only.", |
| 457 | 482 | ].join("\n"); |
| @@ -289,16 +289,20 @@ describe("codemode-core", () => { · 288 unmodified lines | ||
| 289 | 289 | path: asToolPath("source.docs.search"), |
| 290 | 290 | sourceKey: "source.docs", |
| 291 | 291 | description: "Search docs", |
| 292 | − inputTypePreview: "object", | |
| 293 | − outputTypePreview: "object", | |
| 292 | + contract: { | |
| 293 | + inputTypePreview: "object", | |
| 294 | + outputTypePreview: "object", | |
| 295 | + }, | |
| 294 | 296 | }, |
| 295 | 297 | "source.issues.create": { |
| 296 | 298 | path: asToolPath("source.issues.create"), |
| 297 | 299 | sourceKey: "source.issues", |
| 298 | 300 | description: "Create issue", |
| 299 | 301 | interaction: "required", |
| 300 | − inputTypePreview: "object", | |
| 301 | − outputTypePreview: "object", | |
| 302 | + contract: { | |
| 303 | + inputTypePreview: "object", | |
| 304 | + outputTypePreview: "object", | |
| 305 | + }, | |
| 302 | 306 | }, |
| 303 | 307 | }; |
| 304 | 308 | |
| @@ -327,8 +331,24 @@ describe("codemode-core", () => { · 22 unmodified lines | ||
| 327 | 331 | .slice(0, limit) |
| 328 | 332 | .map((descriptor) => ({ |
| 329 | 333 | ...descriptor, |
| 330 | − inputSchema: includeSchemas ? descriptor.inputSchema : undefined, | |
| 331 | − outputSchema: includeSchemas ? descriptor.outputSchema : undefined, | |
| 334 | + ...(descriptor.contract | |
| 335 | + ? { | |
| 336 | + contract: { | |
| 337 | + ...(descriptor.contract.inputTypePreview !== undefined | |
| 338 | + ? { inputTypePreview: descriptor.contract.inputTypePreview } | |
| 339 | + : {}), | |
| 340 | + ...(descriptor.contract.outputTypePreview !== undefined | |
| 341 | + ? { outputTypePreview: descriptor.contract.outputTypePreview } | |
| 342 | + : {}), | |
| 343 | + ...(includeSchemas && descriptor.contract.inputSchema !== undefined | |
| 344 | + ? { inputSchema: descriptor.contract.inputSchema } | |
| 345 | + : {}), | |
| 346 | + ...(includeSchemas && descriptor.contract.outputSchema !== undefined | |
| 347 | + ? { outputSchema: descriptor.contract.outputSchema } | |
| 348 | + : {}), | |
| 349 | + }, | |
| 350 | + } | |
| 351 | + : {}), | |
| 332 | 352 | })), |
| 333 | 353 | ), |
| 334 | 354 | getToolByPath: ({ path }) => |
| @@ -367,9 +387,11 @@ describe("codemode-core", () => { · 32 unmodified lines | ||
| 367 | 387 | path: asToolPath("github.repos.getRepo"), |
| 368 | 388 | sourceKey: "source.github", |
| 369 | 389 | description: "Get repository details", |
| 370 | − inputTypePreview: "{ owner: string; repo: string }", | |
| 371 | − outputTypePreview: "Repository", | |
| 372 | − inputSchema: { type: "object" }, | |
| 390 | + contract: { | |
| 391 | + inputTypePreview: "{ owner: string; repo: string }", | |
| 392 | + outputTypePreview: "Repository", | |
| 393 | + inputSchema: { type: "object" }, | |
| 394 | + }, | |
| 373 | 395 | }, |
| 374 | 396 | }, |
| 375 | 397 | { |
| @@ -383,8 +405,10 @@ describe("codemode-core", () => { · 7 unmodified lines | ||
| 383 | 405 | path: asToolPath("github.issues.list"), |
| 384 | 406 | sourceKey: "source.github", |
| 385 | 407 | description: "List repository issues", |
| 386 | − inputTypePreview: "{ owner: string; repo: string }", | |
| 387 | − outputTypePreview: "Issue[]", | |
| 408 | + contract: { | |
| 409 | + inputTypePreview: "{ owner: string; repo: string }", | |
| 410 | + outputTypePreview: "Issue[]", | |
| 411 | + }, | |
| 388 | 412 | }, |
| 389 | 413 | }, |
| 390 | 414 | ], |
| @@ -403,7 +427,7 @@ describe("codemode-core", () => { · 12 unmodified lines | ||
| 403 | 427 | includeSchemas: false, |
| 404 | 428 | }); |
| 405 | 429 | expect(listed[0]?.path).toBe("github.repos.getRepo"); |
| 406 | − expect(listed[0]?.inputSchema).toBeUndefined(); | |
| 430 | + expect(listed[0]?.contract?.inputSchema).toBeUndefined(); | |
| 407 | 431 | |
| 408 | 432 | const discovered = yield* catalog.searchTools({ |
| 409 | 433 | query: "repository issues", |
| @@ -415,7 +439,7 @@ describe("codemode-core", () => { · 5 unmodified lines | ||
| 415 | 439 | path: asToolPath("github.repos.getRepo"), |
| 416 | 440 | includeSchemas: true, |
| 417 | 441 | }); |
| 418 | − expect(described?.inputSchema).toEqual({ type: "object" }); | |
| 442 | + expect(described?.contract?.inputSchema).toEqual({ type: "object" }); | |
| 419 | 443 | }), |
| 420 | 444 | ); |
| 421 | 445 | |
| @@ -420,12 +420,26 @@ export function toolDescriptorsFromTools(input: { · 419 unmodified lines | ||
| 420 | 420 | const metadata = entry.metadata; |
| 421 | 421 | const definition = entry.tool; |
| 422 | 422 | const inputSchema = |
| 423 | − metadata?.inputSchema | |
| 423 | + metadata?.contract?.inputSchema | |
| 424 | 424 | ?? deriveSchemaValue(definition.inputSchema) |
| 425 | 425 | ?? deriveSchemaValue(definition.parameters); |
| 426 | 426 | const outputSchema = |
| 427 | − metadata?.outputSchema | |
| 427 | + metadata?.contract?.outputSchema | |
| 428 | 428 | ?? deriveSchemaValue(definition.outputSchema); |
| 429 | + const contract = { | |
| 430 | + inputTypePreview: | |
| 431 | + metadata?.contract?.inputTypePreview ?? inferTypeFromSchemaValue(inputSchema, "unknown"), | |
| 432 | + outputTypePreview: | |
| 433 | + metadata?.contract?.outputTypePreview ?? inferTypeFromSchemaValue(outputSchema, "unknown"), | |
| 434 | + ...(inputSchema !== undefined ? { inputSchema } : {}), | |
| 435 | + ...(outputSchema !== undefined ? { outputSchema } : {}), | |
| 436 | + ...(metadata?.contract?.exampleInput !== undefined | |
| 437 | + ? { exampleInput: metadata.contract.exampleInput } | |
| 438 | + : {}), | |
| 439 | + ...(metadata?.contract?.exampleOutput !== undefined | |
| 440 | + ? { exampleOutput: metadata.contract.exampleOutput } | |
| 441 | + : {}), | |
| 442 | + } satisfies NonNullable<ToolDescriptor["contract"]>; | |
| 429 | 443 | |
| 430 | 444 | return { |
| 431 | 445 | path: entry.path, |
| @@ -433,18 +447,7 @@ export function toolDescriptorsFromTools(input: { · 1 unmodified line | ||
| 433 | 447 | description: definition.description, |
| 434 | 448 | interaction: metadata?.interaction, |
| 435 | 449 | elicitation: metadata?.elicitation, |
| 436 | − inputTypePreview: | |
| 437 | − metadata?.inputTypePreview ?? inferTypeFromSchemaValue(inputSchema, "unknown"), | |
| 438 | − outputTypePreview: | |
| 439 | − metadata?.outputTypePreview ?? inferTypeFromSchemaValue(outputSchema, "unknown"), | |
| 440 | − ...(inputSchema !== undefined ? { inputSchema } : {}), | |
| 441 | − ...(outputSchema !== undefined ? { outputSchema } : {}), | |
| 442 | − ...(metadata?.exampleInput !== undefined | |
| 443 | − ? { exampleInput: metadata.exampleInput } | |
| 444 | − : {}), | |
| 445 | − ...(metadata?.exampleOutput !== undefined | |
| 446 | − ? { exampleOutput: metadata.exampleOutput } | |
| 447 | − : {}), | |
| 450 | + contract, | |
| 448 | 451 | ...(metadata?.providerKind ? { providerKind: metadata.providerKind } : {}), |
| 449 | 452 | ...(metadata?.providerData !== undefined |
| 450 | 453 | ? { providerData: metadata.providerData } |
| @@ -75,15 +75,28 @@ export type OnElicitation = ( · 74 unmodified lines | ||
| 75 | 75 | input: ToolElicitationRequest, |
| 76 | 76 | ) => Effect.Effect<ElicitationResponse, unknown>; |
| 77 | 77 | |
| 78 | −export type ToolMetadata = { | |
| 79 | − interaction?: "auto" | "required"; | |
| 80 | − elicitation?: ElicitationRequest; | |
| 78 | +export type ToolContract = { | |
| 81 | 79 | inputTypePreview?: string; |
| 82 | 80 | outputTypePreview?: string; |
| 83 | 81 | inputSchema?: unknown; |
| 84 | 82 | outputSchema?: unknown; |
| 85 | 83 | exampleInput?: unknown; |
| 86 | 84 | exampleOutput?: unknown; |
| 85 | +}; | |
| 86 | + | |
| 87 | +export const ToolContractSchema = Schema.Struct({ | |
| 88 | + inputTypePreview: Schema.optional(Schema.String), | |
| 89 | + outputTypePreview: Schema.optional(Schema.String), | |
| 90 | + inputSchema: Schema.optional(Schema.Unknown), | |
| 91 | + outputSchema: Schema.optional(Schema.Unknown), | |
| 92 | + exampleInput: Schema.optional(Schema.Unknown), | |
| 93 | + exampleOutput: Schema.optional(Schema.Unknown), | |
| 94 | +}); | |
| 95 | + | |
| 96 | +export type ToolMetadata = { | |
| 97 | + interaction?: "auto" | "required"; | |
| 98 | + elicitation?: ElicitationRequest; | |
| 99 | + contract?: ToolContract; | |
| 87 | 100 | sourceKey?: string; |
| 88 | 101 | providerKind?: string; |
| 89 | 102 | providerData?: unknown; |
| @@ -131,12 +144,7 @@ export type ToolDescriptor = { · 41 unmodified lines | ||
| 131 | 144 | description?: string; |
| 132 | 145 | interaction?: "auto" | "required"; |
| 133 | 146 | elicitation?: ElicitationRequest; |
| 134 | − inputTypePreview?: string; | |
| 135 | − outputTypePreview?: string; | |
| 136 | − inputSchema?: unknown; | |
| 137 | − outputSchema?: unknown; | |
| 138 | − exampleInput?: unknown; | |
| 139 | − exampleOutput?: unknown; | |
| 147 | + contract?: ToolContract; | |
| 140 | 148 | providerKind?: string; |
| 141 | 149 | providerData?: unknown; |
| 142 | 150 | }; |
| @@ -149,12 +157,7 @@ export const ToolDescriptorSchema = Schema.Struct({ · 6 unmodified lines | ||
| 149 | 157 | Schema.Union(Schema.Literal("auto"), Schema.Literal("required")), |
| 150 | 158 | ), |
| 151 | 159 | elicitation: Schema.optional(Schema.Unknown), |
| 152 | − inputTypePreview: Schema.optional(Schema.String), | |
| 153 | − outputTypePreview: Schema.optional(Schema.String), | |
| 154 | − inputSchema: Schema.optional(Schema.Unknown), | |
| 155 | − outputSchema: Schema.optional(Schema.Unknown), | |
| 156 | − exampleInput: Schema.optional(Schema.Unknown), | |
| 157 | − exampleOutput: Schema.optional(Schema.Unknown), | |
| 160 | + contract: Schema.optional(ToolContractSchema), | |
| 158 | 161 | providerKind: Schema.optional(Schema.String), |
| 159 | 162 | providerData: Schema.optional(Schema.Unknown), |
| 160 | 163 | }); |
| @@ -2,7 +2,12 @@ import { createHash } from "node:crypto"; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | import type { ProjectedCatalog } from "@executor/ir/catalog"; |
| 4 | 4 | import type { ShapeSymbolId } from "@executor/ir/ids"; |
| 5 | −import type { CatalogV1, ShapeNode, ShapeSymbol } from "@executor/ir/model"; | |
| 5 | +import type { | |
| 6 | + CatalogV1, | |
| 7 | + DocumentationBlock, | |
| 8 | + ShapeNode, | |
| 9 | + ShapeSymbol, | |
| 10 | +} from "@executor/ir/model"; | |
| 6 | 11 | |
| 7 | 12 | export type CatalogTypeRoot = { |
| 8 | 13 | readonly shapeId: ShapeSymbolId; |
| @@ -128,7 +133,9 @@ const objectTypeLiteral = ( · 119 unmodified lines | ||
| 128 | 133 | |
| 129 | 134 | return [ |
| 130 | 135 | "{", |
| 131 | − ...lines.map((line) => `${indent}${line}`), | |
| 136 | + ...lines.flatMap((line) => | |
| 137 | + line.split("\n").map((segment) => `${indent}${segment}`) | |
| 138 | + ), | |
| 132 | 139 | `${indent.slice(0, -2)}}`, |
| 133 | 140 | ].join("\n"); |
| 134 | 141 | }; |
| @@ -153,6 +160,73 @@ const isSyntheticShapeLabel = (value: string): boolean => · 18 unmodified lines | ||
| 153 | 160 | |
| 154 | 161 | const looksLikeHumanPhrase = (value: string): boolean => /\s/.test(value.trim()); |
| 155 | 162 | |
| 163 | +const cleanDocText = (value: string | undefined): string | null => { | |
| 164 | + const trimmed = value?.trim(); | |
| 165 | + return trimmed && trimmed.length > 0 ? trimmed : null; | |
| 166 | +}; | |
| 167 | + | |
| 168 | +const escapeJsDocText = (value: string): string => value.replace(/\*\//g, "*\\/"); | |
| 169 | + | |
| 170 | +const pushParagraphLines = ( | |
| 171 | + lines: Array<string>, | |
| 172 | + value: string | null, | |
| 173 | +): void => { | |
| 174 | + if (!value) { | |
| 175 | + return; | |
| 176 | + } | |
| 177 | + | |
| 178 | + if (lines.length > 0) { | |
| 179 | + lines.push(""); | |
| 180 | + } | |
| 181 | + | |
| 182 | + for (const line of value.split(/\r?\n/)) { | |
| 183 | + lines.push(escapeJsDocText(line.trimEnd())); | |
| 184 | + } | |
| 185 | +}; | |
| 186 | + | |
| 187 | +export const documentationComment = (input: { | |
| 188 | + title?: string; | |
| 189 | + docs?: DocumentationBlock; | |
| 190 | + deprecated?: boolean; | |
| 191 | + includeTitle?: boolean; | |
| 192 | +}): string | null => { | |
| 193 | + const lines: Array<string> = []; | |
| 194 | + const title = input.includeTitle && input.title && looksLikeHumanPhrase(input.title) | |
| 195 | + ? cleanDocText(input.title) | |
| 196 | + : null; | |
| 197 | + const summary = cleanDocText(input.docs?.summary); | |
| 198 | + const description = cleanDocText(input.docs?.description); | |
| 199 | + const externalDocsUrl = cleanDocText(input.docs?.externalDocsUrl); | |
| 200 | + | |
| 201 | + pushParagraphLines(lines, title && title !== summary ? title : null); | |
| 202 | + pushParagraphLines(lines, summary); | |
| 203 | + pushParagraphLines(lines, description && description !== summary ? description : null); | |
| 204 | + | |
| 205 | + if (externalDocsUrl) { | |
| 206 | + if (lines.length > 0) { | |
| 207 | + lines.push(""); | |
| 208 | + } | |
| 209 | + lines.push(`@see ${escapeJsDocText(externalDocsUrl)}`); | |
| 210 | + } | |
| 211 | + | |
| 212 | + if (input.deprecated) { | |
| 213 | + if (lines.length > 0) { | |
| 214 | + lines.push(""); | |
| 215 | + } | |
| 216 | + lines.push("@deprecated"); | |
| 217 | + } | |
| 218 | + | |
| 219 | + if (lines.length === 0) { | |
| 220 | + return null; | |
| 221 | + } | |
| 222 | + | |
| 223 | + return [ | |
| 224 | + "/**", | |
| 225 | + ...lines.map((line) => line.length > 0 ? ` * ${line}` : " *"), | |
| 226 | + " */", | |
| 227 | + ].join("\n"); | |
| 228 | +}; | |
| 229 | + | |
| 156 | 230 | const compactAliasHint = (value: string): string => { |
| 157 | 231 | const segments = typeNameWords(value); |
| 158 | 232 | if (segments.length <= 5) { |
| @@ -254,16 +328,27 @@ const renderObjectNode = ( · 95 unmodified lines | ||
| 254 | 328 | stack: readonly ShapeSymbolId[], |
| 255 | 329 | aliasHint: string | undefined, |
| 256 | 330 | renderShape: RenderShape, |
| 331 | + includeDocs: boolean, | |
| 257 | 332 | ): string => { |
| 258 | 333 | const required = new Set(node.required); |
| 259 | 334 | const lines = Object.keys(node.fields) |
| 260 | 335 | .sort((left, right) => left.localeCompare(right)) |
| 261 | − .map((key) => | |
| 262 | − `${formatPropertyKey(key)}${required.has(key) ? "" : "?"}: ${renderShape(node.fields[key]!.shapeId, { | |
| 263 | − stack, | |
| 264 | − aliasHint: aliasHint ? joinTypeNameSegments(aliasHint, key) : key, | |
| 265 | − })};` | |
| 266 | − ); | |
| 336 | + .map((key) => { | |
| 337 | + const field = node.fields[key]!; | |
| 338 | + const propertyLine = | |
| 339 | + `${formatPropertyKey(key)}${required.has(key) ? "" : "?"}: ${renderShape(field.shapeId, { | |
| 340 | + stack, | |
| 341 | + aliasHint: aliasHint ? joinTypeNameSegments(aliasHint, key) : key, | |
| 342 | + })};`; | |
| 343 | + const comment = includeDocs | |
| 344 | + ? documentationComment({ | |
| 345 | + docs: field.docs, | |
| 346 | + deprecated: field.deprecated, | |
| 347 | + }) | |
| 348 | + : null; | |
| 349 | + | |
| 350 | + return comment ? `${comment}\n${propertyLine}` : propertyLine; | |
| 351 | + }); | |
| 267 | 352 | |
| 268 | 353 | const patternShapeIds = Object.values(node.patternProperties ?? {}); |
| 269 | 354 | const hasUnknownIndex = node.additionalProperties === true; |
| @@ -290,6 +375,7 @@ const renderObjectFields = ( · 20 unmodified lines | ||
| 290 | 375 | stack: readonly ShapeSymbolId[], |
| 291 | 376 | aliasHint: string | undefined, |
| 292 | 377 | renderShape: RenderShape, |
| 378 | + includeDocs: boolean, | |
| 293 | 379 | ): string => |
| 294 | 380 | renderObjectNode( |
| 295 | 381 | { |
| @@ -301,6 +387,7 @@ const renderObjectFields = ( · 5 unmodified lines | ||
| 301 | 387 | stack, |
| 302 | 388 | aliasHint, |
| 303 | 389 | renderShape, |
| 390 | + includeDocs, | |
| 304 | 391 | ); |
| 305 | 392 | |
| 306 | 393 | const renderShapeBody = ( |
| @@ -309,6 +396,7 @@ const renderShapeBody = ( · 2 unmodified lines | ||
| 309 | 396 | stack: readonly ShapeSymbolId[], |
| 310 | 397 | aliasHint: string | undefined, |
| 311 | 398 | renderShape: RenderShape, |
| 399 | + includeDocs: boolean, | |
| 312 | 400 | ): string => { |
| 313 | 401 | const shape = getShapeSymbol(catalog, shapeId); |
| 314 | 402 | const node = shape.node; |
| @@ -321,7 +409,7 @@ const renderShapeBody = ( · 6 unmodified lines | ||
| 321 | 409 | return renderInlineShapeNode(node); |
| 322 | 410 | case "object": |
| 323 | 411 | case "graphqlInterface": |
| 324 | − return renderObjectFields(node, stack, aliasHint, renderShape); | |
| 412 | + return renderObjectFields(node, stack, aliasHint, renderShape, includeDocs); | |
| 325 | 413 | case "array": |
| 326 | 414 | return `Array<${wrapCompositeType(renderShape(node.itemShapeId, { |
| 327 | 415 | stack, |
| @@ -714,6 +802,7 @@ export const createCatalogTypeProjector = (input: { · 386 unmodified lines | ||
| 714 | 802 | stack: readonly ShapeSymbolId[], |
| 715 | 803 | aliasHint: string | undefined, |
| 716 | 804 | renderShape: RenderShape, |
| 805 | + includeDocs: boolean, | |
| 717 | 806 | ): string | null => { |
| 718 | 807 | const variants = shapeIds.map((shapeId) => resolveUnionVariantObject(shapeId)); |
| 719 | 808 | if (variants.some((variant) => variant === null)) { |
| @@ -769,7 +858,7 @@ export const createCatalogTypeProjector = (input: { · 49 unmodified lines | ||
| 769 | 858 | } |
| 770 | 859 | |
| 771 | 860 | const baseText = baseHasSharedStructure |
| 772 | − ? renderObjectNode(sharedNode, stack, aliasHint, renderShape) | |
| 861 | + ? renderObjectNode(sharedNode, stack, aliasHint, renderShape, includeDocs) | |
| 773 | 862 | : null; |
| 774 | 863 | |
| 775 | 864 | const variantTexts = objectVariants.map((variant, index) => { |
| @@ -786,6 +875,7 @@ export const createCatalogTypeProjector = (input: { · 10 unmodified lines | ||
| 786 | 875 | stack, |
| 787 | 876 | aliasHint ? joinTypeNameSegments(aliasHint, variantAliasLabel(discriminator, index)) : variantAliasLabel(discriminator, index), |
| 788 | 877 | renderShape, |
| 878 | + includeDocs, | |
| 789 | 879 | ); |
| 790 | 880 | }); |
| 791 | 881 | const unionText = variantTexts.map((variantText) => wrapCompositeType(variantText)).join(" | "); |
| @@ -857,15 +947,21 @@ export const createCatalogTypeProjector = (input: { · 65 unmodified lines | ||
| 857 | 947 | switch (shape.node.type) { |
| 858 | 948 | case "anyOf": |
| 859 | 949 | case "oneOf": { |
| 860 | − const normalized = normalizedUnionRender(shape.node.items, stack, aliasHint, renderDeclarationShape); | |
| 861 | − return normalized ?? renderShapeBody(catalog, shapeId, stack, aliasHint, renderDeclarationShape); | |
| 950 | + const normalized = normalizedUnionRender( | |
| 951 | + shape.node.items, | |
| 952 | + stack, | |
| 953 | + aliasHint, | |
| 954 | + renderDeclarationShape, | |
| 955 | + true, | |
| 956 | + ); | |
| 957 | + return normalized ?? renderShapeBody(catalog, shapeId, stack, aliasHint, renderDeclarationShape, true); | |
| 862 | 958 | } |
| 863 | 959 | case "graphqlUnion": { |
| 864 | − const normalized = normalizedUnionRender(shape.node.memberTypeIds, stack, aliasHint, renderDeclarationShape); | |
| 865 | − return normalized ?? renderShapeBody(catalog, shapeId, stack, aliasHint, renderDeclarationShape); | |
| 960 | + const normalized = normalizedUnionRender(shape.node.memberTypeIds, stack, aliasHint, renderDeclarationShape, true); | |
| 961 | + return normalized ?? renderShapeBody(catalog, shapeId, stack, aliasHint, renderDeclarationShape, true); | |
| 866 | 962 | } |
| 867 | 963 | default: |
| 868 | − return renderShapeBody(catalog, shapeId, stack, aliasHint, renderDeclarationShape); | |
| 964 | + return renderShapeBody(catalog, shapeId, stack, aliasHint, renderDeclarationShape, true); | |
| 869 | 965 | } |
| 870 | 966 | }; |
| 871 | 967 | |
| @@ -921,16 +1017,49 @@ export const createCatalogTypeProjector = (input: { · 49 unmodified lines | ||
| 921 | 1017 | switch (shape.node.type) { |
| 922 | 1018 | case "anyOf": |
| 923 | 1019 | case "oneOf": { |
| 924 | − const normalized = normalizedUnionRender(shape.node.items, [...stack, shapeId], options.aliasHint, renderSelfContainedShape); | |
| 925 | − return normalized ?? renderShapeBody(catalog, shapeId, [...stack, shapeId], options.aliasHint, renderSelfContainedShape); | |
| 1020 | + const normalized = normalizedUnionRender( | |
| 1021 | + shape.node.items, | |
| 1022 | + [...stack, shapeId], | |
| 1023 | + options.aliasHint, | |
| 1024 | + renderSelfContainedShape, | |
| 1025 | + false, | |
| 1026 | + ); | |
| 1027 | + return normalized ?? renderShapeBody( | |
| 1028 | + catalog, | |
| 1029 | + shapeId, | |
| 1030 | + [...stack, shapeId], | |
| 1031 | + options.aliasHint, | |
| 1032 | + renderSelfContainedShape, | |
| 1033 | + false, | |
| 1034 | + ); | |
| 926 | 1035 | } |
| 927 | 1036 | case "graphqlUnion": { |
| 928 | − const normalized = normalizedUnionRender(shape.node.memberTypeIds, [...stack, shapeId], options.aliasHint, renderSelfContainedShape); | |
| 929 | − return normalized ?? renderShapeBody(catalog, shapeId, [...stack, shapeId], options.aliasHint, renderSelfContainedShape); | |
| 1037 | + const normalized = normalizedUnionRender( | |
| 1038 | + shape.node.memberTypeIds, | |
| 1039 | + [...stack, shapeId], | |
| 1040 | + options.aliasHint, | |
| 1041 | + renderSelfContainedShape, | |
| 1042 | + false, | |
| 1043 | + ); | |
| 1044 | + return normalized ?? renderShapeBody( | |
| 1045 | + catalog, | |
| 1046 | + shapeId, | |
| 1047 | + [...stack, shapeId], | |
| 1048 | + options.aliasHint, | |
| 1049 | + renderSelfContainedShape, | |
| 1050 | + false, | |
| 1051 | + ); | |
| 930 | 1052 | } |
| 931 | 1053 | } |
| 932 | 1054 | |
| 933 | − return renderShapeBody(catalog, shapeId, [...stack, shapeId], options.aliasHint, renderSelfContainedShape); | |
| 1055 | + return renderShapeBody( | |
| 1056 | + catalog, | |
| 1057 | + shapeId, | |
| 1058 | + [...stack, shapeId], | |
| 1059 | + options.aliasHint, | |
| 1060 | + renderSelfContainedShape, | |
| 1061 | + false, | |
| 1062 | + ); | |
| 934 | 1063 | }; |
| 935 | 1064 | |
| 936 | 1065 | const supportingDeclarations = (): readonly string[] => { |
| @@ -960,11 +1089,25 @@ export const createCatalogTypeProjector = (input: { · 23 unmodified lines | ||
| 960 | 1089 | const canonicalType = canonicalTypeForSignature(signature); |
| 961 | 1090 | const representativeShapeId = representativeShapeIdForSignature(signature); |
| 962 | 1091 | const aliasName = aliasNameForSignature(signature); |
| 1092 | + const representativeShape = getShapeSymbol(catalog, representativeShapeId); | |
| 1093 | + const comment = documentationComment({ | |
| 1094 | + title: representativeShape.title, | |
| 1095 | + docs: representativeShape.docs, | |
| 1096 | + deprecated: representativeShape.deprecated, | |
| 1097 | + includeTitle: true, | |
| 1098 | + }); | |
| 963 | 1099 | if (canonicalType !== aliasName) { |
| 964 | − return `type ${aliasName} = ${canonicalType};`; | |
| 1100 | + return comment | |
| 1101 | + ? `${comment}\ntype ${aliasName} = ${canonicalType};` | |
| 1102 | + : `type ${aliasName} = ${canonicalType};`; | |
| 965 | 1103 | } |
| 966 | 1104 | const body = renderDeclarationShapeBody(representativeShapeId, [representativeShapeId], aliasName); |
| 967 | − return body === aliasName ? "" : `type ${aliasName} = ${body};`; | |
| 1105 | + if (body === aliasName) { | |
| 1106 | + return ""; | |
| 1107 | + } | |
| 1108 | + | |
| 1109 | + const declaration = `type ${aliasName} = ${body};`; | |
| 1110 | + return comment ? `${comment}\n${declaration}` : declaration; | |
| 968 | 1111 | }) |
| 969 | 1112 | .filter((declaration) => declaration.length > 0); |
| 970 | 1113 | }; |
| @@ -3,7 +3,7 @@ import parserBabel from "prettier/plugins/babel"; · 2 unmodified lines | ||
| 3 | 3 | import parserEstree from "prettier/plugins/estree"; |
| 4 | 4 | import parserTypescript from "prettier/plugins/typescript"; |
| 5 | 5 | |
| 6 | −export type PrettierParser = "json" | "typescript"; | |
| 6 | +export type PrettierParser = "json" | "typescript" | "typescript-module"; | |
| 7 | 7 | |
| 8 | 8 | const plugins = [parserBabel, parserEstree, parserTypescript]; |
| 9 | 9 | |
| @@ -31,7 +31,7 @@ export async function formatWithPrettier( · 21 unmodified lines | ||
| 31 | 31 | } |
| 32 | 32 | |
| 33 | 33 | const result = await format(input, { |
| 34 | − parser, | |
| 34 | + parser: parser === "typescript-module" ? "typescript" : parser, | |
| 35 | 35 | plugins, |
| 36 | 36 | printWidth: 60, |
| 37 | 37 | tabWidth: 2, |
| @@ -353,7 +353,7 @@ describe("source-catalog-runtime", () => { · 352 unmodified lines | ||
| 353 | 353 | }); |
| 354 | 354 | |
| 355 | 355 | expect(tool).toBeDefined(); |
| 356 | − expect(tool?.descriptor.inputSchema).toMatchObject({ | |
| 356 | + expect(tool?.descriptor.contract?.inputSchema).toMatchObject({ | |
| 357 | 357 | type: "object", |
| 358 | 358 | properties: { |
| 359 | 359 | filter: { |
| @@ -368,11 +368,11 @@ describe("source-catalog-runtime", () => { · 8 unmodified lines | ||
| 368 | 368 | }, |
| 369 | 369 | }); |
| 370 | 370 | |
| 371 | − const serializedInput = JSON.stringify(tool?.descriptor.inputSchema); | |
| 371 | + const serializedInput = JSON.stringify(tool?.descriptor.contract?.inputSchema); | |
| 372 | 372 | expect(serializedInput).not.toContain("\"$ref\":\"#/$defs/shape_"); |
| 373 | 373 | expect(serializedInput).not.toContain("\"shape_"); |
| 374 | − expect(tool?.descriptor.inputTypePreview).toContain("filter?: {"); | |
| 375 | − expect(tool?.descriptor.outputTypePreview).toContain("nodes?: {"); | |
| 374 | + expect(tool?.descriptor.contract?.inputTypePreview).toContain("filter?: {"); | |
| 375 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("nodes?: {"); | |
| 376 | 376 | })); |
| 377 | 377 | |
| 378 | 378 | it.effect("can skip type previews when only list metadata is needed", () => |
| @@ -384,10 +384,10 @@ describe("source-catalog-runtime", () => { · 5 unmodified lines | ||
| 384 | 384 | }); |
| 385 | 385 | |
| 386 | 386 | expect(tool).toBeDefined(); |
| 387 | − expect(tool?.descriptor.inputSchema).toBeUndefined(); | |
| 388 | − expect(tool?.descriptor.outputSchema).toBeUndefined(); | |
| 389 | − expect(tool?.descriptor.inputTypePreview).toBeUndefined(); | |
| 390 | − expect(tool?.descriptor.outputTypePreview).toBeUndefined(); | |
| 387 | + expect(tool?.descriptor.contract?.inputSchema).toBeUndefined(); | |
| 388 | + expect(tool?.descriptor.contract?.outputSchema).toBeUndefined(); | |
| 389 | + expect(tool?.descriptor.contract?.inputTypePreview).toBeUndefined(); | |
| 390 | + expect(tool?.descriptor.contract?.outputTypePreview).toBeUndefined(); | |
| 391 | 391 | })); |
| 392 | 392 | |
| 393 | 393 | it.effect("projects a single tool by path without expanding the whole catalog", () => |
| @@ -400,7 +400,7 @@ describe("source-catalog-runtime", () => { · 6 unmodified lines | ||
| 400 | 400 | |
| 401 | 401 | expect(tool).not.toBeNull(); |
| 402 | 402 | expect(tool?.path).toBe("linear.teams"); |
| 403 | − expect(tool?.descriptor.inputSchema).toMatchObject({ | |
| 403 | + expect(tool?.descriptor.contract?.inputSchema).toMatchObject({ | |
| 404 | 404 | type: "object", |
| 405 | 405 | properties: { |
| 406 | 406 | filter: { |
| @@ -569,12 +569,12 @@ describe("source-catalog-runtime", () => { · 162 unmodified lines | ||
| 569 | 569 | }); |
| 570 | 570 | |
| 571 | 571 | expect(tool).not.toBeNull(); |
| 572 | − expect(tool?.descriptor.outputTypePreview).toContain("actor?: string;"); | |
| 573 | − expect(tool?.descriptor.outputTypePreview).toContain('action: "blocked";'); | |
| 574 | − expect(tool?.descriptor.outputTypePreview).toContain('action: "unblocked";'); | |
| 575 | − expect(tool?.descriptor.outputTypePreview).toContain('action: "route-blocked";'); | |
| 576 | − expect(tool?.descriptor.outputTypePreview).toContain("& ("); | |
| 577 | − expect(tool?.descriptor.outputTypePreview).not.toContain("Member2"); | |
| 572 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("actor?: string;"); | |
| 573 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain('action: "blocked";'); | |
| 574 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain('action: "unblocked";'); | |
| 575 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain('action: "route-blocked";'); | |
| 576 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("& ("); | |
| 577 | + expect(tool?.descriptor.contract?.outputTypePreview).not.toContain("Member2"); | |
| 578 | 578 | })); |
| 579 | 579 | |
| 580 | 580 | it.effect("uses the shared TS projector for unsupported preview shapes", () => |
| @@ -622,6 +622,6 @@ describe("source-catalog-runtime", () => { · 41 unmodified lines | ||
| 622 | 622 | }); |
| 623 | 623 | |
| 624 | 624 | expect(tool).not.toBeNull(); |
| 625 | − expect(tool?.descriptor.inputTypePreview).toContain("unsupported?: unknown;"); | |
| 625 | + expect(tool?.descriptor.contract?.inputTypePreview).toContain("unsupported?: unknown;"); | |
| 626 | 626 | })); |
| 627 | 627 | }); |
| @@ -19,6 +19,7 @@ import { · 18 unmodified lines | ||
| 19 | 19 | projectCatalogForAgentSdk, |
| 20 | 20 | type ProjectedCatalog, |
| 21 | 21 | } from "@executor/ir/catalog"; |
| 22 | +import type { ShapeSymbolId } from "@executor/ir/ids"; | |
| 22 | 23 | import type { |
| 23 | 24 | Capability, |
| 24 | 25 | CatalogSnapshotV1, |
| @@ -29,10 +30,13 @@ import type { · 4 unmodified lines | ||
| 29 | 30 | import { LocalSourceArtifactMissingError } from "../../local/errors"; |
| 30 | 31 | import { |
| 31 | 32 | createCatalogTypeProjector, |
| 33 | + documentationComment, | |
| 32 | 34 | joinTypeNameSegments, |
| 33 | 35 | projectedCatalogTypeRoots, |
| 36 | + shapeAllowsOmittedArgs, | |
| 34 | 37 | type CatalogTypeProjector, |
| 35 | 38 | } from "../catalog-typescript"; |
| 39 | +import { formatWithPrettier } from "../prettier-format"; | |
| 36 | 40 | import { |
| 37 | 41 | RuntimeLocalWorkspaceService, |
| 38 | 42 | type RuntimeLocalWorkspaceState, |
| @@ -85,6 +89,24 @@ export type LoadedSourceCatalogToolIndexEntry = Omit< · 46 unmodified lines | ||
| 85 | 89 | "revision" | "projectedDescriptor" | "typeProjector" |
| 86 | 90 | >; |
| 87 | 91 | |
| 92 | +export type LoadedSourceCatalogToolContractSide = { | |
| 93 | + shapeId: string | null; | |
| 94 | + typePreview: string | null; | |
| 95 | + typeDeclaration: string | null; | |
| 96 | + schemaJson: string | null; | |
| 97 | + exampleJson: string | null; | |
| 98 | +}; | |
| 99 | + | |
| 100 | +export type LoadedSourceCatalogToolContract = { | |
| 101 | + callSignature: string; | |
| 102 | + callDeclaration: string; | |
| 103 | + callShapeId: string; | |
| 104 | + resultShapeId: string | null; | |
| 105 | + responseSetId: string; | |
| 106 | + input: LoadedSourceCatalogToolContractSide; | |
| 107 | + output: LoadedSourceCatalogToolContractSide; | |
| 108 | +}; | |
| 109 | + | |
| 88 | 110 | export const catalogToolCatalogEntry = (input: { |
| 89 | 111 | tool: LoadedSourceCatalogToolIndexEntry; |
| 90 | 112 | score: (queryTokens: readonly string[]) => number; |
| @@ -102,6 +124,14 @@ const catalogNamespaceFromPath = (path: string): string => { · 11 unmodified lines | ||
| 102 | 124 | |
| 103 | 125 | const descriptorPath = (descriptor: CatalogToolDescriptor): string => descriptor.path; |
| 104 | 126 | |
| 127 | +const optionalJsonString = (value: unknown): string | null => { | |
| 128 | + if (value === undefined || value === null) { | |
| 129 | + return null; | |
| 130 | + } | |
| 131 | + | |
| 132 | + return JSON.stringify(value, null, 2); | |
| 133 | +}; | |
| 134 | + | |
| 105 | 135 | const projectedToolPath = (projected: ProjectedCatalog, capability: Capability): string => |
| 106 | 136 | projected.toolDescriptors[capability.id]?.toolPath.join(".") ?? ""; |
| 107 | 137 | |
| @@ -535,10 +565,12 @@ const codemodeDescriptorFromCapability = (input: { · 427 unmodified lines | ||
| 535 | 565 | sourceKey: input.source.id, |
| 536 | 566 | description: input.capability.surface.summary ?? input.capability.surface.description, |
| 537 | 567 | interaction, |
| 538 | − ...(inputSchema !== undefined ? { inputSchema } : {}), | |
| 539 | − ...(outputSchema !== undefined ? { outputSchema } : {}), | |
| 540 | − inputTypePreview, | |
| 541 | − ...(outputTypePreview !== undefined ? { outputTypePreview } : {}), | |
| 568 | + contract: { | |
| 569 | + inputTypePreview, | |
| 570 | + ...(outputTypePreview !== undefined ? { outputTypePreview } : {}), | |
| 571 | + ...(inputSchema !== undefined ? { inputSchema } : {}), | |
| 572 | + ...(outputSchema !== undefined ? { outputSchema } : {}), | |
| 573 | + }, | |
| 542 | 574 | providerKind: input.executable.adapterKey, |
| 543 | 575 | providerData: { |
| 544 | 576 | capabilityId: input.capability.id, |
| @@ -576,6 +608,8 @@ const loadedCatalogToolFromCapability = (input: { · 31 unmodified lines | ||
| 576 | 608 | input.capability.surface.title, |
| 577 | 609 | input.capability.surface.summary, |
| 578 | 610 | input.capability.surface.description, |
| 611 | + descriptor.contract?.inputTypePreview, | |
| 612 | + descriptor.contract?.outputTypePreview, | |
| 579 | 613 | ...(searchDoc?.tags ?? []), |
| 580 | 614 | ...(searchDoc?.protocolHints ?? []), |
| 581 | 615 | ...(searchDoc?.authHints ?? []), |
| @@ -853,6 +887,199 @@ export const expandCatalogTools = (input: { · 271 unmodified lines | ||
| 853 | 887 | ), |
| 854 | 888 | ); |
| 855 | 889 | |
| 890 | +const declarationBlockForShape = (input: { | |
| 891 | + catalog: CatalogV1; | |
| 892 | + shapeId: ShapeSymbolId; | |
| 893 | + aliasHint: string; | |
| 894 | +}): Effect.Effect<string, Error, never> => | |
| 895 | + Effect.tryPromise({ | |
| 896 | + try: async () => { | |
| 897 | + const projector = createCatalogTypeProjector({ | |
| 898 | + catalog: input.catalog, | |
| 899 | + roots: [{ | |
| 900 | + shapeId: input.shapeId, | |
| 901 | + aliasHint: input.aliasHint, | |
| 902 | + }], | |
| 903 | + }); | |
| 904 | + const rootType = projector.renderDeclarationShape(input.shapeId, { | |
| 905 | + aliasHint: input.aliasHint, | |
| 906 | + }); | |
| 907 | + const supportingDeclarations = projector.supportingDeclarations(); | |
| 908 | + const rootDeclarationPrefix = `type ${input.aliasHint} =`; | |
| 909 | + const declarationText = supportingDeclarations.some((declaration) => | |
| 910 | + declaration.includes(rootDeclarationPrefix) | |
| 911 | + ) | |
| 912 | + ? supportingDeclarations.join("\n\n") | |
| 913 | + : [ | |
| 914 | + ...supportingDeclarations, | |
| 915 | + typeAliasDeclaration({ | |
| 916 | + catalog: input.catalog, | |
| 917 | + shapeId: input.shapeId, | |
| 918 | + aliasHint: input.aliasHint, | |
| 919 | + body: rootType, | |
| 920 | + }), | |
| 921 | + ].join("\n\n"); | |
| 922 | + | |
| 923 | + return formatWithPrettier(declarationText, "typescript-module"); | |
| 924 | + }, | |
| 925 | + catch: (cause) => | |
| 926 | + cause instanceof Error ? cause : new Error(String(cause)), | |
| 927 | + }); | |
| 928 | + | |
| 929 | +const formattedOptionalTypeExpression = ( | |
| 930 | + value: string | undefined, | |
| 931 | +): Effect.Effect<string | null, Error, never> => | |
| 932 | + value === undefined | |
| 933 | + ? Effect.succeed(null) | |
| 934 | + : Effect.tryPromise({ | |
| 935 | + try: () => formatWithPrettier(value, "typescript"), | |
| 936 | + catch: (cause) => | |
| 937 | + cause instanceof Error ? cause : new Error(String(cause)), | |
| 938 | + }); | |
| 939 | + | |
| 940 | +const formattedOptionalJson = ( | |
| 941 | + value: unknown, | |
| 942 | +): Effect.Effect<string | null, Error, never> => { | |
| 943 | + const serialized = optionalJsonString(value); | |
| 944 | + return serialized === null | |
| 945 | + ? Effect.succeed(null) | |
| 946 | + : Effect.tryPromise({ | |
| 947 | + try: () => formatWithPrettier(serialized, "json"), | |
| 948 | + catch: (cause) => | |
| 949 | + cause instanceof Error ? cause : new Error(String(cause)), | |
| 950 | + }); | |
| 951 | +}; | |
| 952 | + | |
| 953 | +const lowerCamelCase = (value: string): string => | |
| 954 | + value.length === 0 ? "tool" : `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`; | |
| 955 | + | |
| 956 | +const typeAliasDeclaration = (input: { | |
| 957 | + catalog: CatalogV1; | |
| 958 | + shapeId: ShapeSymbolId; | |
| 959 | + aliasHint: string; | |
| 960 | + body: string; | |
| 961 | +}): string => { | |
| 962 | + const shape = input.catalog.symbols[input.shapeId]; | |
| 963 | + const comment = shape?.kind === "shape" | |
| 964 | + ? documentationComment({ | |
| 965 | + title: shape.title, | |
| 966 | + docs: shape.docs, | |
| 967 | + deprecated: shape.deprecated, | |
| 968 | + includeTitle: true, | |
| 969 | + }) | |
| 970 | + : null; | |
| 971 | + const declaration = `type ${input.aliasHint} = ${input.body};`; | |
| 972 | + return comment ? `${comment}\n${declaration}` : declaration; | |
| 973 | +}; | |
| 974 | + | |
| 975 | +export const buildLoadedSourceCatalogToolContract = ( | |
| 976 | + tool: LoadedSourceCatalogTool, | |
| 977 | +): Effect.Effect<LoadedSourceCatalogToolContract, Error, never> => { | |
| 978 | + const inputAlias = joinTypeNameSegments(...tool.projectedDescriptor.toolPath, "call"); | |
| 979 | + const outputAlias = joinTypeNameSegments(...tool.projectedDescriptor.toolPath, "result"); | |
| 980 | + const inputShapeId = tool.projectedDescriptor.callShapeId; | |
| 981 | + const outputShapeId = tool.projectedDescriptor.resultShapeId ?? null; | |
| 982 | + const argsOptional = shapeAllowsOmittedArgs(tool.projectedCatalog, inputShapeId); | |
| 983 | + const outputTypeName = outputShapeId ? outputAlias : "unknown"; | |
| 984 | + const callFunctionName = lowerCamelCase( | |
| 985 | + joinTypeNameSegments(...tool.projectedDescriptor.toolPath), | |
| 986 | + ); | |
| 987 | + const callComment = documentationComment({ | |
| 988 | + title: tool.capability.surface.title, | |
| 989 | + docs: { | |
| 990 | + ...(tool.capability.surface.summary | |
| 991 | + ? { summary: tool.capability.surface.summary } | |
| 992 | + : {}), | |
| 993 | + ...(tool.capability.surface.description | |
| 994 | + ? { description: tool.capability.surface.description } | |
| 995 | + : {}), | |
| 996 | + }, | |
| 997 | + includeTitle: true, | |
| 998 | + }); | |
| 999 | + | |
| 1000 | + return Effect.gen(function* () { | |
| 1001 | + const [ | |
| 1002 | + inputTypePreview, | |
| 1003 | + outputTypePreview, | |
| 1004 | + inputTypeDeclaration, | |
| 1005 | + outputTypeDeclaration, | |
| 1006 | + inputSchemaJson, | |
| 1007 | + outputSchemaJson, | |
| 1008 | + callSignature, | |
| 1009 | + callDeclaration, | |
| 1010 | + ] = | |
| 1011 | + yield* Effect.all([ | |
| 1012 | + formattedOptionalTypeExpression(tool.descriptor.contract?.inputTypePreview), | |
| 1013 | + formattedOptionalTypeExpression(tool.descriptor.contract?.outputTypePreview), | |
| 1014 | + declarationBlockForShape({ | |
| 1015 | + catalog: tool.projectedCatalog, | |
| 1016 | + shapeId: inputShapeId, | |
| 1017 | + aliasHint: inputAlias, | |
| 1018 | + }), | |
| 1019 | + outputShapeId | |
| 1020 | + ? declarationBlockForShape({ | |
| 1021 | + catalog: tool.projectedCatalog, | |
| 1022 | + shapeId: outputShapeId, | |
| 1023 | + aliasHint: outputAlias, | |
| 1024 | + }) | |
| 1025 | + : Effect.succeed<string | null>(null), | |
| 1026 | + formattedOptionalJson( | |
| 1027 | + tool.descriptor.contract?.inputSchema | |
| 1028 | + ?? shapeToJsonSchema(tool.projectedCatalog, inputShapeId), | |
| 1029 | + ), | |
| 1030 | + outputShapeId | |
| 1031 | + ? formattedOptionalJson( | |
| 1032 | + tool.descriptor.contract?.outputSchema | |
| 1033 | + ?? shapeToJsonSchema(tool.projectedCatalog, outputShapeId), | |
| 1034 | + ) | |
| 1035 | + : Effect.succeed<string | null>(null), | |
| 1036 | + Effect.tryPromise({ | |
| 1037 | + try: () => | |
| 1038 | + formatWithPrettier( | |
| 1039 | + `(${argsOptional ? "args?" : "args"}: ${inputAlias}) => Promise<${outputTypeName}>`, | |
| 1040 | + "typescript", | |
| 1041 | + ), | |
| 1042 | + catch: (cause) => | |
| 1043 | + cause instanceof Error ? cause : new Error(String(cause)), | |
| 1044 | + }), | |
| 1045 | + Effect.tryPromise({ | |
| 1046 | + try: () => | |
| 1047 | + formatWithPrettier( | |
| 1048 | + [ | |
| 1049 | + ...(callComment ? [callComment] : []), | |
| 1050 | + `declare function ${callFunctionName}(${argsOptional ? "args?" : "args"}: ${inputAlias}): Promise<${outputTypeName}>;`, | |
| 1051 | + ].join("\n"), | |
| 1052 | + "typescript-module", | |
| 1053 | + ), | |
| 1054 | + catch: (cause) => | |
| 1055 | + cause instanceof Error ? cause : new Error(String(cause)), | |
| 1056 | + }), | |
| 1057 | + ]); | |
| 1058 | + | |
| 1059 | + return { | |
| 1060 | + callSignature, | |
| 1061 | + callDeclaration, | |
| 1062 | + callShapeId: inputShapeId, | |
| 1063 | + resultShapeId: outputShapeId, | |
| 1064 | + responseSetId: tool.projectedDescriptor.responseSetId, | |
| 1065 | + input: { | |
| 1066 | + shapeId: inputShapeId, | |
| 1067 | + typePreview: inputTypePreview, | |
| 1068 | + typeDeclaration: inputTypeDeclaration, | |
| 1069 | + schemaJson: inputSchemaJson, | |
| 1070 | + exampleJson: null, | |
| 1071 | + }, | |
| 1072 | + output: { | |
| 1073 | + shapeId: outputShapeId, | |
| 1074 | + typePreview: outputTypePreview, | |
| 1075 | + typeDeclaration: outputTypeDeclaration, | |
| 1076 | + schemaJson: outputSchemaJson, | |
| 1077 | + exampleJson: null, | |
| 1078 | + }, | |
| 1079 | + } satisfies LoadedSourceCatalogToolContract; | |
| 1080 | + }); | |
| 1081 | +}; | |
| 1082 | + | |
| 856 | 1083 | export const expandCatalogToolByPath = (input: { |
| 857 | 1084 | catalogs: readonly LoadedSourceCatalog[]; |
| 858 | 1085 | path: string; |
| @@ -186,6 +186,13 @@ const prepareExecutorAddSourceInput = (input: { · 185 unmodified lines | ||
| 186 | 186 | endpoint: input.args.endpoint, |
| 187 | 187 | name: input.args.name ?? null, |
| 188 | 188 | namespace: input.args.namespace ?? null, |
| 189 | + transport: input.args.transport ?? null, | |
| 190 | + queryParams: input.args.queryParams ?? null, | |
| 191 | + headers: input.args.headers ?? null, | |
| 192 | + command: input.args.command ?? null, | |
| 193 | + args: input.args.args ?? null, | |
| 194 | + env: input.args.env ?? null, | |
| 195 | + cwd: input.args.cwd ?? null, | |
| 189 | 196 | workspaceId: input.workspaceId, |
| 190 | 197 | actorAccountId: input.accountId, |
| 191 | 198 | executionId: input.executionId, |
| @@ -468,10 +475,12 @@ export const createExecutorToolMap = (input: { · 276 unmodified lines | ||
| 468 | 475 | }, |
| 469 | 476 | }, |
| 470 | 477 | metadata: { |
| 471 | − inputTypePreview: EXECUTOR_SOURCES_ADD_INPUT_HINT, | |
| 472 | − outputTypePreview: EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE, | |
| 473 | − inputSchema: EXECUTOR_SOURCES_ADD_INPUT_SCHEMA, | |
| 474 | − outputSchema: EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA, | |
| 478 | + contract: { | |
| 479 | + inputTypePreview: EXECUTOR_SOURCES_ADD_INPUT_HINT, | |
| 480 | + outputTypePreview: EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE, | |
| 481 | + inputSchema: EXECUTOR_SOURCES_ADD_INPUT_SCHEMA, | |
| 482 | + outputSchema: EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA, | |
| 483 | + }, | |
| 475 | 484 | sourceKey: "executor", |
| 476 | 485 | interaction: "auto", |
| 477 | 486 | }, |
| @@ -362,10 +362,10 @@ describe("source adapter fixture matrix", () => { · 361 unmodified lines | ||
| 362 | 362 | Object.keys(snapshot.catalog.capabilities).length, |
| 363 | 363 | ).toBeGreaterThan(250); |
| 364 | 364 | expect(tool).toBeDefined(); |
| 365 | − expect(tool?.descriptor.inputTypePreview).not.toContain("unknown"); | |
| 366 | − expect(tool?.descriptor.outputTypePreview).toContain("data:"); | |
| 367 | − expect(tool?.descriptor.outputTypePreview).toContain("status:"); | |
| 368 | − expect(tool?.descriptor.inputSchema).toMatchObject({ | |
| 365 | + expect(tool?.descriptor.contract?.inputTypePreview).not.toContain("unknown"); | |
| 366 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("data:"); | |
| 367 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("status:"); | |
| 368 | + expect(tool?.descriptor.contract?.inputSchema).toMatchObject({ | |
| 369 | 369 | type: "object", |
| 370 | 370 | properties: { |
| 371 | 371 | idOrName: { |
| @@ -428,8 +428,8 @@ describe("source adapter fixture matrix", () => { · 56 unmodified lines | ||
| 428 | 428 | |
| 429 | 429 | expect(manifest.tools.length).toBeGreaterThan(50); |
| 430 | 430 | expect(tool).toBeDefined(); |
| 431 | − expect(tool?.descriptor.inputTypePreview).toContain("key_name"); | |
| 432 | − expect(tool?.descriptor.inputSchema).toMatchObject({ | |
| 431 | + expect(tool?.descriptor.contract?.inputTypePreview).toContain("key_name"); | |
| 432 | + expect(tool?.descriptor.contract?.inputSchema).toMatchObject({ | |
| 433 | 433 | type: "object", |
| 434 | 434 | properties: { |
| 435 | 435 | body: { |
| @@ -444,7 +444,7 @@ describe("source adapter fixture matrix", () => { · 8 unmodified lines | ||
| 444 | 444 | }, |
| 445 | 445 | required: ["body"], |
| 446 | 446 | }); |
| 447 | − expect(tool?.descriptor.outputSchema).toMatchObject({ | |
| 447 | + expect(tool?.descriptor.contract?.outputSchema).toMatchObject({ | |
| 448 | 448 | type: "object", |
| 449 | 449 | properties: { |
| 450 | 450 | data: { |
| @@ -452,7 +452,7 @@ describe("source adapter fixture matrix", () => { · 1 unmodified line | ||
| 452 | 452 | }, |
| 453 | 453 | }, |
| 454 | 454 | }); |
| 455 | − const outputSchema = tool?.descriptor.outputSchema; | |
| 455 | + const outputSchema = tool?.descriptor.contract?.outputSchema; | |
| 456 | 456 | const dataVariants = |
| 457 | 457 | outputSchema && typeof outputSchema === "object" |
| 458 | 458 | ? ( |
| @@ -979,10 +979,10 @@ describe("source adapter fixture matrix", () => { · 520 unmodified lines | ||
| 979 | 979 | Object.keys(snapshot.catalog.capabilities).length, |
| 980 | 980 | ).toBeGreaterThan(10); |
| 981 | 981 | expect(tool).toBeDefined(); |
| 982 | − expect(tool?.descriptor.inputTypePreview).not.toContain("unknown"); | |
| 983 | − expect(tool?.descriptor.outputTypePreview).toContain("data:"); | |
| 984 | − expect(tool?.descriptor.outputTypePreview).toContain("status:"); | |
| 985 | − expect(tool?.descriptor.inputSchema).toMatchObject({ | |
| 982 | + expect(tool?.descriptor.contract?.inputTypePreview).not.toContain("unknown"); | |
| 983 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("data:"); | |
| 984 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("status:"); | |
| 985 | + expect(tool?.descriptor.contract?.inputSchema).toMatchObject({ | |
| 986 | 986 | type: "object", |
| 987 | 987 | properties: { |
| 988 | 988 | spreadsheetId: { |
| @@ -1001,7 +1001,7 @@ describe("source adapter fixture matrix", () => { · 12 unmodified lines | ||
| 1001 | 1001 | }, |
| 1002 | 1002 | }, |
| 1003 | 1003 | }); |
| 1004 | − expect(JSON.stringify(tool?.descriptor.outputSchema)).toContain( | |
| 1004 | + expect(JSON.stringify(tool?.descriptor.contract?.outputSchema)).toContain( | |
| 1005 | 1005 | '"gridProperties"', |
| 1006 | 1006 | ); |
| 1007 | 1007 | expect( |
| @@ -1111,11 +1111,11 @@ describe("source adapter fixture matrix", () => { · 103 unmodified lines | ||
| 1111 | 1111 | ), |
| 1112 | 1112 | ).toEqual([]); |
| 1113 | 1113 | expect(tool).toBeDefined(); |
| 1114 | − expect(tool?.descriptor.inputTypePreview).toContain("{"); | |
| 1115 | − expect(tool?.descriptor.inputTypePreview).toContain("input: {"); | |
| 1116 | − expect(tool?.descriptor.outputTypePreview).toContain("data:"); | |
| 1117 | − expect(tool?.descriptor.outputTypePreview).not.toContain("unknown[]"); | |
| 1118 | − expect(tool?.descriptor.inputSchema).toMatchObject({ | |
| 1114 | + expect(tool?.descriptor.contract?.inputTypePreview).toContain("{"); | |
| 1115 | + expect(tool?.descriptor.contract?.inputTypePreview).toContain("input: {"); | |
| 1116 | + expect(tool?.descriptor.contract?.outputTypePreview).toContain("data:"); | |
| 1117 | + expect(tool?.descriptor.contract?.outputTypePreview).not.toContain("unknown[]"); | |
| 1118 | + expect(tool?.descriptor.contract?.inputSchema).toMatchObject({ | |
| 1119 | 1119 | type: "object", |
| 1120 | 1120 | required: ["input"], |
| 1121 | 1121 | properties: { |
| @@ -1303,7 +1303,7 @@ describe("source adapter fixture matrix", () => { · 181 unmodified lines | ||
| 1303 | 1303 | ); |
| 1304 | 1304 | } |
| 1305 | 1305 | |
| 1306 | − expect(tool.descriptor.outputSchema).toMatchObject({ | |
| 1306 | + expect(tool.descriptor.contract?.outputSchema).toMatchObject({ | |
| 1307 | 1307 | type: "object", |
| 1308 | 1308 | properties: { |
| 1309 | 1309 | data: { |
| @@ -1,11 +1,13 @@ | ||
| 1 | 1 | import { describe, expect, it } from "@effect/vitest"; |
| 2 | +import * as Effect from "effect/Effect"; | |
| 2 | 3 | |
| 3 | 4 | import type { LoadedSourceCatalogTool } from "../catalog/source/runtime"; |
| 4 | 5 | import { inspectionToolDetailFromTool } from "./source-inspection"; |
| 5 | 6 | |
| 6 | 7 | describe("source inspection", () => { |
| 7 | − it("keeps tool detail lightweight by default", () => { | |
| 8 | − const detail = inspectionToolDetailFromTool({ | |
| 8 | + it.effect("keeps the shared TypeScript contract first-class on tool detail", () => | |
| 9 | + Effect.gen(function* () { | |
| 10 | + const detail = yield* inspectionToolDetailFromTool({ | |
| 9 | 11 | path: "linear.administrableTeams", |
| 10 | 12 | source: { |
| 11 | 13 | id: "linear", |
| @@ -30,7 +32,9 @@ describe("source inspection", () => { · 18 unmodified lines | ||
| 30 | 32 | }, |
| 31 | 33 | projection: { |
| 32 | 34 | callShapeId: "shape_call", |
| 33 | − resultShapeId: "shape_result", | |
| 35 | + resultDataShapeId: "shape_result_data", | |
| 36 | + resultErrorShapeId: "shape_result_error", | |
| 37 | + responseSetId: "response_set_1", | |
| 34 | 38 | }, |
| 35 | 39 | display: { |
| 36 | 40 | protocol: "graphql", |
| @@ -47,26 +51,220 @@ describe("source inspection", () => { · 10 unmodified lines | ||
| 47 | 51 | toolPath: ["linear", "administrableTeams"], |
| 48 | 52 | callShapeId: "shape_call", |
| 49 | 53 | resultShapeId: "shape_result", |
| 54 | + responseSetId: "response_set_1", | |
| 50 | 55 | }, |
| 51 | − descriptor: {}, | |
| 56 | + descriptor: { | |
| 57 | + inputTypePreview: "{ input: string }", | |
| 58 | + outputTypePreview: "{ data: string | null; error: unknown | null; headers: Record<string, string>; status: number | null }", | |
| 59 | + inputSchema: { | |
| 60 | + type: "object", | |
| 61 | + properties: { | |
| 62 | + input: { type: "string" }, | |
| 63 | + }, | |
| 64 | + required: ["input"], | |
| 65 | + }, | |
| 66 | + outputSchema: { | |
| 67 | + type: "object", | |
| 68 | + properties: { | |
| 69 | + data: { type: ["string", "null"] }, | |
| 70 | + }, | |
| 71 | + required: ["data"], | |
| 72 | + }, | |
| 73 | + }, | |
| 74 | + projectedCatalog: { | |
| 75 | + version: "ir.v1", | |
| 76 | + documents: {}, | |
| 77 | + resources: {}, | |
| 78 | + scopes: {}, | |
| 79 | + capabilities: {}, | |
| 80 | + executables: {}, | |
| 81 | + responseSets: {}, | |
| 82 | + diagnostics: {}, | |
| 83 | + symbols: { | |
| 84 | + shape_call: { | |
| 85 | + id: "shape_call", | |
| 86 | + kind: "shape", | |
| 87 | + resourceId: "res_1", | |
| 88 | + title: "AdministrableTeamsCall", | |
| 89 | + node: { | |
| 90 | + type: "object", | |
| 91 | + fields: { | |
| 92 | + input: { | |
| 93 | + shapeId: "shape_call_input", | |
| 94 | + docs: { | |
| 95 | + description: "The exact input string to filter by.", | |
| 96 | + }, | |
| 97 | + }, | |
| 98 | + }, | |
| 99 | + required: ["input"], | |
| 100 | + additionalProperties: false, | |
| 101 | + }, | |
| 102 | + synthetic: false, | |
| 103 | + provenance: [], | |
| 104 | + }, | |
| 105 | + shape_call_input: { | |
| 106 | + id: "shape_call_input", | |
| 107 | + kind: "shape", | |
| 108 | + resourceId: "res_1", | |
| 109 | + title: "Input", | |
| 110 | + node: { | |
| 111 | + type: "scalar", | |
| 112 | + scalar: "string", | |
| 113 | + }, | |
| 114 | + synthetic: false, | |
| 115 | + provenance: [], | |
| 116 | + }, | |
| 117 | + shape_result: { | |
| 118 | + id: "shape_result", | |
| 119 | + kind: "shape", | |
| 120 | + resourceId: "res_1", | |
| 121 | + title: "AdministrableTeamsResult", | |
| 122 | + node: { | |
| 123 | + type: "object", | |
| 124 | + fields: { | |
| 125 | + data: { | |
| 126 | + shapeId: "shape_result_data_nullable", | |
| 127 | + docs: { | |
| 128 | + description: "Matching team data when present.", | |
| 129 | + }, | |
| 130 | + }, | |
| 131 | + error: { | |
| 132 | + shapeId: "shape_result_error_nullable", | |
| 133 | + }, | |
| 134 | + headers: { | |
| 135 | + shapeId: "shape_result_headers", | |
| 136 | + }, | |
| 137 | + status: { | |
| 138 | + shapeId: "shape_result_status_nullable", | |
| 139 | + }, | |
| 140 | + }, | |
| 141 | + required: ["data", "error", "headers", "status"], | |
| 142 | + additionalProperties: false, | |
| 143 | + }, | |
| 144 | + synthetic: true, | |
| 145 | + provenance: [], | |
| 146 | + }, | |
| 147 | + shape_result_data_nullable: { | |
| 148 | + id: "shape_result_data_nullable", | |
| 149 | + kind: "shape", | |
| 150 | + resourceId: "res_1", | |
| 151 | + node: { | |
| 152 | + type: "nullable", | |
| 153 | + itemShapeId: "shape_result_data", | |
| 154 | + }, | |
| 155 | + synthetic: true, | |
| 156 | + provenance: [], | |
| 157 | + }, | |
| 158 | + shape_result_data: { | |
| 159 | + id: "shape_result_data", | |
| 160 | + kind: "shape", | |
| 161 | + resourceId: "res_1", | |
| 162 | + node: { | |
| 163 | + type: "scalar", | |
| 164 | + scalar: "string", | |
| 165 | + }, | |
| 166 | + synthetic: false, | |
| 167 | + provenance: [], | |
| 168 | + }, | |
| 169 | + shape_result_error_nullable: { | |
| 170 | + id: "shape_result_error_nullable", | |
| 171 | + kind: "shape", | |
| 172 | + resourceId: "res_1", | |
| 173 | + node: { | |
| 174 | + type: "nullable", | |
| 175 | + itemShapeId: "shape_result_error", | |
| 176 | + }, | |
| 177 | + synthetic: true, | |
| 178 | + provenance: [], | |
| 179 | + }, | |
| 180 | + shape_result_error: { | |
| 181 | + id: "shape_result_error", | |
| 182 | + kind: "shape", | |
| 183 | + resourceId: "res_1", | |
| 184 | + node: { | |
| 185 | + type: "unknown", | |
| 186 | + }, | |
| 187 | + synthetic: false, | |
| 188 | + provenance: [], | |
| 189 | + }, | |
| 190 | + shape_result_headers: { | |
| 191 | + id: "shape_result_headers", | |
| 192 | + kind: "shape", | |
| 193 | + resourceId: "res_1", | |
| 194 | + node: { | |
| 195 | + type: "object", | |
| 196 | + fields: {}, | |
| 197 | + additionalProperties: "shape_result_header_value", | |
| 198 | + }, | |
| 199 | + synthetic: true, | |
| 200 | + provenance: [], | |
| 201 | + }, | |
| 202 | + shape_result_header_value: { | |
| 203 | + id: "shape_result_header_value", | |
| 204 | + kind: "shape", | |
| 205 | + resourceId: "res_1", | |
| 206 | + node: { | |
| 207 | + type: "scalar", | |
| 208 | + scalar: "string", | |
| 209 | + }, | |
| 210 | + synthetic: true, | |
| 211 | + provenance: [], | |
| 212 | + }, | |
| 213 | + shape_result_status_nullable: { | |
| 214 | + id: "shape_result_status_nullable", | |
| 215 | + kind: "shape", | |
| 216 | + resourceId: "res_1", | |
| 217 | + node: { | |
| 218 | + type: "nullable", | |
| 219 | + itemShapeId: "shape_result_status", | |
| 220 | + }, | |
| 221 | + synthetic: true, | |
| 222 | + provenance: [], | |
| 223 | + }, | |
| 224 | + shape_result_status: { | |
| 225 | + id: "shape_result_status", | |
| 226 | + kind: "shape", | |
| 227 | + resourceId: "res_1", | |
| 228 | + node: { | |
| 229 | + type: "scalar", | |
| 230 | + scalar: "integer", | |
| 231 | + }, | |
| 232 | + synthetic: true, | |
| 233 | + provenance: [], | |
| 234 | + }, | |
| 235 | + }, | |
| 236 | + }, | |
| 237 | + typeProjector: {} as LoadedSourceCatalogTool["typeProjector"], | |
| 52 | 238 | } as LoadedSourceCatalogTool); |
| 53 | 239 | |
| 54 | − expect(detail.summary.path).toBe("linear.administrableTeams"); | |
| 55 | − expect(detail.summary.method).toBe("query"); | |
| 240 | + expect(detail.summary.path).toBe("linear.administrableTeams"); | |
| 241 | + expect(detail.summary.method).toBe("query"); | |
| 242 | + | |
| 243 | + expect(detail.contract.callSignature).toContain("Promise<"); | |
| 244 | + expect(detail.contract.callDeclaration).toContain("/**"); | |
| 245 | + expect(detail.contract.callDeclaration).toContain("Administrable Teams"); | |
| 246 | + expect(detail.contract.callDeclaration).toContain("declare function linearAdministrableTeams"); | |
| 247 | + expect(detail.contract.callShapeId).toBe("shape_call"); | |
| 248 | + expect(detail.contract.resultShapeId).toBe("shape_result"); | |
| 249 | + expect(detail.contract.input.typeDeclaration).toContain("type LinearAdministrableTeamsCall"); | |
| 250 | + expect(detail.contract.output.typeDeclaration).toContain("type LinearAdministrableTeamsResult"); | |
| 251 | + expect(detail.contract.input.typeDeclaration).toContain("The exact input string to filter by."); | |
| 252 | + expect(detail.contract.output.typeDeclaration).toContain("Matching team data when present."); | |
| 253 | + expect(detail.contract.input.schemaJson).toContain("\"input\""); | |
| 254 | + expect(detail.contract.output.schemaJson).toContain("\"data\""); | |
| 56 | 255 | |
| 57 | − const sectionTitles = detail.sections.map((section) => section.title); | |
| 58 | − expect(sectionTitles).not.toContain("Input Type"); | |
| 59 | − expect(sectionTitles).not.toContain("Output Type"); | |
| 60 | − expect(sectionTitles).not.toContain("Input Schema"); | |
| 61 | − expect(sectionTitles).not.toContain("Output Schema"); | |
| 256 | + const sectionTitles = detail.sections.map((section) => section.title); | |
| 257 | + expect(sectionTitles).not.toContain("Input Schema"); | |
| 258 | + expect(sectionTitles).not.toContain("Output Schema"); | |
| 62 | 259 | |
| 63 | − const overview = detail.sections.find((section) => section.kind === "facts"); | |
| 64 | − expect(overview).toBeDefined(); | |
| 65 | − expect(overview?.items).toEqual( | |
| 66 | − expect.arrayContaining([ | |
| 67 | − expect.objectContaining({ label: "Call shape", value: "shape_call" }), | |
| 68 | − expect.objectContaining({ label: "Result shape", value: "shape_result" }), | |
| 69 | − ]), | |
| 70 | − ); | |
| 71 | − }); | |
| 260 | + const overview = detail.sections.find((section) => section.kind === "facts"); | |
| 261 | + expect(overview).toBeDefined(); | |
| 262 | + expect(overview?.items).toEqual( | |
| 263 | + expect.arrayContaining([ | |
| 264 | + expect.objectContaining({ label: "Signature" }), | |
| 265 | + expect.objectContaining({ label: "Call shape", value: "shape_call" }), | |
| 266 | + expect.objectContaining({ label: "Result shape", value: "shape_result" }), | |
| 267 | + ]), | |
| 268 | + ); | |
| 269 | + })); | |
| 72 | 270 | }); |
| @@ -19,6 +19,7 @@ import * as Effect from "effect/Effect"; · 18 unmodified lines | ||
| 19 | 19 | import { LocalSourceArtifactMissingError } from "../local/errors"; |
| 20 | 20 | import { operationErrors } from "../policy/operation-errors"; |
| 21 | 21 | import { |
| 22 | + buildLoadedSourceCatalogToolContract, | |
| 22 | 23 | expandCatalogToolByPath, |
| 23 | 24 | expandCatalogTools, |
| 24 | 25 | loadSourceWithCatalog, |
| @@ -107,6 +108,12 @@ const executableDetails = (tool: LoadedSourceCatalogTool) => { · 82 unmodified lines | ||
| 107 | 108 | const inspectionToolListItemFromTool = (tool: LoadedSourceCatalogTool): SourceInspectionToolListItem => ({ |
| 108 | 109 | path: tool.path, |
| 109 | 110 | method: executableDetails(tool).method, |
| 111 | + ...(tool.descriptor.contract?.inputTypePreview | |
| 112 | + ? { inputTypePreview: tool.descriptor.contract.inputTypePreview } | |
| 113 | + : {}), | |
| 114 | + ...(tool.descriptor.contract?.outputTypePreview | |
| 115 | + ? { outputTypePreview: tool.descriptor.contract.outputTypePreview } | |
| 116 | + : {}), | |
| 110 | 117 | }); |
| 111 | 118 | |
| 112 | 119 | const persistedToolSummaryFromTool = (tool: LoadedSourceCatalogTool): SourceInspectionToolSummary => { |
| @@ -128,11 +135,11 @@ const persistedToolSummaryFromTool = (tool: LoadedSourceCatalogTool): SourceInsp · 15 unmodified lines | ||
| 128 | 135 | tags: [...details.tags], |
| 129 | 136 | method: details.method, |
| 130 | 137 | pathTemplate: details.pathTemplate, |
| 131 | − ...(tool.descriptor.inputTypePreview | |
| 132 | − ? { inputTypePreview: tool.descriptor.inputTypePreview } | |
| 138 | + ...(tool.descriptor.contract?.inputTypePreview | |
| 139 | + ? { inputTypePreview: tool.descriptor.contract.inputTypePreview } | |
| 133 | 140 | : {}), |
| 134 | − ...(tool.descriptor.outputTypePreview | |
| 135 | − ? { outputTypePreview: tool.descriptor.outputTypePreview } | |
| 141 | + ...(tool.descriptor.contract?.outputTypePreview | |
| 142 | + ? { outputTypePreview: tool.descriptor.contract.outputTypePreview } | |
| 136 | 143 | : {}), |
| 137 | 144 | }; |
| 138 | 145 | }; |
| @@ -157,85 +164,89 @@ const jsonSection = ( · 18 unmodified lines | ||
| 157 | 164 | |
| 158 | 165 | export const inspectionToolDetailFromTool = ( |
| 159 | 166 | tool: LoadedSourceCatalogTool, |
| 160 | −): SourceInspectionToolDetail => { | |
| 161 | − const summary = persistedToolSummaryFromTool(tool); | |
| 162 | − const details = executableDetails(tool); | |
| 163 | − const overviewItems = [ | |
| 164 | − { label: "Protocol", value: details.protocol }, | |
| 165 | − ...(details.method ? [{ label: "Method", value: details.method, mono: true }] : []), | |
| 166 | − ...(details.pathTemplate ? [{ label: "Target", value: details.pathTemplate, mono: true }] : []), | |
| 167 | − ...(details.operationId ? [{ label: "Operation", value: details.operationId, mono: true }] : []), | |
| 168 | − ...(details.group ? [{ label: "Group", value: details.group, mono: true }] : []), | |
| 169 | − ...(details.leaf ? [{ label: "Leaf", value: details.leaf, mono: true }] : []), | |
| 170 | − ...(details.rawToolId ? [{ label: "Raw tool", value: details.rawToolId, mono: true }] : []), | |
| 171 | − { label: "Call shape", value: tool.projectedDescriptor.callShapeId, mono: true }, | |
| 172 | − ...(tool.projectedDescriptor.resultShapeId | |
| 173 | − ? [{ label: "Result shape", value: tool.projectedDescriptor.resultShapeId, mono: true }] | |
| 174 | − : []), | |
| 175 | − ]; | |
| 176 | − const nativeSections = [ | |
| 177 | − ...(tool.capability.native ?? []).map((blob, index) => ({ | |
| 178 | − kind: "code" as const, | |
| 179 | − title: `Capability native ${String(index + 1)}: ${blob.kind}`, | |
| 180 | − language: nativeEncodingLanguage(blob.encoding), | |
| 181 | − body: | |
| 182 | − typeof blob.value === "string" | |
| 183 | − ? blob.value | |
| 184 | − : JSON.stringify(blob.value ?? null, null, 2), | |
| 185 | − })), | |
| 186 | − ...(tool.executable.native ?? []).map((blob, index) => ({ | |
| 187 | − kind: "code" as const, | |
| 188 | − title: `Executable native ${String(index + 1)}: ${blob.kind}`, | |
| 189 | − language: nativeEncodingLanguage(blob.encoding), | |
| 190 | − body: | |
| 191 | − typeof blob.value === "string" | |
| 192 | − ? blob.value | |
| 193 | − : JSON.stringify(blob.value ?? null, null, 2), | |
| 194 | − })), | |
| 195 | − ]; | |
| 196 | − const sections = [ | |
| 197 | − { | |
| 198 | − kind: "facts" as const, | |
| 199 | − title: "Overview", | |
| 200 | − items: overviewItems, | |
| 201 | − }, | |
| 202 | − ...(summary.description | |
| 203 | − ? [{ | |
| 204 | − kind: "markdown" as const, | |
| 205 | − title: "Description", | |
| 206 | − body: summary.description, | |
| 207 | − }] | |
| 208 | − : []), | |
| 209 | − ...([ | |
| 210 | − jsonSection("Input Schema", tool.descriptor.inputSchema), | |
| 211 | − jsonSection("Output Schema", tool.descriptor.outputSchema), | |
| 212 | − jsonSection("Capability", tool.capability), | |
| 213 | − jsonSection("Executable", { | |
| 214 | − id: tool.executable.id, | |
| 215 | − adapterKey: tool.executable.adapterKey, | |
| 216 | − bindingVersion: tool.executable.bindingVersion, | |
| 217 | − binding: tool.executable.binding, | |
| 218 | − projection: tool.executable.projection, | |
| 219 | − display: tool.executable.display ?? null, | |
| 220 | − }), | |
| 221 | − jsonSection("Documentation", { | |
| 222 | − summary: tool.capability.surface.summary, | |
| 223 | − description: tool.capability.surface.description, | |
| 224 | − }), | |
| 225 | − ].filter((section) => section !== null) as Array<SourceInspectionToolDetail["sections"][number]>), | |
| 226 | − ...nativeSections, | |
| 227 | − ]; | |
| 228 | − | |
| 229 | − return ({ | |
| 230 | − summary, | |
| 231 | − sections, | |
| 167 | +): Effect.Effect<SourceInspectionToolDetail, Error, never> => | |
| 168 | + Effect.gen(function* () { | |
| 169 | + const summary = persistedToolSummaryFromTool(tool); | |
| 170 | + const details = executableDetails(tool); | |
| 171 | + const contract = yield* buildLoadedSourceCatalogToolContract(tool); | |
| 172 | + const overviewItems = [ | |
| 173 | + { label: "Protocol", value: details.protocol }, | |
| 174 | + ...(details.method ? [{ label: "Method", value: details.method, mono: true }] : []), | |
| 175 | + ...(details.pathTemplate ? [{ label: "Target", value: details.pathTemplate, mono: true }] : []), | |
| 176 | + ...(details.operationId ? [{ label: "Operation", value: details.operationId, mono: true }] : []), | |
| 177 | + ...(details.group ? [{ label: "Group", value: details.group, mono: true }] : []), | |
| 178 | + ...(details.leaf ? [{ label: "Leaf", value: details.leaf, mono: true }] : []), | |
| 179 | + ...(details.rawToolId ? [{ label: "Raw tool", value: details.rawToolId, mono: true }] : []), | |
| 180 | + { label: "Signature", value: contract.callSignature, mono: true }, | |
| 181 | + { label: "Call shape", value: contract.callShapeId, mono: true }, | |
| 182 | + ...(contract.resultShapeId | |
| 183 | + ? [{ label: "Result shape", value: contract.resultShapeId, mono: true }] | |
| 184 | + : []), | |
| 185 | + { label: "Response set", value: contract.responseSetId, mono: true }, | |
| 186 | + ]; | |
| 187 | + const nativeSections = [ | |
| 188 | + ...(tool.capability.native ?? []).map((blob, index) => ({ | |
| 189 | + kind: "code" as const, | |
| 190 | + title: `Capability native ${String(index + 1)}: ${blob.kind}`, | |
| 191 | + language: nativeEncodingLanguage(blob.encoding), | |
| 192 | + body: | |
| 193 | + typeof blob.value === "string" | |
| 194 | + ? blob.value | |
| 195 | + : JSON.stringify(blob.value ?? null, null, 2), | |
| 196 | + })), | |
| 197 | + ...(tool.executable.native ?? []).map((blob, index) => ({ | |
| 198 | + kind: "code" as const, | |
| 199 | + title: `Executable native ${String(index + 1)}: ${blob.kind}`, | |
| 200 | + language: nativeEncodingLanguage(blob.encoding), | |
| 201 | + body: | |
| 202 | + typeof blob.value === "string" | |
| 203 | + ? blob.value | |
| 204 | + : JSON.stringify(blob.value ?? null, null, 2), | |
| 205 | + })), | |
| 206 | + ]; | |
| 207 | + const sections = [ | |
| 208 | + { | |
| 209 | + kind: "facts" as const, | |
| 210 | + title: "Overview", | |
| 211 | + items: overviewItems, | |
| 212 | + }, | |
| 213 | + ...(summary.description | |
| 214 | + ? [{ | |
| 215 | + kind: "markdown" as const, | |
| 216 | + title: "Description", | |
| 217 | + body: summary.description, | |
| 218 | + }] | |
| 219 | + : []), | |
| 220 | + ...([ | |
| 221 | + jsonSection("Capability", tool.capability), | |
| 222 | + jsonSection("Executable", { | |
| 223 | + id: tool.executable.id, | |
| 224 | + adapterKey: tool.executable.adapterKey, | |
| 225 | + bindingVersion: tool.executable.bindingVersion, | |
| 226 | + binding: tool.executable.binding, | |
| 227 | + projection: tool.executable.projection, | |
| 228 | + display: tool.executable.display ?? null, | |
| 229 | + }), | |
| 230 | + jsonSection("Documentation", { | |
| 231 | + summary: tool.capability.surface.summary, | |
| 232 | + description: tool.capability.surface.description, | |
| 233 | + }), | |
| 234 | + ].filter((section) => section !== null) as Array<SourceInspectionToolDetail["sections"][number]>), | |
| 235 | + ...nativeSections, | |
| 236 | + ]; | |
| 237 | + | |
| 238 | + return { | |
| 239 | + summary, | |
| 240 | + contract, | |
| 241 | + sections, | |
| 242 | + } satisfies SourceInspectionToolDetail; | |
| 232 | 243 | }); |
| 233 | −}; | |
| 234 | 244 | |
| 235 | 245 | const resolveSourceInspection = (input: { |
| 236 | 246 | workspaceId: WorkspaceId; |
| 237 | 247 | sourceId: SourceId; |
| 238 | 248 | includeSchemas: boolean; |
| 249 | + includeTypePreviews: boolean; | |
| 239 | 250 | }) => |
| 240 | 251 | Effect.gen(function* () { |
| 241 | 252 | const loaded = yield* loadSourceCatalogOrEmpty({ |
| @@ -255,7 +266,7 @@ const resolveSourceInspection = (input: { · 13 unmodified lines | ||
| 255 | 266 | const tools = yield* expandCatalogTools({ |
| 256 | 267 | catalogs: [loaded.catalogEntry], |
| 257 | 268 | includeSchemas: input.includeSchemas, |
| 258 | − includeTypePreviews: input.includeSchemas, | |
| 269 | + includeTypePreviews: input.includeTypePreviews, | |
| 259 | 270 | }); |
| 260 | 271 | |
| 261 | 272 | return { |
| @@ -289,8 +300,8 @@ const resolveSourceInspectionTool = (input: { · 27 unmodified lines | ||
| 289 | 300 | const tool = yield* expandCatalogToolByPath({ |
| 290 | 301 | catalogs: [loaded.catalogEntry], |
| 291 | 302 | path: input.toolPath, |
| 292 | − includeSchemas: false, | |
| 293 | − includeTypePreviews: false, | |
| 303 | + includeSchemas: true, | |
| 304 | + includeTypePreviews: true, | |
| 294 | 305 | }); |
| 295 | 306 | |
| 296 | 307 | return { |
| @@ -382,6 +393,7 @@ export const getSourceInspection = (input: { · 85 unmodified lines | ||
| 382 | 393 | const inspection = yield* resolveSourceInspection({ |
| 383 | 394 | ...input, |
| 384 | 395 | includeSchemas: false, |
| 396 | + includeTypePreviews: true, | |
| 385 | 397 | }); |
| 386 | 398 | |
| 387 | 399 | return { |
| @@ -420,7 +432,7 @@ export const getSourceInspectionToolDetail = (input: { · 32 unmodified lines | ||
| 420 | 432 | ); |
| 421 | 433 | } |
| 422 | 434 | |
| 423 | − return inspectionToolDetailFromTool(tool); | |
| 435 | + return yield* inspectionToolDetailFromTool(tool); | |
| 424 | 436 | }).pipe( |
| 425 | 437 | Effect.mapError((cause) => |
| 426 | 438 | mapInspectionError( |
| @@ -440,6 +452,7 @@ export const discoverSourceInspectionTools = (input: { · 13 unmodified lines | ||
| 440 | 452 | workspaceId: input.workspaceId, |
| 441 | 453 | sourceId: input.sourceId, |
| 442 | 454 | includeSchemas: false, |
| 455 | + includeTypePreviews: true, | |
| 443 | 456 | }); |
| 444 | 457 | const queryTokens = tokenize(input.payload.query); |
| 445 | 458 | const results = inspection.tools |
| @@ -56,6 +56,26 @@ export const SourceInspectionSectionSchema = Schema.Union( · 55 unmodified lines | ||
| 56 | 56 | export const SourceInspectionToolListItemSchema = Schema.Struct({ |
| 57 | 57 | path: Schema.String, |
| 58 | 58 | method: Schema.NullOr(Schema.String), |
| 59 | + inputTypePreview: Schema.optional(Schema.String), | |
| 60 | + outputTypePreview: Schema.optional(Schema.String), | |
| 61 | +}); | |
| 62 | + | |
| 63 | +export const SourceInspectionToolContractSideSchema = Schema.Struct({ | |
| 64 | + shapeId: Schema.NullOr(Schema.String), | |
| 65 | + typePreview: Schema.NullOr(Schema.String), | |
| 66 | + typeDeclaration: Schema.NullOr(Schema.String), | |
| 67 | + schemaJson: Schema.NullOr(Schema.String), | |
| 68 | + exampleJson: Schema.NullOr(Schema.String), | |
| 69 | +}); | |
| 70 | + | |
| 71 | +export const SourceInspectionToolContractSchema = Schema.Struct({ | |
| 72 | + callSignature: Schema.String, | |
| 73 | + callDeclaration: Schema.String, | |
| 74 | + callShapeId: Schema.String, | |
| 75 | + resultShapeId: Schema.NullOr(Schema.String), | |
| 76 | + responseSetId: Schema.String, | |
| 77 | + input: SourceInspectionToolContractSideSchema, | |
| 78 | + output: SourceInspectionToolContractSideSchema, | |
| 59 | 79 | }); |
| 60 | 80 | |
| 61 | 81 | export const SourceInspectionSchema = Schema.Struct({ |
| @@ -68,6 +88,7 @@ export const SourceInspectionSchema = Schema.Struct({ · 6 unmodified lines | ||
| 68 | 88 | |
| 69 | 89 | export const SourceInspectionToolDetailSchema = Schema.Struct({ |
| 70 | 90 | summary: SourceInspectionToolSummarySchema, |
| 91 | + contract: SourceInspectionToolContractSchema, | |
| 71 | 92 | sections: Schema.Array(SourceInspectionSectionSchema), |
| 72 | 93 | }); |
| 73 | 94 | |
| @@ -109,6 +130,10 @@ export type SourceInspectionSection = · 35 unmodified lines | ||
| 109 | 130 | typeof SourceInspectionSectionSchema.Type; |
| 110 | 131 | export type SourceInspectionToolListItem = |
| 111 | 132 | typeof SourceInspectionToolListItemSchema.Type; |
| 133 | +export type SourceInspectionToolContractSide = | |
| 134 | + typeof SourceInspectionToolContractSideSchema.Type; | |
| 135 | +export type SourceInspectionToolContract = | |
| 136 | + typeof SourceInspectionToolContractSchema.Type; | |
| 112 | 137 | export type SourceInspection = typeof SourceInspectionSchema.Type; |
| 113 | 138 | export type SourceInspectionToolDetail = |
| 114 | 139 | typeof SourceInspectionToolDetailSchema.Type; |
| @@ -25,6 +25,27 @@ describe("control-plane-schema", () => { · 24 unmodified lines | ||
| 25 | 25 | method: "GET", |
| 26 | 26 | pathTemplate: "/issues", |
| 27 | 27 | }, |
| 28 | + contract: { | |
| 29 | + callSignature: "(args: GithubIssuesListCall) => Promise<GithubIssuesListResult>", | |
| 30 | + callDeclaration: "declare function githubIssuesList(args: GithubIssuesListCall): Promise<GithubIssuesListResult>;", | |
| 31 | + callShapeId: "shape_call", | |
| 32 | + resultShapeId: "shape_result", | |
| 33 | + responseSetId: "response_set_1", | |
| 34 | + input: { | |
| 35 | + shapeId: "shape_call", | |
| 36 | + typePreview: "{ owner: string; repo: string }", | |
| 37 | + typeDeclaration: "type GithubIssuesListCall = { owner: string; repo: string };", | |
| 38 | + schemaJson: "{}", | |
| 39 | + exampleJson: null, | |
| 40 | + }, | |
| 41 | + output: { | |
| 42 | + shapeId: "shape_result", | |
| 43 | + typePreview: "{ data: unknown; error: unknown; headers: Record<string, string>; status: number | null }", | |
| 44 | + typeDeclaration: "type GithubIssuesListResult = { data: unknown; error: unknown; headers: Record<string, string>; status: number | null };", | |
| 45 | + schemaJson: "{}", | |
| 46 | + exampleJson: null, | |
| 47 | + }, | |
| 48 | + }, | |
| 28 | 49 | sections: [{ |
| 29 | 50 | kind: "facts", |
| 30 | 51 | title: "Overview", |
| @@ -456,12 +456,16 @@ export const createGoogleDiscoveryToolFromDefinition = ( · 455 unmodified lines | ||
| 456 | 456 | interaction: input.definition.method === "get" || input.definition.method === "head" |
| 457 | 457 | ? "auto" |
| 458 | 458 | : "required", |
| 459 | − ...(presentation.inputSchema !== undefined | |
| 460 | − ? { inputSchema: presentation.inputSchema } | |
| 461 | − : {}), | |
| 462 | − ...(presentation.outputSchema !== undefined | |
| 463 | − ? { outputSchema: presentation.outputSchema } | |
| 464 | − : {}), | |
| 459 | + contract: { | |
| 460 | + inputTypePreview: presentation.inputTypePreview, | |
| 461 | + outputTypePreview: presentation.outputTypePreview, | |
| 462 | + ...(presentation.inputSchema !== undefined | |
| 463 | + ? { inputSchema: presentation.inputSchema } | |
| 464 | + : {}), | |
| 465 | + ...(presentation.outputSchema !== undefined | |
| 466 | + ? { outputSchema: presentation.outputSchema } | |
| 467 | + : {}), | |
| 468 | + }, | |
| 465 | 469 | sourceKey: input.sourceKey, |
| 466 | 470 | providerKind: "google_discovery", |
| 467 | 471 | providerData, |
| @@ -1437,25 +1437,27 @@ export const createGraphqlToolFromPersistedOperation = (input: { · 1436 unmodified lines | ||
| 1437 | 1437 | : providerData.operationType === "query" |
| 1438 | 1438 | ? "auto" |
| 1439 | 1439 | : "required", |
| 1440 | − inputTypePreview: typeSignatureFromSchema( | |
| 1441 | − input.inputSchema, | |
| 1442 | − "unknown", | |
| 1443 | − GRAPHQL_PRESENTATION_TYPE_MAX_LENGTH, | |
| 1444 | − ), | |
| 1445 | − outputTypePreview: typeSignatureFromSchema( | |
| 1446 | − input.outputSchema, | |
| 1447 | − "unknown", | |
| 1448 | − GRAPHQL_PRESENTATION_TYPE_MAX_LENGTH, | |
| 1449 | − ), | |
| 1450 | − ...(input.inputSchema !== undefined | |
| 1451 | − ? { inputSchema: input.inputSchema } | |
| 1452 | − : {}), | |
| 1453 | − ...(input.outputSchema !== undefined | |
| 1454 | − ? { outputSchema: input.outputSchema } | |
| 1455 | − : {}), | |
| 1456 | − ...(input.exampleInput !== undefined | |
| 1457 | − ? { exampleInput: input.exampleInput } | |
| 1458 | − : {}), | |
| 1440 | + contract: { | |
| 1441 | + inputTypePreview: typeSignatureFromSchema( | |
| 1442 | + input.inputSchema, | |
| 1443 | + "unknown", | |
| 1444 | + GRAPHQL_PRESENTATION_TYPE_MAX_LENGTH, | |
| 1445 | + ), | |
| 1446 | + outputTypePreview: typeSignatureFromSchema( | |
| 1447 | + input.outputSchema, | |
| 1448 | + "unknown", | |
| 1449 | + GRAPHQL_PRESENTATION_TYPE_MAX_LENGTH, | |
| 1450 | + ), | |
| 1451 | + ...(input.inputSchema !== undefined | |
| 1452 | + ? { inputSchema: input.inputSchema } | |
| 1453 | + : {}), | |
| 1454 | + ...(input.outputSchema !== undefined | |
| 1455 | + ? { outputSchema: input.outputSchema } | |
| 1456 | + : {}), | |
| 1457 | + ...(input.exampleInput !== undefined | |
| 1458 | + ? { exampleInput: input.exampleInput } | |
| 1459 | + : {}), | |
| 1460 | + }, | |
| 1459 | 1461 | sourceKey: input.sourceKey, |
| 1460 | 1462 | providerKind: "graphql", |
| 1461 | 1463 | providerData, |
| @@ -1524,17 +1526,19 @@ export const graphqlToolDescriptorFromDefinition = (input: { · 62 unmodified lines | ||
| 1524 | 1526 | description: input.definition.description, |
| 1525 | 1527 | interaction: |
| 1526 | 1528 | input.definition.operationType === "query" ? "auto" : "required", |
| 1527 | − inputTypePreview: presentation.inputTypePreview, | |
| 1528 | − outputTypePreview: presentation.outputTypePreview, | |
| 1529 | − ...(input.includeSchemas && presentation.inputSchema !== undefined | |
| 1530 | − ? { inputSchema: presentation.inputSchema } | |
| 1531 | − : {}), | |
| 1532 | − ...(input.includeSchemas && presentation.outputSchema !== undefined | |
| 1533 | − ? { outputSchema: presentation.outputSchema } | |
| 1534 | − : {}), | |
| 1535 | − ...(presentation.exampleInput !== undefined | |
| 1536 | − ? { exampleInput: presentation.exampleInput } | |
| 1537 | − : {}), | |
| 1529 | + contract: { | |
| 1530 | + inputTypePreview: presentation.inputTypePreview, | |
| 1531 | + outputTypePreview: presentation.outputTypePreview, | |
| 1532 | + ...(input.includeSchemas && presentation.inputSchema !== undefined | |
| 1533 | + ? { inputSchema: presentation.inputSchema } | |
| 1534 | + : {}), | |
| 1535 | + ...(input.includeSchemas && presentation.outputSchema !== undefined | |
| 1536 | + ? { outputSchema: presentation.outputSchema } | |
| 1537 | + : {}), | |
| 1538 | + ...(presentation.exampleInput !== undefined | |
| 1539 | + ? { exampleInput: presentation.exampleInput } | |
| 1540 | + : {}), | |
| 1541 | + }, | |
| 1538 | 1542 | providerKind: "graphql", |
| 1539 | 1543 | providerData: presentation.providerData, |
| 1540 | 1544 | }; |
| @@ -4,7 +4,8 @@ import { · 3 unmodified lines | ||
| 4 | 4 | type ToolInput, |
| 5 | 5 | type ToolPath, |
| 6 | 6 | } from "@executor/codemode-core"; |
| 7 | −import { createSdkMcpConnector } from "./connection"; | |
| 7 | +import { createPooledMcpConnector } from "./connection-pool"; | |
| 8 | +import { createSdkMcpConnector, isMcpStdioTransport } from "./connection"; | |
| 8 | 9 | import { |
| 9 | 10 | createMcpToolsFromManifest, |
| 10 | 11 | discoverMcpToolsFromConnector, |
| @@ -26,8 +27,8 @@ import { · 15 unmodified lines | ||
| 26 | 27 | encodeBindingConfig, |
| 27 | 28 | McpConnectFieldsSchema, |
| 28 | 29 | OptionalNullableStringSchema, |
| 29 | − SourceConnectCommonFieldsSchema, | |
| 30 | 30 | SourceTransportSchema, |
| 31 | + StringArraySchema, | |
| 31 | 32 | StringMapSchema, |
| 32 | 33 | createCatalogImportMetadata, |
| 33 | 34 | EXECUTABLE_BINDING_VERSION, |
| @@ -35,6 +36,7 @@ import { · 1 unmodified line | ||
| 35 | 36 | type Source, |
| 36 | 37 | type SourceAdapter, |
| 37 | 38 | type SourceCatalogSyncResult, |
| 39 | + type SourceTransport, | |
| 38 | 40 | } from "@executor/source-core"; |
| 39 | 41 | import * as Effect from "effect/Effect"; |
| 40 | 42 | import * as Schema from "effect/Schema"; |
| @@ -69,27 +71,38 @@ const namespaceFromSourceName = (name: string): string => { · 28 unmodified lines | ||
| 69 | 71 | return normalized.length > 0 ? normalized : "source"; |
| 70 | 72 | }; |
| 71 | 73 | |
| 74 | +const OptionalNullableStringArraySchema = Schema.optional( | |
| 75 | + Schema.NullOr(StringArraySchema), | |
| 76 | +); | |
| 77 | + | |
| 72 | 78 | const McpConnectPayloadSchema = Schema.extend( |
| 73 | − SourceConnectCommonFieldsSchema, | |
| 74 | − Schema.extend( | |
| 75 | − McpConnectFieldsSchema, | |
| 76 | − Schema.Struct({ | |
| 77 | − kind: Schema.Literal("mcp"), | |
| 78 | − }), | |
| 79 | − ), | |
| 79 | + McpConnectFieldsSchema, | |
| 80 | + Schema.Struct({ | |
| 81 | + kind: Schema.Literal("mcp"), | |
| 82 | + endpoint: OptionalNullableStringSchema, | |
| 83 | + name: OptionalNullableStringSchema, | |
| 84 | + namespace: OptionalNullableStringSchema, | |
| 85 | + }), | |
| 80 | 86 | ); |
| 81 | 87 | |
| 82 | −const McpExecutorAddInputSchema = Schema.Struct({ | |
| 83 | − kind: Schema.optional(Schema.Literal("mcp")), | |
| 84 | − endpoint: Schema.String, | |
| 85 | − name: OptionalNullableStringSchema, | |
| 86 | − namespace: OptionalNullableStringSchema, | |
| 87 | −}); | |
| 88 | +const McpExecutorAddInputSchema = Schema.extend( | |
| 89 | + McpConnectFieldsSchema, | |
| 90 | + Schema.Struct({ | |
| 91 | + kind: Schema.optional(Schema.Literal("mcp")), | |
| 92 | + endpoint: OptionalNullableStringSchema, | |
| 93 | + name: OptionalNullableStringSchema, | |
| 94 | + namespace: OptionalNullableStringSchema, | |
| 95 | + }), | |
| 96 | +); | |
| 88 | 97 | |
| 89 | 98 | const McpBindingConfigSchema = Schema.Struct({ |
| 90 | 99 | transport: Schema.NullOr(SourceTransportSchema), |
| 91 | 100 | queryParams: Schema.NullOr(StringMapSchema), |
| 92 | 101 | headers: Schema.NullOr(StringMapSchema), |
| 102 | + command: Schema.NullOr(Schema.String), | |
| 103 | + args: Schema.NullOr(StringArraySchema), | |
| 104 | + env: Schema.NullOr(StringMapSchema), | |
| 105 | + cwd: Schema.NullOr(Schema.String), | |
| 93 | 106 | }); |
| 94 | 107 | |
| 95 | 108 | type McpBindingConfig = typeof McpBindingConfigSchema.Type; |
| @@ -98,6 +111,10 @@ const McpSourceBindingPayloadSchema = Schema.Struct({ · 2 unmodified lines | ||
| 98 | 111 | transport: Schema.optional(Schema.NullOr(SourceTransportSchema)), |
| 99 | 112 | queryParams: Schema.optional(Schema.NullOr(StringMapSchema)), |
| 100 | 113 | headers: Schema.optional(Schema.NullOr(StringMapSchema)), |
| 114 | + command: Schema.optional(Schema.NullOr(Schema.String)), | |
| 115 | + args: OptionalNullableStringArraySchema, | |
| 116 | + env: Schema.optional(Schema.NullOr(StringMapSchema)), | |
| 117 | + cwd: Schema.optional(Schema.NullOr(Schema.String)), | |
| 101 | 118 | }); |
| 102 | 119 | |
| 103 | 120 | const McpExecutableBindingSchema = Schema.Struct({ |
| @@ -127,6 +144,86 @@ const bindingHasAnyField = ( · 23 unmodified lines | ||
| 127 | 144 | !Array.isArray(value) && |
| 128 | 145 | fields.some((field) => Object.prototype.hasOwnProperty.call(value, field)); |
| 129 | 146 | |
| 147 | +const trimOrNull = (value: string | null | undefined): string | null => { | |
| 148 | + if (value === null || value === undefined) { | |
| 149 | + return null; | |
| 150 | + } | |
| 151 | + | |
| 152 | + const trimmed = value.trim(); | |
| 153 | + return trimmed.length > 0 ? trimmed : null; | |
| 154 | +}; | |
| 155 | + | |
| 156 | +const normalizeStringArray = ( | |
| 157 | + value: ReadonlyArray<string> | null | undefined, | |
| 158 | +): string[] | null => { | |
| 159 | + if (!value || value.length === 0) { | |
| 160 | + return null; | |
| 161 | + } | |
| 162 | + | |
| 163 | + const normalized = value | |
| 164 | + .map((entry) => entry.trim()) | |
| 165 | + .filter((entry) => entry.length > 0); | |
| 166 | + | |
| 167 | + return normalized.length > 0 ? normalized : null; | |
| 168 | +}; | |
| 169 | + | |
| 170 | +const normalizeMcpBindingConfig = ( | |
| 171 | + bindingConfig: { | |
| 172 | + transport?: SourceTransport | null | undefined; | |
| 173 | + queryParams?: Record<string, string> | null | undefined; | |
| 174 | + headers?: Record<string, string> | null | undefined; | |
| 175 | + command?: string | null | undefined; | |
| 176 | + args?: ReadonlyArray<string> | null | undefined; | |
| 177 | + env?: Record<string, string> | null | undefined; | |
| 178 | + cwd?: string | null | undefined; | |
| 179 | + }, | |
| 180 | +): Effect.Effect<McpBindingConfig, Error, never> => | |
| 181 | + Effect.gen(function* () { | |
| 182 | + const command = trimOrNull(bindingConfig.command); | |
| 183 | + const isStdio = isMcpStdioTransport({ | |
| 184 | + transport: bindingConfig.transport ?? undefined, | |
| 185 | + command: command ?? undefined, | |
| 186 | + }); | |
| 187 | + | |
| 188 | + if (isStdio) { | |
| 189 | + if (command === null) { | |
| 190 | + return yield* sourceCoreEffectError("mcp/adapter", "MCP stdio transport requires a command"); | |
| 191 | + } | |
| 192 | + | |
| 193 | + if (bindingConfig.queryParams && Object.keys(bindingConfig.queryParams).length > 0) { | |
| 194 | + return yield* sourceCoreEffectError("mcp/adapter", "MCP stdio transport does not support query params"); | |
| 195 | + } | |
| 196 | + | |
| 197 | + if (bindingConfig.headers && Object.keys(bindingConfig.headers).length > 0) { | |
| 198 | + return yield* sourceCoreEffectError("mcp/adapter", "MCP stdio transport does not support request headers"); | |
| 199 | + } | |
| 200 | + | |
| 201 | + return { | |
| 202 | + transport: "stdio", | |
| 203 | + queryParams: null, | |
| 204 | + headers: null, | |
| 205 | + command, | |
| 206 | + args: normalizeStringArray(bindingConfig.args), | |
| 207 | + env: bindingConfig.env ?? null, | |
| 208 | + cwd: trimOrNull(bindingConfig.cwd), | |
| 209 | + } satisfies McpBindingConfig; | |
| 210 | + } | |
| 211 | + | |
| 212 | + if (command !== null || bindingConfig.args || bindingConfig.env || trimOrNull(bindingConfig.cwd) !== null) { | |
| 213 | + return yield* sourceCoreEffectError("mcp/adapter", "MCP process settings require transport: \"stdio\""); | |
| 214 | + } | |
| 215 | + | |
| 216 | + return { | |
| 217 | + transport: bindingConfig.transport ?? null, | |
| 218 | + queryParams: bindingConfig.queryParams ?? null, | |
| 219 | + headers: bindingConfig.headers ?? null, | |
| 220 | + command: null, | |
| 221 | + args: null, | |
| 222 | + env: null, | |
| 223 | + cwd: null, | |
| 224 | + } satisfies McpBindingConfig; | |
| 225 | + }); | |
| 226 | + | |
| 130 | 227 | const mcpBindingConfigFromSource = ( |
| 131 | 228 | source: Pick<Source, "id" | "bindingVersion" | "binding">, |
| 132 | 229 | ): Effect.Effect<McpBindingConfig, Error, never> => |
| @@ -145,14 +242,10 @@ const mcpBindingConfigFromSource = ( · 12 unmodified lines | ||
| 145 | 242 | expectedVersion: MCP_BINDING_CONFIG_VERSION, |
| 146 | 243 | schema: McpSourceBindingPayloadSchema, |
| 147 | 244 | value: source.binding, |
| 148 | − allowedKeys: ["transport", "queryParams", "headers"], | |
| 245 | + allowedKeys: ["transport", "queryParams", "headers", "command", "args", "env", "cwd"], | |
| 149 | 246 | }); |
| 150 | 247 | |
| 151 | − return { | |
| 152 | − transport: bindingConfig.transport ?? null, | |
| 153 | − queryParams: bindingConfig.queryParams ?? null, | |
| 154 | − headers: bindingConfig.headers ?? null, | |
| 155 | − } satisfies McpBindingConfig; | |
| 248 | + return yield* normalizeMcpBindingConfig(bindingConfig); | |
| 156 | 249 | }); |
| 157 | 250 | |
| 158 | 251 | const effectFromMcpManifestEntry = ( |
| @@ -233,7 +326,8 @@ export const mcpSourceAdapter: SourceAdapter = { · 74 unmodified lines | ||
| 233 | 326 | connectPayloadSchema: McpConnectPayloadSchema, |
| 234 | 327 | executorAddInputSchema: McpExecutorAddInputSchema, |
| 235 | 328 | executorAddHelpText: [ |
| 236 | − 'Omit kind or set kind: "mcp". endpoint is the MCP server URL.', | |
| 329 | + 'Omit kind or set kind: "mcp". For remote servers, provide endpoint plus optional transport/queryParams/headers.', | |
| 330 | + 'For local servers, set transport: "stdio" and provide command plus optional args/env/cwd.', | |
| 237 | 331 | ], |
| 238 | 332 | executorAddInputSignatureWidth: 240, |
| 239 | 333 | serializeBindingConfig: (source) => |
| @@ -264,6 +358,10 @@ export const mcpSourceAdapter: SourceAdapter = { · 24 unmodified lines | ||
| 264 | 358 | transport: bindingConfig.transport, |
| 265 | 359 | queryParams: bindingConfig.queryParams, |
| 266 | 360 | headers: bindingConfig.headers, |
| 361 | + command: bindingConfig.command, | |
| 362 | + args: bindingConfig.args, | |
| 363 | + env: bindingConfig.env, | |
| 364 | + cwd: bindingConfig.cwd, | |
| 267 | 365 | })), |
| 268 | 366 | sourceConfigFromSource: (source) => |
| 269 | 367 | Effect.runSync( |
| @@ -273,6 +371,10 @@ export const mcpSourceAdapter: SourceAdapter = { · 3 unmodified lines | ||
| 273 | 371 | transport: bindingConfig.transport, |
| 274 | 372 | queryParams: bindingConfig.queryParams, |
| 275 | 373 | headers: bindingConfig.headers, |
| 374 | + command: bindingConfig.command, | |
| 375 | + args: bindingConfig.args, | |
| 376 | + env: bindingConfig.env, | |
| 377 | + cwd: bindingConfig.cwd, | |
| 276 | 378 | })), |
| 277 | 379 | ), |
| 278 | 380 | validateSource: (source) => |
| @@ -286,6 +388,10 @@ export const mcpSourceAdapter: SourceAdapter = { · 7 unmodified lines | ||
| 286 | 388 | transport: bindingConfig.transport, |
| 287 | 389 | queryParams: bindingConfig.queryParams, |
| 288 | 390 | headers: bindingConfig.headers, |
| 391 | + command: bindingConfig.command, | |
| 392 | + args: bindingConfig.args, | |
| 393 | + env: bindingConfig.env, | |
| 394 | + cwd: bindingConfig.cwd, | |
| 289 | 395 | }, |
| 290 | 396 | }; |
| 291 | 397 | }), |
| @@ -309,6 +415,10 @@ export const mcpSourceAdapter: SourceAdapter = { · 17 unmodified lines | ||
| 309 | 415 | authCookies: auth.cookies, |
| 310 | 416 | }), |
| 311 | 417 | authProvider: auth.authProvider, |
| 418 | + command: bindingConfig.command ?? undefined, | |
| 419 | + args: bindingConfig.args ?? undefined, | |
| 420 | + env: bindingConfig.env ?? undefined, | |
| 421 | + cwd: bindingConfig.cwd ?? undefined, | |
| 312 | 422 | }), |
| 313 | 423 | catch: (cause) => |
| 314 | 424 | cause instanceof Error ? cause : new Error(String(cause)), |
| @@ -353,19 +463,30 @@ export const mcpSourceAdapter: SourceAdapter = { · 38 unmodified lines | ||
| 353 | 463 | const bindingConfig = Effect.runSync( |
| 354 | 464 | mcpBindingConfigFromSource(input.source), |
| 355 | 465 | ); |
| 356 | − const connector = createSdkMcpConnector({ | |
| 357 | − endpoint: input.source.endpoint, | |
| 358 | − transport: bindingConfig.transport ?? undefined, | |
| 359 | − queryParams: { | |
| 360 | − ...bindingConfig.queryParams, | |
| 361 | − ...input.auth.queryParams, | |
| 362 | − }, | |
| 363 | − headers: headersWithAuthCookies({ | |
| 364 | − headers: bindingConfig.headers ?? {}, | |
| 365 | − authHeaders: input.auth.headers, | |
| 366 | − authCookies: input.auth.cookies, | |
| 466 | + const connector = createPooledMcpConnector({ | |
| 467 | + connect: createSdkMcpConnector({ | |
| 468 | + endpoint: input.source.endpoint, | |
| 469 | + transport: bindingConfig.transport ?? undefined, | |
| 470 | + queryParams: { | |
| 471 | + ...bindingConfig.queryParams, | |
| 472 | + ...input.auth.queryParams, | |
| 473 | + }, | |
| 474 | + headers: headersWithAuthCookies({ | |
| 475 | + headers: bindingConfig.headers ?? {}, | |
| 476 | + authHeaders: input.auth.headers, | |
| 477 | + authCookies: input.auth.cookies, | |
| 478 | + }), | |
| 479 | + authProvider: input.auth.authProvider, | |
| 480 | + command: bindingConfig.command ?? undefined, | |
| 481 | + args: bindingConfig.args ?? undefined, | |
| 482 | + env: bindingConfig.env ?? undefined, | |
| 483 | + cwd: bindingConfig.cwd ?? undefined, | |
| 367 | 484 | }), |
| 368 | − authProvider: input.auth.authProvider, | |
| 485 | + runId: | |
| 486 | + typeof input.context?.runId === "string" && input.context.runId.length > 0 | |
| 487 | + ? input.context.runId | |
| 488 | + : undefined, | |
| 489 | + sourceKey: input.source.id, | |
| 369 | 490 | }); |
| 370 | 491 | const tools = createMcpToolsFromManifest({ |
| 371 | 492 | manifest: { |
| @@ -392,8 +513,8 @@ export const mcpSourceAdapter: SourceAdapter = { · 20 unmodified lines | ||
| 392 | 513 | icons: null, |
| 393 | 514 | meta: null, |
| 394 | 515 | rawTool: null, |
| 395 | − inputSchema: input.descriptor.inputSchema, | |
| 396 | − outputSchema: input.descriptor.outputSchema, | |
| 516 | + inputSchema: input.descriptor.contract?.inputSchema, | |
| 517 | + outputSchema: input.descriptor.contract?.outputSchema, | |
| 397 | 518 | }, |
| 398 | 519 | ], |
| 399 | 520 | }, |
| @@ -430,8 +551,14 @@ export const mcpSourceAdapter: SourceAdapter = { · 30 unmodified lines | ||
| 430 | 551 | metadata: { |
| 431 | 552 | sourceKey: input.source.id, |
| 432 | 553 | interaction: input.descriptor.interaction, |
| 433 | − inputSchema: input.descriptor.inputSchema, | |
| 434 | − outputSchema: input.descriptor.outputSchema, | |
| 554 | + contract: { | |
| 555 | + ...(input.descriptor.contract?.inputSchema !== undefined | |
| 556 | + ? { inputSchema: input.descriptor.contract.inputSchema } | |
| 557 | + : {}), | |
| 558 | + ...(input.descriptor.contract?.outputSchema !== undefined | |
| 559 | + ? { outputSchema: input.descriptor.contract.outputSchema } | |
| 560 | + : {}), | |
| 561 | + }, | |
| 435 | 562 | providerKind: input.descriptor.providerKind, |
| 436 | 563 | providerData: input.descriptor.providerData, |
| 437 | 564 | }, |
| @@ -377,7 +377,7 @@ describe("codemode-mcp", () => { · 376 unmodified lines | ||
| 377 | 377 | |
| 378 | 378 | const toolDefinition = resolveToolDefinition(discovered.tools["source.mcp.echo"]!); |
| 379 | 379 | expect(toolDefinition.metadata?.sourceKey).toBe("mcp.demo"); |
| 380 | − expect(JSON.stringify(toolDefinition.metadata?.inputSchema)).toContain("value"); | |
| 380 | + expect(JSON.stringify(toolDefinition.metadata?.contract?.inputSchema)).toContain("value"); | |
| 381 | 381 | |
| 382 | 382 | const invoke = resolveToolExecutor(discovered.tools, "source.mcp.echo"); |
| 383 | 383 | const invocationResult = yield* Effect.promise(() => invoke({ value: "hello" })); |
| @@ -503,12 +503,14 @@ export const createMcpToolsFromManifest = (input: { · 502 unmodified lines | ||
| 503 | 503 | }, |
| 504 | 504 | metadata: { |
| 505 | 505 | sourceKey, |
| 506 | − ...(entry.inputSchema !== undefined | |
| 507 | − ? { inputSchema: entry.inputSchema } | |
| 508 | − : {}), | |
| 509 | − ...(entry.outputSchema !== undefined | |
| 510 | − ? { outputSchema: entry.outputSchema } | |
| 511 | − : {}), | |
| 506 | + contract: { | |
| 507 | + ...(entry.inputSchema !== undefined | |
| 508 | + ? { inputSchema: entry.inputSchema } | |
| 509 | + : {}), | |
| 510 | + ...(entry.outputSchema !== undefined | |
| 511 | + ? { outputSchema: entry.outputSchema } | |
| 512 | + : {}), | |
| 513 | + }, | |
| 512 | 514 | }, |
| 513 | 515 | }), |
| 514 | 516 | ] as const; |
| @@ -111,7 +111,7 @@ describe("openapi-extraction real specs", () => { · 110 unmodified lines | ||
| 111 | 111 | |
| 112 | 112 | for (const expectedTool of expectedTools) { |
| 113 | 113 | const resolved = resolveToolDefinition(tools[expectedTool.expectedPath]!); |
| 114 | − const inputSchema = parseInputSchema(resolved.metadata?.inputSchema); | |
| 114 | + const inputSchema = parseInputSchema(resolved.metadata?.contract?.inputSchema); | |
| 115 | 115 | |
| 116 | 116 | expect(inputSchema?.type).toBe("object"); |
| 117 | 117 | expect(Object.keys(inputSchema?.properties ?? {})).toEqual( |
| @@ -354,7 +354,7 @@ describe("openapi-tools", () => { · 353 unmodified lines | ||
| 354 | 354 | expect(resolvedGet.metadata?.sourceKey).toBe("api.demo"); |
| 355 | 355 | expect(resolvedGet.metadata?.providerKind).toBe("openapi"); |
| 356 | 356 | expect(resolvedGet.metadata?.providerData).toMatchObject({ group: "repos" }); |
| 357 | − expect(resolvedGet.metadata?.inputSchema).toBeDefined(); | |
| 357 | + expect(resolvedGet.metadata?.contract?.inputSchema).toBeDefined(); | |
| 358 | 358 | }), |
| 359 | 359 | ); |
| 360 | 360 | |
| @@ -541,20 +541,22 @@ export const createOpenApiToolFromDefinition = ( · 540 unmodified lines | ||
| 541 | 541 | }, |
| 542 | 542 | metadata: { |
| 543 | 543 | sourceKey: input.sourceKey, |
| 544 | − inputTypePreview: presentation.inputTypePreview, | |
| 545 | − outputTypePreview: presentation.outputTypePreview, | |
| 546 | − ...((presentation.inputSchema ?? input.definition.typing?.inputSchema) !== undefined | |
| 547 | − ? { inputSchema: presentation.inputSchema ?? input.definition.typing?.inputSchema } | |
| 548 | − : {}), | |
| 549 | − ...((presentation.outputSchema ?? input.definition.typing?.outputSchema) !== undefined | |
| 550 | − ? { outputSchema: presentation.outputSchema ?? input.definition.typing?.outputSchema } | |
| 551 | − : {}), | |
| 552 | − ...(presentation.exampleInput !== undefined | |
| 553 | − ? { exampleInput: presentation.exampleInput } | |
| 554 | − : {}), | |
| 555 | − ...(presentation.exampleOutput !== undefined | |
| 556 | − ? { exampleOutput: presentation.exampleOutput } | |
| 557 | − : {}), | |
| 544 | + contract: { | |
| 545 | + inputTypePreview: presentation.inputTypePreview, | |
| 546 | + outputTypePreview: presentation.outputTypePreview, | |
| 547 | + ...((presentation.inputSchema ?? input.definition.typing?.inputSchema) !== undefined | |
| 548 | + ? { inputSchema: presentation.inputSchema ?? input.definition.typing?.inputSchema } | |
| 549 | + : {}), | |
| 550 | + ...((presentation.outputSchema ?? input.definition.typing?.outputSchema) !== undefined | |
| 551 | + ? { outputSchema: presentation.outputSchema ?? input.definition.typing?.outputSchema } | |
| 552 | + : {}), | |
| 553 | + ...(presentation.exampleInput !== undefined | |
| 554 | + ? { exampleInput: presentation.exampleInput } | |
| 555 | + : {}), | |
| 556 | + ...(presentation.exampleOutput !== undefined | |
| 557 | + ? { exampleOutput: presentation.exampleOutput } | |
| 558 | + : {}), | |
| 559 | + }, | |
| 558 | 560 | providerKind: "openapi", |
| 559 | 561 | providerData: presentation.providerData, |
| 560 | 562 | }, |