History

move ir into kernel

Rhys Sullivan committed 8 months ago

Commit e2dcd001 parent b2b2e8023 files · +94 −32Browse files

Showing the changes to packages/kernel (8 of 23 files). Show all

Unified · Split

8 files
  1. packages/kernel/ir/README.md +34 −0
  2. packages/kernel/ir/package.json +27 −0
  3. packages/kernel/ir/src/catalog.test.ts +748 −0
  4. packages/kernel/ir/src/catalog.ts +2018 −0
  5. packages/kernel/ir/src/ids.ts +58 −0
  6. packages/kernel/ir/src/index.ts +3 −0
  7. packages/kernel/ir/src/model.ts +971 −0
  8. packages/kernel/ir/tsconfig.json +15 −0
packages/kernel/ir/README.md+34−0
@@ -0,0 +1,34 @@
1# IR v1
2
3`@executor/ir` is the greenfield semantic center for imported sources.
4
5## Boundary
6
7- `CatalogSnapshotV1` is the canonical persisted import artifact.
8- `CatalogV1` is the normalized semantic graph.
9- `CatalogFragmentV1` is the adapter/importer output contract.
10- `ToolDescriptor`, search docs, inspect views, and `InvocationPlan` are projections.
11
12## Direction
13
14Importers should move toward:
15
16`source -> CatalogFragmentV1 -> merge -> CatalogV1 -> projectors -> InvocationPlan`
17
18The existing runtime-specific compiled payload blobs are no longer the target semantic center.
19
20## Package Surface
21
22Use either:
23
24- `@executor/ir`
25- `@executor/control-plane/ir` as a compatibility shim
26- `import { ir } from "@executor/control-plane"`
27
28## Guarantees
29
30- provenance is required on entities
31- synthetic entities are marked explicitly
32- lossy normalization is represented by diagnostics
33- inheritance is preserved in scopes and resolved at projection/planning time
34- invocation plans remain connection-bound runtime artifacts, not canonical IR
packages/kernel/ir/package.json+27−0
@@ -0,0 +1,27 @@
1{
2 "name": "@executor/ir",
3 "private": true,
4 "type": "module",
5 "exports": {
6 ".": "./src/index.ts",
7 "./catalog": "./src/catalog.ts",
8 "./ids": "./src/ids.ts",
9 "./model": "./src/model.ts"
10 },
11 "scripts": {
12 "typecheck": "bunx tsc --noEmit -p tsconfig.json",
13 "test": "vitest run",
14 "test:watch": "vitest"
15 },
16 "dependencies": {
17 "@executor/codemode-core": "workspace:*",
18 "effect": "catalog:"
19 },
20 "devDependencies": {
21 "@effect/vitest": "catalog:",
22 "@types/node": "catalog:",
23 "bun-types": "catalog:",
24 "vitest": "catalog:"
25 },
26 "version": "0.0.0"
27}
packages/kernel/ir/src/catalog.test.ts+748−0
@@ -0,0 +1,748 @@
1import { describe, expect, it } from "@effect/vitest";
2
3import {
4 buildInvocationPlan,
5 createCatalogSnapshotV1,
6 createEmptyCatalogV1,
7 decodeCatalogSnapshotV1,
8 mergeCatalogFragments,
9 projectCatalogForAgentSdk,
10 projectSymbolExpandedView,
11 projectSymbolShallowView,
12 validateCatalogInvariants,
13} from "./catalog";
14import {
15 CapabilityIdSchema,
16 DiagnosticIdSchema,
17 DocumentIdSchema,
18 ExecutableIdSchema,
19 ExampleSymbolIdSchema,
20 HeaderSymbolIdSchema,
21 ParameterSymbolIdSchema,
22 RequestBodySymbolIdSchema,
23 ResourceIdSchema,
24 ResponseSetIdSchema,
25 ResponseSymbolIdSchema,
26 ScopeIdSchema,
27 SecuritySchemeSymbolIdSchema,
28 ShapeSymbolIdSchema,
29} from "./ids";
30import type { CatalogV1, CatalogFragmentV1, ProvenanceRef } from "./model";
31
32const put = <K extends string, V>(record: Record<K, V>, key: K, value: V) => {
33 record[key] = value;
34};
35
36const expectNullableShapeContains = (
37 catalog: CatalogV1,
38 nullableShapeId: ReturnType<typeof ShapeSymbolIdSchema.make>,
39 expectedInnerShapeId: ReturnType<typeof ShapeSymbolIdSchema.make>,
40) => {
41 const shape = catalog.symbols[nullableShapeId];
42 if (shape?.kind !== "shape" || shape.node.type !== "anyOf") {
43 throw new Error("Expected nullable anyOf shape");
44 }
45
46 expect(shape.node.items).toContain(expectedInnerShapeId);
47};
48
49const docId = DocumentIdSchema.make("doc_primary");
50const baseProvenance = (pointer: string): ProvenanceRef[] => [{
51 relation: "declared",
52 documentId: docId,
53 pointer,
54}];
55
56const createHttpCatalog = (input: {
57 collideIds?: boolean;
58 method?: string;
59} = {}): CatalogV1 => {
60 const catalog = createEmptyCatalogV1();
61
62 const resourceId = ResourceIdSchema.make("res_primary");
63 const scopeId = ScopeIdSchema.make("scope_service");
64 const authSchemeId = SecuritySchemeSymbolIdSchema.make("sym_security_oauth");
65 const bodyShapeId = ShapeSymbolIdSchema.make("shape_event_body");
66 const resultShapeId = ShapeSymbolIdSchema.make("shape_event_result");
67 const stringShapeId = ShapeSymbolIdSchema.make("shape_string");
68 const idPathParamId = ParameterSymbolIdSchema.make("sym_param_path_event_id");
69 const calendarParamId = ParameterSymbolIdSchema.make("sym_param_path_calendar_id");
70 const queryParamId = ParameterSymbolIdSchema.make(
71 input.collideIds ? "sym_param_query_id" : "sym_param_query_send_updates",
72 );
73 const requestBodyId = RequestBodySymbolIdSchema.make("sym_request_body_event");
74 const responseId = ResponseSymbolIdSchema.make("sym_response_200");
75 const responseSetId = ResponseSetIdSchema.make("response_set_events_update");
76 const executableId = ExecutableIdSchema.make("exec_http_events_update");
77 const capabilityId = CapabilityIdSchema.make("cap_events_update");
78 const exampleId = ExampleSymbolIdSchema.make("sym_example_event_body");
79
80 put(catalog.documents as Record<typeof docId, CatalogV1["documents"][typeof docId]>, docId, {
81 id: docId,
82 kind: "openapi",
83 title: "Calendar API",
84 fetchedAt: "2026-03-14T00:00:00.000Z",
85 rawRef: "memory://calendar/openapi.json",
86 });
87
88 put(catalog.resources as Record<typeof resourceId, CatalogV1["resources"][typeof resourceId]>, resourceId, {
89 id: resourceId,
90 documentId: docId,
91 canonicalUri: "https://example.test/openapi.json",
92 baseUri: "https://example.test/openapi.json",
93 anchors: {},
94 dynamicAnchors: {},
95 synthetic: false,
96 provenance: baseProvenance("#"),
97 });
98
99 put(catalog.scopes as Record<typeof scopeId, CatalogV1["scopes"][typeof scopeId]>, scopeId, {
100 id: scopeId,
101 kind: "service",
102 name: "Calendar",
103 namespace: "google.calendar",
104 docs: {
105 summary: "Calendar service",
106 description: "Manage calendar events.",
107 },
108 synthetic: false,
109 provenance: baseProvenance("#/servers/0"),
110 });
111
112 put(catalog.symbols as Record<typeof authSchemeId, CatalogV1["symbols"][typeof authSchemeId]>, authSchemeId, {
113 id: authSchemeId,
114 kind: "securityScheme",
115 schemeType: "oauth2",
116 docs: {
117 summary: "OAuth 2.0",
118 },
119 oauth: {
120 scopes: {
121 "calendar.events": "Manage calendar events",
122 },
123 },
124 synthetic: false,
125 provenance: baseProvenance("#/components/securitySchemes/oauth2"),
126 });
127
128 put(catalog.symbols as Record<typeof stringShapeId, CatalogV1["symbols"][typeof stringShapeId]>, stringShapeId, {
129 id: stringShapeId,
130 kind: "shape",
131 resourceId,
132 title: "String",
133 node: {
134 type: "scalar",
135 scalar: "string",
136 },
137 synthetic: false,
138 provenance: baseProvenance("#/components/schemas/String"),
139 });
140
141 put(catalog.symbols as Record<typeof bodyShapeId, CatalogV1["symbols"][typeof bodyShapeId]>, bodyShapeId, {
142 id: bodyShapeId,
143 kind: "shape",
144 resourceId,
145 title: "EventUpdateBody",
146 node: {
147 type: "object",
148 fields: {
149 summary: {
150 shapeId: stringShapeId,
151 },
152 },
153 additionalProperties: false,
154 },
155 synthetic: false,
156 provenance: baseProvenance("#/components/schemas/EventUpdateBody"),
157 });
158
159 put(catalog.symbols as Record<typeof resultShapeId, CatalogV1["symbols"][typeof resultShapeId]>, resultShapeId, {
160 id: resultShapeId,
161 kind: "shape",
162 resourceId,
163 title: "Event",
164 node: {
165 type: "object",
166 fields: {
167 id: {
168 shapeId: stringShapeId,
169 },
170 summary: {
171 shapeId: stringShapeId,
172 },
173 },
174 required: ["id"],
175 additionalProperties: false,
176 },
177 synthetic: false,
178 provenance: baseProvenance("#/components/schemas/Event"),
179 });
180
181 put(catalog.symbols as Record<typeof exampleId, CatalogV1["symbols"][typeof exampleId]>, exampleId, {
182 id: exampleId,
183 kind: "example",
184 exampleKind: "value",
185 name: "Event body example",
186 value: {
187 summary: "Team sync",
188 },
189 synthetic: false,
190 provenance: baseProvenance("#/components/examples/EventBody"),
191 });
192
193 put(catalog.symbols as Record<typeof calendarParamId, CatalogV1["symbols"][typeof calendarParamId]>, calendarParamId, {
194 id: calendarParamId,
195 kind: "parameter",
196 name: input.collideIds ? "id" : "calendarId",
197 location: "path",
198 required: true,
199 docs: {
200 summary: "Calendar identifier",
201 description: "The target calendar.",
202 },
203 schemaShapeId: stringShapeId,
204 synthetic: false,
205 provenance: baseProvenance("#/paths/~1events~1{calendarId}/parameters/0"),
206 });
207
208 put(catalog.symbols as Record<typeof idPathParamId, CatalogV1["symbols"][typeof idPathParamId]>, idPathParamId, {
209 id: idPathParamId,
210 kind: "parameter",
211 name: "eventId",
212 location: "path",
213 required: true,
214 docs: {
215 summary: "Event identifier",
216 },
217 schemaShapeId: stringShapeId,
218 synthetic: false,
219 provenance: baseProvenance("#/paths/~1events~1{eventId}/parameters/0"),
220 });
221
222 put(catalog.symbols as Record<typeof queryParamId, CatalogV1["symbols"][typeof queryParamId]>, queryParamId, {
223 id: queryParamId,
224 kind: "parameter",
225 name: input.collideIds ? "id" : "sendUpdates",
226 location: "query",
227 docs: {
228 summary: "Notification mode",
229 },
230 schemaShapeId: stringShapeId,
231 synthetic: false,
232 provenance: baseProvenance("#/paths/~1events/parameters/0"),
233 });
234
235 put(catalog.symbols as Record<typeof requestBodyId, CatalogV1["symbols"][typeof requestBodyId]>, requestBodyId, {
236 id: requestBodyId,
237 kind: "requestBody",
238 required: true,
239 docs: {
240 summary: "Event payload",
241 },
242 contents: [
243 {
244 mediaType: "application/json",
245 shapeId: bodyShapeId,
246 exampleIds: [exampleId],
247 },
248 ],
249 synthetic: false,
250 provenance: baseProvenance("#/paths/~1events/requestBody"),
251 });
252
253 put(catalog.symbols as Record<typeof responseId, CatalogV1["symbols"][typeof responseId]>, responseId, {
254 id: responseId,
255 kind: "response",
256 docs: {
257 summary: "Updated event",
258 },
259 contents: [
260 {
261 mediaType: "application/json",
262 shapeId: resultShapeId,
263 },
264 ],
265 synthetic: false,
266 provenance: baseProvenance("#/paths/~1events/responses/200"),
267 });
268
269 put(catalog.responseSets as Record<typeof responseSetId, CatalogV1["responseSets"][typeof responseSetId]>, responseSetId, {
270 id: responseSetId,
271 variants: [
272 {
273 match: {
274 kind: "exact",
275 status: 200,
276 },
277 responseId,
278 traits: ["success"],
279 },
280 ],
281 synthetic: false,
282 provenance: baseProvenance("#/paths/~1events/responses"),
283 });
284
285 put(catalog.executables as Record<typeof executableId, CatalogV1["executables"][typeof executableId]>, executableId, {
286 id: executableId,
287 protocol: "http",
288 capabilityId,
289 scopeId,
290 method: input.method ?? "PATCH",
291 pathTemplate: "/calendars/{calendarId}/events/{eventId}",
292 pathParameterIds: [calendarParamId, idPathParamId],
293 queryParameterIds: [queryParamId],
294 requestBodyId,
295 responseSetId,
296 synthetic: false,
297 provenance: baseProvenance("#/paths/~1events/patch"),
298 });
299
300 put(catalog.capabilities as Record<typeof capabilityId, CatalogV1["capabilities"][typeof capabilityId]>, capabilityId, {
301 id: capabilityId,
302 serviceScopeId: scopeId,
303 surface: {
304 toolPath: ["google", "calendar", "events", "update"],
305 title: "Update event",
306 summary: "Update a calendar event.",
307 description: "Updates an existing event by ID.",
308 tags: ["calendar", "events"],
309 },
310 semantics: {
311 effect: "write",
312 safe: false,
313 idempotent: false,
314 destructive: false,
315 },
316 auth: {
317 kind: "scheme",
318 schemeId: authSchemeId,
319 scopes: ["calendar.events"],
320 },
321 interaction: {
322 approval: {
323 mayRequire: true,
324 reasons: ["write"],
325 },
326 elicitation: {
327 mayRequest: false,
328 },
329 resume: {
330 supported: true,
331 },
332 },
333 executableIds: [executableId],
334 preferredExecutableId: executableId,
335 synthetic: false,
336 provenance: baseProvenance("#/paths/~1events/patch"),
337 });
338
339 return catalog;
340};
341
342const createGraphqlCatalog = (): CatalogV1 => {
343 const catalog = createEmptyCatalogV1();
344
345 const resourceId = ResourceIdSchema.make("res_graphql");
346 const scopeId = ScopeIdSchema.make("scope_graphql_service");
347 const argShapeId = ShapeSymbolIdSchema.make("shape_graphql_args");
348 const inputShapeId = ShapeSymbolIdSchema.make("shape_graphql_input");
349 const selectShapeId = ShapeSymbolIdSchema.make("shape_graphql_select");
350 const resultShapeId = ShapeSymbolIdSchema.make("shape_graphql_result");
351 const stringShapeId = ShapeSymbolIdSchema.make("shape_graphql_string");
352 const responseId = ResponseSymbolIdSchema.make("sym_graphql_response");
353 const responseSetId = ResponseSetIdSchema.make("response_set_graphql");
354 const executableId = ExecutableIdSchema.make("exec_graphql_update");
355 const capabilityId = CapabilityIdSchema.make("cap_graphql_update");
356
357 put(catalog.documents as Record<typeof docId, CatalogV1["documents"][typeof docId]>, docId, {
358 id: docId,
359 kind: "graphql-schema",
360 title: "GraphQL schema",
361 fetchedAt: "2026-03-14T00:00:00.000Z",
362 rawRef: "memory://graphql/schema.json",
363 });
364
365 put(catalog.resources as Record<typeof resourceId, CatalogV1["resources"][typeof resourceId]>, resourceId, {
366 id: resourceId,
367 documentId: docId,
368 canonicalUri: "https://example.test/graphql",
369 baseUri: "https://example.test/graphql",
370 anchors: {},
371 dynamicAnchors: {},
372 synthetic: false,
373 provenance: baseProvenance("#"),
374 });
375
376 put(catalog.scopes as Record<typeof scopeId, CatalogV1["scopes"][typeof scopeId]>, scopeId, {
377 id: scopeId,
378 kind: "service",
379 name: "GraphQL",
380 synthetic: false,
381 provenance: baseProvenance("#/schema"),
382 });
383
384 put(catalog.symbols as Record<typeof stringShapeId, CatalogV1["symbols"][typeof stringShapeId]>, stringShapeId, {
385 id: stringShapeId,
386 kind: "shape",
387 resourceId,
388 title: "String",
389 node: {
390 type: "scalar",
391 scalar: "string",
392 },
393 synthetic: false,
394 provenance: baseProvenance("#/$defs/String"),
395 });
396
397 put(catalog.symbols as Record<typeof inputShapeId, CatalogV1["symbols"][typeof inputShapeId]>, inputShapeId, {
398 id: inputShapeId,
399 kind: "shape",
400 resourceId,
401 title: "UserInput",
402 node: {
403 type: "object",
404 fields: {
405 name: {
406 shapeId: stringShapeId,
407 },
408 },
409 additionalProperties: false,
410 },
411 synthetic: false,
412 provenance: baseProvenance("#/$defs/UserInput"),
413 });
414
415 put(catalog.symbols as Record<typeof argShapeId, CatalogV1["symbols"][typeof argShapeId]>, argShapeId, {
416 id: argShapeId,
417 kind: "shape",
418 resourceId,
419 title: "UpdateUserArgs",
420 node: {
421 type: "object",
422 fields: {
423 id: {
424 shapeId: stringShapeId,
425 },
426 input: {
427 shapeId: inputShapeId,
428 },
429 },
430 required: ["id", "input"],
431 additionalProperties: false,
432 },
433 synthetic: false,
434 provenance: baseProvenance("#/$defs/UpdateUserArgs"),
435 });
436
437 put(catalog.symbols as Record<typeof selectShapeId, CatalogV1["symbols"][typeof selectShapeId]>, selectShapeId, {
438 id: selectShapeId,
439 kind: "shape",
440 resourceId,
441 title: "UserSelection",
442 node: {
443 type: "object",
444 fields: {
445 id: {
446 shapeId: stringShapeId,
447 },
448 name: {
449 shapeId: stringShapeId,
450 },
451 },
452 additionalProperties: false,
453 },
454 synthetic: false,
455 provenance: baseProvenance("#/$defs/UserSelection"),
456 });
457
458 put(catalog.symbols as Record<typeof resultShapeId, CatalogV1["symbols"][typeof resultShapeId]>, resultShapeId, {
459 id: resultShapeId,
460 kind: "shape",
461 resourceId,
462 title: "User",
463 node: {
464 type: "object",
465 fields: {
466 id: {
467 shapeId: stringShapeId,
468 },
469 name: {
470 shapeId: stringShapeId,
471 },
472 },
473 required: ["id"],
474 additionalProperties: false,
475 },
476 synthetic: false,
477 provenance: baseProvenance("#/$defs/User"),
478 });
479
480 put(catalog.symbols as Record<typeof responseId, CatalogV1["symbols"][typeof responseId]>, responseId, {
481 id: responseId,
482 kind: "response",
483 contents: [
484 {
485 mediaType: "application/json",
486 shapeId: resultShapeId,
487 },
488 ],
489 synthetic: false,
490 provenance: baseProvenance("#/responses/default"),
491 });
492
493 put(catalog.responseSets as Record<typeof responseSetId, CatalogV1["responseSets"][typeof responseSetId]>, responseSetId, {
494 id: responseSetId,
495 variants: [
496 {
497 match: {
498 kind: "default",
499 },
500 responseId,
501 traits: ["success"],
502 },
503 ],
504 synthetic: false,
505 provenance: baseProvenance("#/responses"),
506 });
507
508 put(catalog.executables as Record<typeof executableId, CatalogV1["executables"][typeof executableId]>, executableId, {
509 id: executableId,
510 protocol: "graphql",
511 capabilityId,
512 scopeId,
513 toolKind: "field",
514 operationType: "mutation",
515 rootField: "updateUser",
516 argumentShapeId: argShapeId,
517 resultShapeId,
518 selectionShapeId: selectShapeId,
519 selectionMode: "caller",
520 responseSetId,
521 synthetic: false,
522 provenance: baseProvenance("#/mutations/updateUser"),
523 });
524
525 put(catalog.capabilities as Record<typeof capabilityId, CatalogV1["capabilities"][typeof capabilityId]>, capabilityId, {
526 id: capabilityId,
527 serviceScopeId: scopeId,
528 surface: {
529 toolPath: ["graphql", "users", "update"],
530 title: "Update user",
531 summary: "Update a user.",
532 },
533 semantics: {
534 effect: "write",
535 },
536 auth: {
537 kind: "none",
538 },
539 interaction: {
540 approval: {
541 mayRequire: true,
542 reasons: ["write"],
543 },
544 elicitation: {
545 mayRequest: false,
546 },
547 resume: {
548 supported: false,
549 },
550 },
551 executableIds: [executableId],
552 preferredExecutableId: executableId,
553 synthetic: false,
554 provenance: baseProvenance("#/mutations/updateUser"),
555 });
556
557 return catalog;
558};
559
560describe("IR catalog", () => {
561 it("decodes a snapshot through the canonical schema", () => {
562 const catalog = createHttpCatalog();
563 const snapshot = createCatalogSnapshotV1({
564 import: {
565 sourceKind: "openapi",
566 adapterKey: "openapi",
567 importerVersion: "1.0.0",
568 importedAt: "2026-03-14T00:00:00.000Z",
569 sourceConfigHash: "hash_source",
570 },
571 catalog,
572 });
573
574 const decoded = decodeCatalogSnapshotV1(snapshot);
575 expect(decoded.version).toBe("ir.v1.snapshot");
576 expect(decoded.catalog.capabilities[CapabilityIdSchema.make("cap_events_update")]?.surface.title).toBe(
577 "Update event",
578 );
579 });
580
581 it("merges fragments and emits diagnostics on conflicting IDs", () => {
582 const fragmentA: CatalogFragmentV1 = {
583 version: "ir.v1.fragment",
584 documents: {
585 [docId]: {
586 id: docId,
587 kind: "openapi",
588 title: "A",
589 fetchedAt: "2026-03-14T00:00:00.000Z",
590 rawRef: "memory://a",
591 },
592 },
593 };
594 const fragmentB: CatalogFragmentV1 = {
595 version: "ir.v1.fragment",
596 documents: {
597 [docId]: {
598 id: docId,
599 kind: "openapi",
600 title: "B",
601 fetchedAt: "2026-03-14T00:00:00.000Z",
602 rawRef: "memory://b",
603 },
604 },
605 };
606
607 const merged = mergeCatalogFragments([fragmentA, fragmentB]);
608 expect(merged.documents[docId]?.title).toBe("A");
609 expect(Object.values(merged.diagnostics).some((diagnostic) => diagnostic.code === "merge_conflict_preserved_first")).toBe(true);
610 });
611
612 it("reports invariant violations for unresolved shapes without diagnostics", () => {
613 const catalog = createEmptyCatalogV1();
614 const shapeId = ShapeSymbolIdSchema.make("shape_unresolved");
615
616 put(catalog.documents as Record<typeof docId, CatalogV1["documents"][typeof docId]>, docId, {
617 id: docId,
618 kind: "custom",
619 fetchedAt: "2026-03-14T00:00:00.000Z",
620 rawRef: "memory://custom",
621 });
622
623 put(catalog.symbols as Record<typeof shapeId, CatalogV1["symbols"][typeof shapeId]>, shapeId, {
624 id: shapeId,
625 kind: "shape",
626 title: "Unresolved",
627 node: {
628 type: "unknown",
629 reason: "unresolved reference #/components/schemas/Missing",
630 },
631 synthetic: false,
632 provenance: baseProvenance("#/components/schemas/Missing"),
633 });
634
635 const violations = validateCatalogInvariants(catalog);
636 expect(violations.map((violation) => violation.code)).toContain("missing_resource_context");
637 expect(violations.map((violation) => violation.code)).toContain("missing_unresolved_ref_diagnostic");
638 });
639
640 it("projects HTTP capabilities into ergonomic call and result shapes", () => {
641 const projected = projectCatalogForAgentSdk({
642 catalog: createHttpCatalog(),
643 });
644
645 const descriptor = projected.toolDescriptors[CapabilityIdSchema.make("cap_events_update")];
646 expect(descriptor.toolPath).toEqual(["google", "calendar", "events", "update"]);
647
648 const callShape = projected.catalog.symbols[descriptor.callShapeId];
649 expect(callShape?.kind).toBe("shape");
650 if (callShape?.kind !== "shape" || callShape.node.type !== "object") {
651 throw new Error("Expected projected call shape object");
652 }
653
654 expect(Object.keys(callShape.node.fields)).toEqual([
655 "calendarId",
656 "eventId",
657 "sendUpdates",
658 "body",
659 ]);
660 expect(callShape.node.required).toEqual(["calendarId", "eventId", "body"]);
661
662 const resultShape = descriptor.resultShapeId
663 ? projected.catalog.symbols[descriptor.resultShapeId]
664 : undefined;
665 if (resultShape?.kind !== "shape" || resultShape.node.type !== "object") {
666 throw new Error("Expected projected HTTP result envelope");
667 }
668 expect(Object.keys(resultShape.node.fields)).toEqual(["data", "error", "headers", "status"]);
669 expectNullableShapeContains(
670 projected.catalog,
671 resultShape.node.fields.data.shapeId,
672 ShapeSymbolIdSchema.make("shape_event_result"),
673 );
674
675 const searchDoc = projected.searchDocs[CapabilityIdSchema.make("cap_events_update")];
676 expect(searchDoc.authHints).toContain("oauth2");
677 expect(searchDoc.tags).toEqual(["calendar", "events"]);
678 });
679
680 it("groups colliding HTTP parameter names into path/query containers", () => {
681 const projected = projectCatalogForAgentSdk({
682 catalog: createHttpCatalog({ collideIds: true }),
683 });
684
685 const descriptor = projected.toolDescriptors[CapabilityIdSchema.make("cap_events_update")];
686 const callShape = projected.catalog.symbols[descriptor.callShapeId];
687 if (callShape?.kind !== "shape" || callShape.node.type !== "object") {
688 throw new Error("Expected projected call shape object");
689 }
690
691 expect(Object.keys(callShape.node.fields)).toContain("path");
692 expect(Object.keys(callShape.node.fields)).toContain("query");
693 expect(Object.keys(callShape.node.fields)).not.toContain("id");
694 expect(Object.values(projected.catalog.diagnostics).some((diagnostic) => diagnostic.code === "projection_collision_grouped_fields")).toBe(true);
695 });
696
697 it("keeps GraphQL selection explicit for caller-driven outputs", () => {
698 const projected = projectCatalogForAgentSdk({
699 catalog: createGraphqlCatalog(),
700 });
701
702 const descriptor = projected.toolDescriptors[CapabilityIdSchema.make("cap_graphql_update")];
703 const callShape = projected.catalog.symbols[descriptor.callShapeId];
704 if (callShape?.kind !== "shape" || callShape.node.type !== "object") {
705 throw new Error("Expected projected GraphQL call shape object");
706 }
707
708 expect(Object.keys(callShape.node.fields)).toEqual(["id", "input", "select"]);
709 const resultShape = descriptor.resultShapeId
710 ? projected.catalog.symbols[descriptor.resultShapeId]
711 : undefined;
712 if (resultShape?.kind !== "shape" || resultShape.node.type !== "object") {
713 throw new Error("Expected projected GraphQL result envelope");
714 }
715 expect(Object.keys(resultShape.node.fields)).toEqual(["data", "error", "headers", "status"]);
716 expectNullableShapeContains(
717 projected.catalog,
718 resultShape.node.fields.data.shapeId,
719 ShapeSymbolIdSchema.make("shape_graphql_result"),
720 );
721 });
722
723 it("builds inspect views and invocation plans from the projected graph", () => {
724 const projected = projectCatalogForAgentSdk({
725 catalog: createHttpCatalog(),
726 });
727 const capabilityId = CapabilityIdSchema.make("cap_events_update");
728 const descriptor = projected.toolDescriptors[capabilityId];
729 const shallow = projectSymbolShallowView(projected.catalog, descriptor.callShapeId);
730 const expanded = projectSymbolExpandedView(projected.catalog, descriptor.callShapeId);
731 const plan = buildInvocationPlan({
732 catalog: projected.catalog,
733 capabilityId,
734 connectionBindingId: "binding_primary",
735 resolvedEndpoint: "https://example.test",
736 requestPlan: {
737 method: "PATCH",
738 path: "/calendars/primary/events/abc",
739 },
740 });
741
742 expect(shallow.edges.some((edge) => edge.label === "field:body")).toBe(true);
743 expect(expanded.symbol.id).toBe(descriptor.callShapeId);
744 expect(plan.connectionBindingId).toBe("binding_primary");
745 expect(plan.resolvedScopeChain).toEqual([ScopeIdSchema.make("scope_service")]);
746 expect(plan.resolvedInteraction?.requiresApproval).toBe(true);
747 });
748});
packages/kernel/ir/src/catalog.ts+2018−0
@@ -0,0 +1,2018 @@
1import { sha256Hex } from "@executor/codemode-core";
2import * as ParseResult from "effect/ParseResult";
3import { Schema } from "effect";
4
5import {
6 CapabilityIdSchema,
7 type CapabilityId,
8 DiagnosticIdSchema,
9 type DiagnosticId,
10 DocumentIdSchema,
11 type DocumentId,
12 ExecutableIdSchema,
13 type ExecutableId,
14 ExampleSymbolIdSchema,
15 type ExampleSymbolId,
16 HeaderSymbolIdSchema,
17 type HeaderSymbolId,
18 ParameterSymbolIdSchema,
19 type ParameterSymbolId,
20 type RequestBodySymbolId,
21 ResourceIdSchema,
22 type ResourceId,
23 type ResponseSymbolId,
24 type ResponseSetId,
25 ScopeIdSchema,
26 type ScopeId,
27 type ShapeSymbolId,
28 ShapeSymbolIdSchema,
29 type SymbolId,
30} from "./ids";
31import {
32 CatalogFragmentV1Schema,
33 type CatalogFragmentV1,
34 CatalogSnapshotV1Schema,
35 CatalogV1Schema,
36 type CatalogV1,
37 type Capability,
38 type ContentSpec,
39 type DocumentationBlock,
40 type Executable,
41 type GraphQLExecutable,
42 type HttpExecutable,
43 type ImportDiagnostic,
44 type ImportMetadata,
45 type InteractionSpec,
46 type McpExecutable,
47 type ParameterSymbol,
48 type HeaderSymbol,
49 type ResponseSet,
50 type ResponseSymbol,
51 type Scope,
52 type ScopeDefaults,
53 type ShapeSymbol,
54 type StatusMatch,
55 type Symbol as IrSymbol,
56} from "./model";
57
58export interface ToolDescriptor {
59 toolPath: string[];
60 capabilityId: CapabilityId;
61 title?: string;
62 summary?: string;
63 effect: Capability["semantics"]["effect"];
64 interaction: {
65 mayRequireApproval: boolean;
66 mayElicit: boolean;
67 };
68 callShapeId: ShapeSymbolId;
69 resultShapeId?: ShapeSymbolId;
70 responseSetId: ResponseSetId;
71 diagnosticCounts: {
72 warning: number;
73 error: number;
74 };
75}
76
77export interface ToolSearchDoc {
78 capabilityId: CapabilityId;
79 toolPath: string[];
80 title?: string;
81 summary?: string;
82 tags?: string[];
83 protocolHints: string[];
84 authHints: string[];
85 effect: Capability["semantics"]["effect"];
86}
87
88export interface CapabilitySummaryView {
89 capabilityId: CapabilityId;
90 toolPath: string[];
91 summary?: string;
92 executableIds: ExecutableId[];
93 auth: Capability["auth"];
94 interaction: InteractionSpec;
95 callShapeId: ShapeSymbolId;
96 resultShapeId?: ShapeSymbolId;
97 responseSetId: ResponseSetId;
98 diagnosticIds?: DiagnosticId[];
99}
100
101export interface SymbolShallowView {
102 symbolId: SymbolId;
103 kind: IrSymbol["kind"];
104 title?: string;
105 summary?: string;
106 edges: Array<{ label: string; targetId: SymbolId }>;
107}
108
109export interface SymbolExpandedView {
110 symbolId: SymbolId;
111 symbol: IrSymbol;
112 diagnostics?: ImportDiagnostic[];
113}
114
115export interface InvocationPlan {
116 capabilityId: CapabilityId;
117 executableId: ExecutableId;
118 connectionBindingId: string;
119 resolvedScopeChain: ScopeId[];
120 resolvedEndpoint?: string;
121 resolvedAuth?: unknown;
122 resolvedInteraction?: {
123 requiresApproval: boolean;
124 mayPauseForElicitation: boolean;
125 resumable: boolean;
126 };
127 requestPlan: unknown;
128 provenance: CatalogV1["capabilities"][CapabilityId]["provenance"];
129}
130
131export interface ProjectedCatalog {
132 catalog: CatalogV1;
133 toolDescriptors: Record<CapabilityId, ToolDescriptor>;
134 searchDocs: Record<CapabilityId, ToolSearchDoc>;
135 capabilityViews: Record<CapabilityId, CapabilitySummaryView>;
136}
137
138export interface CatalogInvariantViolation {
139 code:
140 | "missing_symbol_provenance"
141 | "missing_entity_provenance"
142 | "missing_document"
143 | "missing_provenance_document"
144 | "missing_resource_context"
145 | "missing_reference_target"
146 | "missing_unresolved_ref_diagnostic"
147 | "missing_executable"
148 | "missing_response_set"
149 | "missing_scope"
150 | "missing_service_scope"
151 | "invalid_preferred_executable"
152 | "missing_projection_synthetic_marker";
153 message: string;
154 entityId?: string;
155}
156
157export type DecodeCatalogError = Error;
158
159type MutableScopeDefaults = {
160 servers?: ScopeDefaults["servers"];
161 auth?: ScopeDefaults["auth"];
162 parameterIds?: ParameterSymbolId[];
163 headerIds?: HeaderSymbolId[];
164 variables?: Record<string, string>;
165};
166
167type CatalogSnapshotV1 = {
168 version: "ir.v1.snapshot";
169 import: ImportMetadata;
170 catalog: CatalogV1;
171};
172
173const decodeCatalogSync = Schema.decodeUnknownSync(
174 CatalogV1Schema as Schema.Schema<CatalogV1, unknown, never>,
175);
176const decodeCatalogSnapshotSync = Schema.decodeUnknownSync(
177 CatalogSnapshotV1Schema as Schema.Schema<CatalogSnapshotV1, unknown, never>,
178);
179const decodeCatalogFragmentSync = Schema.decodeUnknownSync(
180 CatalogFragmentV1Schema as Schema.Schema<CatalogFragmentV1, unknown, never>,
181);
182
183const stableStringify = (value: unknown): string => {
184 if (value === null || typeof value !== "object") {
185 return JSON.stringify(value);
186 }
187
188 if (Array.isArray(value)) {
189 return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
190 }
191
192 const entries = Object.entries(value as Record<string, unknown>)
193 .sort(([left], [right]) => left.localeCompare(right))
194 .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`);
195 return `{${entries.join(",")}}`;
196};
197
198const stableHash = (value: unknown): string => sha256Hex(stableStringify(value)).slice(0, 16);
199
200const unique = <T>(values: readonly T[]): T[] => [...new Set(values)];
201
202const cloneCatalog = (catalog: CatalogV1): CatalogV1 => ({
203 version: catalog.version,
204 documents: { ...catalog.documents },
205 resources: { ...catalog.resources },
206 scopes: { ...catalog.scopes },
207 symbols: { ...catalog.symbols },
208 capabilities: { ...catalog.capabilities },
209 executables: { ...catalog.executables },
210 responseSets: { ...catalog.responseSets },
211 diagnostics: { ...catalog.diagnostics },
212});
213
214const mutableRecord = <K extends string, V>(value: Readonly<Record<K, V>>): Record<K, V> =>
215 value as unknown as Record<K, V>;
216
217const createStableEntityId = <A extends string>(
218 make: (value: string) => A,
219 prefix: string,
220 value: unknown,
221): A => make(`${prefix}_${stableHash(value)}`);
222
223const emptyCatalog = (): CatalogV1 => ({
224 version: "ir.v1",
225 documents: {},
226 resources: {},
227 scopes: {},
228 symbols: {},
229 capabilities: {},
230 executables: {},
231 responseSets: {},
232 diagnostics: {},
233});
234
235const authHintStrings = (catalog: CatalogV1, auth: Capability["auth"]): string[] => {
236 switch (auth.kind) {
237 case "none":
238 return ["none"];
239 case "scheme": {
240 const scheme = catalog.symbols[auth.schemeId];
241 if (!scheme || scheme.kind !== "securityScheme") {
242 return ["unknown"];
243 }
244 return [
245 scheme.schemeType,
246 ...((auth.scopes ?? []).map((scope: string) => `scope:${scope}`)),
247 ];
248 }
249 case "allOf":
250 case "anyOf":
251 return unique(auth.items.flatMap((item: Capability["auth"]) => authHintStrings(catalog, item)));
252 }
253};
254
255const docsSummary = (docs: DocumentationBlock | undefined): string | undefined =>
256 docs?.summary ?? docs?.description;
257
258const getShape = (catalog: CatalogV1, shapeId: ShapeSymbolId): ShapeSymbol | undefined => {
259 const symbol = catalog.symbols[shapeId];
260 return symbol && symbol.kind === "shape" ? symbol : undefined;
261};
262
263const getParameter = (
264 catalog: CatalogV1,
265 parameterId: ParameterSymbolId,
266): ParameterSymbol | undefined => {
267 const symbol = catalog.symbols[parameterId];
268 return symbol && symbol.kind === "parameter" ? symbol : undefined;
269};
270
271const getResponseSymbol = (
272 catalog: CatalogV1,
273 responseId: ResponseSymbolId,
274): ResponseSymbol | undefined => {
275 const symbol = catalog.symbols[responseId];
276 return symbol && symbol.kind === "response" ? symbol : undefined;
277};
278
279const getHeader = (
280 catalog: CatalogV1,
281 headerId: HeaderSymbolId,
282): HeaderSymbol | undefined => {
283 const symbol = catalog.symbols[headerId];
284 return symbol && symbol.kind === "header" ? symbol : undefined;
285};
286
287const scopeChain = (catalog: CatalogV1, scopeId: ScopeId): Scope[] => {
288 const chain: Scope[] = [];
289 let currentId: ScopeId | undefined = scopeId;
290
291 while (currentId) {
292 const scope: Scope | undefined = catalog.scopes[currentId];
293 if (!scope) {
294 break;
295 }
296 chain.unshift(scope);
297 currentId = scope.parentId;
298 }
299
300 return chain;
301};
302
303const mergeScopeDefaults = (chain: readonly Scope[]): ScopeDefaults => {
304 const merged: MutableScopeDefaults = {};
305
306 for (const scope of chain) {
307 const defaults = scope.defaults;
308 if (!defaults) {
309 continue;
310 }
311
312 if (defaults.servers) {
313 merged.servers = [...defaults.servers];
314 }
315
316 if (defaults.auth) {
317 merged.auth = defaults.auth;
318 }
319
320 if (defaults.parameterIds) {
321 merged.parameterIds = unique([
322 ...(merged.parameterIds ?? []),
323 ...defaults.parameterIds,
324 ]);
325 }
326
327 if (defaults.headerIds) {
328 merged.headerIds = unique([
329 ...(merged.headerIds ?? []),
330 ...defaults.headerIds,
331 ]);
332 }
333
334 if (defaults.variables) {
335 merged.variables = {
336 ...(merged.variables ?? {}),
337 ...defaults.variables,
338 };
339 }
340 }
341
342 return merged as ScopeDefaults;
343};
344
345const isJsonMediaType = (mediaType: string): boolean => {
346 const normalized = mediaType.trim().toLowerCase();
347 return normalized === "application/json"
348 || normalized.endsWith("+json")
349 || normalized === "text/json";
350};
351
352const createDiagnostic = (
353 catalog: CatalogV1,
354 input: Omit<ImportDiagnostic, "id"> & { idSeed: unknown },
355): DiagnosticId => {
356 const diagnosticId = createStableEntityId(
357 DiagnosticIdSchema.make,
358 "diag",
359 input.idSeed,
360 );
361
362 mutableRecord(catalog.diagnostics)[diagnosticId] = {
363 id: diagnosticId,
364 level: input.level,
365 code: input.code,
366 message: input.message,
367 ...(input.relatedSymbolIds ? { relatedSymbolIds: input.relatedSymbolIds } : {}),
368 provenance: input.provenance,
369 };
370
371 return diagnosticId;
372};
373
374const ensureProjectionResource = (
375 catalog: CatalogV1,
376 capability: Capability,
377): ResourceId => {
378 const resourceId = createStableEntityId(
379 ResourceIdSchema.make,
380 "res",
381 {
382 kind: "projection",
383 capabilityId: capability.id,
384 },
385 );
386
387 if (!catalog.resources[resourceId]) {
388 const serviceScope = catalog.scopes[capability.serviceScopeId];
389 const documentId = serviceScope?.provenance[0]?.documentId
390 ?? DocumentIdSchema.make(`doc_projection_${stableHash(capability.id)}`);
391
392 if (!catalog.documents[documentId]) {
393 mutableRecord(catalog.documents)[documentId] = {
394 id: documentId,
395 kind: "custom",
396 title: `Projection resource for ${capability.id}`,
397 fetchedAt: new Date(0).toISOString(),
398 rawRef: `synthetic://projection/${capability.id}`,
399 };
400 }
401
402 mutableRecord(catalog.resources)[resourceId] = {
403 id: resourceId,
404 documentId,
405 canonicalUri: `synthetic://projection/${capability.id}`,
406 baseUri: `synthetic://projection/${capability.id}`,
407 anchors: {},
408 dynamicAnchors: {},
409 synthetic: true,
410 provenance: capability.provenance,
411 };
412
413 createDiagnostic(catalog, {
414 idSeed: {
415 code: "synthetic_resource_context_created",
416 capabilityId: capability.id,
417 },
418 level: "info",
419 code: "synthetic_resource_context_created",
420 message: `Created synthetic projection resource for ${capability.id}`,
421 provenance: capability.provenance,
422 });
423 }
424
425 return resourceId;
426};
427
428const createSyntheticShape = (
429 catalog: CatalogV1,
430 input: {
431 capability: Capability;
432 label: string;
433 node: ShapeSymbol["node"];
434 title?: string;
435 docs?: DocumentationBlock;
436 diagnostic?:
437 | {
438 level: ImportDiagnostic["level"];
439 code: ImportDiagnostic["code"];
440 message: string;
441 relatedSymbolIds?: SymbolId[];
442 }
443 | undefined;
444 },
445): ShapeSymbolId => {
446 const resourceId = ensureProjectionResource(catalog, input.capability);
447 const shapeId = createStableEntityId(
448 ShapeSymbolIdSchema.make,
449 "shape",
450 {
451 capabilityId: input.capability.id,
452 label: input.label,
453 node: input.node,
454 },
455 );
456
457 if (!catalog.symbols[shapeId]) {
458 const diagnostics = input.diagnostic
459 ? [
460 createDiagnostic(catalog, {
461 idSeed: {
462 code: input.diagnostic.code,
463 shapeId,
464 },
465 level: input.diagnostic.level,
466 code: input.diagnostic.code,
467 message: input.diagnostic.message,
468 ...(input.diagnostic.relatedSymbolIds
469 ? { relatedSymbolIds: input.diagnostic.relatedSymbolIds }
470 : {}),
471 provenance: input.capability.provenance,
472 }),
473 ]
474 : undefined;
475
476 mutableRecord(catalog.symbols)[shapeId] = {
477 id: shapeId,
478 kind: "shape",
479 resourceId,
480 ...(input.title ? { title: input.title } : {}),
481 ...(input.docs ? { docs: input.docs } : {}),
482 node: input.node,
483 synthetic: true,
484 provenance: input.capability.provenance,
485 ...(diagnostics ? { diagnosticIds: diagnostics } : {}),
486 };
487 }
488
489 return shapeId;
490};
491
492const contentShapeId = (
493 catalog: CatalogV1,
494 capability: Capability,
495 contents: readonly ContentSpec[] | undefined,
496 label: string,
497): ShapeSymbolId | undefined => {
498 const shapeIds = unique(
499 (contents ?? [])
500 .map((content) => content.shapeId)
501 .filter((shapeId): shapeId is ShapeSymbolId => shapeId !== undefined),
502 );
503
504 if (shapeIds.length === 0) {
505 return undefined;
506 }
507
508 if (shapeIds.length === 1) {
509 return shapeIds[0];
510 }
511
512 return createSyntheticShape(catalog, {
513 capability,
514 label,
515 title: `${capability.surface.title ?? capability.id} content union`,
516 node: {
517 type: "anyOf",
518 items: shapeIds,
519 },
520 diagnostic: {
521 level: "warning",
522 code: "projection_result_shape_synthesized",
523 message: `Synthesized content union for ${capability.id}`,
524 relatedSymbolIds: shapeIds,
525 },
526 });
527};
528
529const parameterShapeId = (
530 catalog: CatalogV1,
531 capability: Capability,
532 parameter: ParameterSymbol,
533): ShapeSymbolId => {
534 if (parameter.schemaShapeId) {
535 return parameter.schemaShapeId;
536 }
537
538 const contentShape = contentShapeId(
539 catalog,
540 capability,
541 parameter.content,
542 `parameter:${parameter.id}`,
543 );
544 if (contentShape) {
545 return contentShape;
546 }
547
548 return createSyntheticShape(catalog, {
549 capability,
550 label: `parameter:${parameter.id}:unknown`,
551 title: parameter.name,
552 docs: parameter.docs,
553 node: {
554 type: "unknown",
555 reason: `Missing shape for parameter ${parameter.id}`,
556 },
557 diagnostic: {
558 level: "warning",
559 code: "projection_call_shape_synthesized",
560 message: `Missing parameter shape for ${parameter.name}; using unknown`,
561 relatedSymbolIds: [parameter.id],
562 },
563 });
564};
565
566const requestBodyShapeId = (
567 catalog: CatalogV1,
568 capability: Capability,
569 requestBodyId: RequestBodySymbolId | undefined,
570): ShapeSymbolId | undefined => {
571 if (!requestBodyId) {
572 return undefined;
573 }
574
575 const requestBody = catalog.symbols[requestBodyId];
576 if (!requestBody || requestBody.kind !== "requestBody") {
577 return undefined;
578 }
579
580 return contentShapeId(
581 catalog,
582 capability,
583 requestBody.contents,
584 `requestBody:${requestBody.id}`,
585 );
586};
587
588const chooseExecutable = (catalog: CatalogV1, capability: Capability): Executable => {
589 const preferredId = capability.preferredExecutableId;
590 if (preferredId) {
591 const preferred = catalog.executables[preferredId];
592 if (preferred) {
593 return preferred;
594 }
595 }
596
597 const executables = capability.executableIds
598 .map((executableId) => catalog.executables[executableId])
599 .filter((executable): executable is Executable => executable !== undefined);
600
601 if (executables.length === 0) {
602 throw new Error(`Capability ${capability.id} has no executables`);
603 }
604
605 return executables[0];
606};
607
608const scoreStatusMatch = (match: StatusMatch): number => {
609 switch (match.kind) {
610 case "exact":
611 return match.status === 200 ? 100 : match.status >= 200 && match.status < 300 ? 80 : 10;
612 case "range":
613 return match.value === "2XX" ? 60 : 5;
614 case "default":
615 return 40;
616 }
617};
618
619const responseContentCandidates = (
620 response: ResponseSymbol,
621): Array<{ mediaType: string; shapeId: ShapeSymbolId }> =>
622 (response.contents ?? [])
623 .flatMap((content) =>
624 content.shapeId ? [{ mediaType: content.mediaType, shapeId: content.shapeId }] : [],
625 );
626
627type RankedResponseVariantEntry = {
628 variant: ResponseSet["variants"][number];
629 response: ResponseSymbol;
630 score: number;
631};
632
633const responseVariantEntries = (
634 catalog: CatalogV1,
635 responseSet: ResponseSet,
636): RankedResponseVariantEntry[] =>
637 [...responseSet.variants]
638 .map((variant) => {
639 const response = getResponseSymbol(catalog, variant.responseId);
640 return response
641 ? {
642 variant,
643 response,
644 score: scoreStatusMatch(variant.match),
645 }
646 : null;
647 })
648 .filter((entry): entry is RankedResponseVariantEntry => entry !== null);
649
650const isSuccessStatusMatch = (match: StatusMatch): boolean => {
651 switch (match.kind) {
652 case "exact":
653 return match.status >= 200 && match.status < 300;
654 case "range":
655 return match.value === "2XX";
656 case "default":
657 return false;
658 }
659};
660
661const contentShapeIdFromResponseEntries = (
662 catalog: CatalogV1,
663 capability: Capability,
664 label: string,
665 title: string,
666 entries: readonly RankedResponseVariantEntry[],
667): ShapeSymbolId | undefined => {
668 const jsonCandidates = entries.flatMap(({ response }) =>
669 responseContentCandidates(response).filter((candidate) => isJsonMediaType(candidate.mediaType)),
670 );
671
672 if (jsonCandidates.length > 0) {
673 return contentShapeId(
674 catalog,
675 capability,
676 jsonCandidates.map((candidate) => ({
677 mediaType: candidate.mediaType,
678 shapeId: candidate.shapeId,
679 })),
680 `${label}:json`,
681 );
682 }
683
684 const fallbackShapeIds = unique(
685 entries.flatMap(({ response }) =>
686 responseContentCandidates(response).map((candidate) => candidate.shapeId),
687 ),
688 );
689
690 if (fallbackShapeIds.length === 0) {
691 return undefined;
692 }
693
694 if (fallbackShapeIds.length === 1) {
695 return fallbackShapeIds[0];
696 }
697
698 return createSyntheticShape(catalog, {
699 capability,
700 label: `${label}:union`,
701 title,
702 node: {
703 type: "anyOf",
704 items: fallbackShapeIds,
705 },
706 diagnostic: {
707 level: "warning",
708 code: "multi_response_union_synthesized",
709 message: `Synthesized response union for ${capability.id}`,
710 relatedSymbolIds: fallbackShapeIds,
711 },
712 });
713};
714
715const projectResultShapeFromResponses = (
716 catalog: CatalogV1,
717 capability: Capability,
718 responseSet: ResponseSet,
719): ShapeSymbolId | undefined =>
720 contentShapeIdFromResponseEntries(
721 catalog,
722 capability,
723 `responseSet:${responseSet.id}`,
724 `${capability.surface.title ?? capability.id} result`,
725 responseVariantEntries(catalog, responseSet).filter(({ variant }) => isSuccessStatusMatch(variant.match)),
726 );
727
728const projectErrorShapeFromResponses = (
729 catalog: CatalogV1,
730 capability: Capability,
731 responseSet: ResponseSet,
732): ShapeSymbolId | undefined =>
733 contentShapeIdFromResponseEntries(
734 catalog,
735 capability,
736 `responseSet:${responseSet.id}:error`,
737 `${capability.surface.title ?? capability.id} error`,
738 responseVariantEntries(catalog, responseSet).filter(({ variant }) => !isSuccessStatusMatch(variant.match)),
739 );
740
741const scalarShape = (
742 catalog: CatalogV1,
743 capability: Capability,
744 input: {
745 label: string;
746 title: string;
747 scalar: "string" | "number" | "integer" | "boolean" | "null" | "bytes";
748 },
749): ShapeSymbolId =>
750 createSyntheticShape(catalog, {
751 capability,
752 label: input.label,
753 title: input.title,
754 node: {
755 type: "scalar",
756 scalar: input.scalar,
757 },
758 });
759
760const constNullShape = (
761 catalog: CatalogV1,
762 capability: Capability,
763 label: string,
764): ShapeSymbolId =>
765 createSyntheticShape(catalog, {
766 capability,
767 label,
768 title: "null",
769 node: {
770 type: "const",
771 value: null,
772 },
773 });
774
775const unknownShape = (
776 catalog: CatalogV1,
777 capability: Capability,
778 input: {
779 label: string;
780 title: string;
781 reason: string;
782 },
783): ShapeSymbolId =>
784 createSyntheticShape(catalog, {
785 capability,
786 label: input.label,
787 title: input.title,
788 node: {
789 type: "unknown",
790 reason: input.reason,
791 },
792 });
793
794const nullableShape = (
795 catalog: CatalogV1,
796 capability: Capability,
797 input: {
798 label: string;
799 title: string;
800 baseShapeId: ShapeSymbolId;
801 },
802): ShapeSymbolId => {
803 const nullShapeId = constNullShape(catalog, capability, `${input.label}:null`);
804 if (input.baseShapeId === nullShapeId) {
805 return nullShapeId;
806 }
807
808 return createSyntheticShape(catalog, {
809 capability,
810 label: `${input.label}:nullable`,
811 title: input.title,
812 node: {
813 type: "anyOf",
814 items: unique([input.baseShapeId, nullShapeId]),
815 },
816 });
817};
818
819const headersShape = (
820 catalog: CatalogV1,
821 capability: Capability,
822 label: string,
823): ShapeSymbolId => {
824 const valueShapeId = scalarShape(catalog, capability, {
825 label: `${label}:value`,
826 title: "Header value",
827 scalar: "string",
828 });
829
830 return createSyntheticShape(catalog, {
831 capability,
832 label,
833 title: "Response headers",
834 node: {
835 type: "object",
836 fields: {},
837 additionalProperties: valueShapeId,
838 },
839 });
840};
841
842const shapeFieldShapeId = (
843 catalog: CatalogV1,
844 shapeId: ShapeSymbolId | undefined,
845 fieldName: string,
846 seen = new Set<ShapeSymbolId>(),
847): ShapeSymbolId | undefined => {
848 if (!shapeId || seen.has(shapeId)) {
849 return undefined;
850 }
851
852 const shape = getShape(catalog, shapeId);
853 if (!shape) {
854 return undefined;
855 }
856
857 const nextSeen = new Set(seen);
858 nextSeen.add(shapeId);
859
860 switch (shape.node.type) {
861 case "ref":
862 return shapeFieldShapeId(catalog, shape.node.target, fieldName, nextSeen);
863 case "object":
864 return shape.node.fields[fieldName]?.shapeId;
865 case "allOf":
866 for (const item of shape.node.items) {
867 const found = shapeFieldShapeId(catalog, item, fieldName, nextSeen);
868 if (found) {
869 return found;
870 }
871 }
872 return undefined;
873 default:
874 return undefined;
875 }
876};
877
878const projectExecutionResultShape = (
879 catalog: CatalogV1,
880 capability: Capability,
881 executable: Executable,
882 responseSet: ResponseSet,
883): ShapeSymbolId => {
884 const genericDataShape = unknownShape(catalog, capability, {
885 label: `executionResult:${capability.id}:data:unknown`,
886 title: "Response data",
887 reason: `Execution result data for ${capability.id} is not statically known`,
888 });
889 const genericErrorShape = unknownShape(catalog, capability, {
890 label: `executionResult:${capability.id}:error:unknown`,
891 title: "Response error",
892 reason: `Execution result error for ${capability.id} is not statically known`,
893 });
894 const genericHeadersShape = headersShape(
895 catalog,
896 capability,
897 `executionResult:${capability.id}:headers`,
898 );
899 const genericStatusShape = scalarShape(catalog, capability, {
900 label: `executionResult:${capability.id}:status`,
901 title: "Response status",
902 scalar: "integer",
903 });
904
905 let dataShapeId: ShapeSymbolId | undefined;
906 let errorShapeId: ShapeSymbolId | undefined;
907 let headersShapeId: ShapeSymbolId = genericHeadersShape;
908 let statusShapeId: ShapeSymbolId = genericStatusShape;
909
910 switch (executable.protocol) {
911 case "http":
912 dataShapeId = projectResultShapeFromResponses(catalog, capability, responseSet);
913 errorShapeId = projectErrorShapeFromResponses(catalog, capability, responseSet);
914 break;
915 case "graphql":
916 dataShapeId =
917 shapeFieldShapeId(catalog, executable.resultShapeId, "data")
918 ?? shapeFieldShapeId(catalog, executable.resultShapeId, "body")
919 ?? executable.resultShapeId;
920 errorShapeId =
921 shapeFieldShapeId(catalog, executable.resultShapeId, "error")
922 ?? shapeFieldShapeId(catalog, executable.resultShapeId, "errors");
923 headersShapeId =
924 shapeFieldShapeId(catalog, executable.resultShapeId, "headers")
925 ?? genericHeadersShape;
926 statusShapeId =
927 shapeFieldShapeId(catalog, executable.resultShapeId, "status")
928 ?? genericStatusShape;
929 break;
930 case "mcp":
931 dataShapeId = executable.outputShapeId;
932 errorShapeId = undefined;
933 statusShapeId = constNullShape(
934 catalog,
935 capability,
936 `executionResult:${capability.id}:status:null`,
937 );
938 break;
939 }
940
941 const dataFieldShapeId = nullableShape(catalog, capability, {
942 label: `executionResult:${capability.id}:data`,
943 title: "Result data",
944 baseShapeId: dataShapeId ?? genericDataShape,
945 });
946 const errorFieldShapeId = nullableShape(catalog, capability, {
947 label: `executionResult:${capability.id}:error`,
948 title: "Result error",
949 baseShapeId: errorShapeId ?? genericErrorShape,
950 });
951 const statusFieldShapeId = nullableShape(catalog, capability, {
952 label: `executionResult:${capability.id}:status`,
953 title: "Result status",
954 baseShapeId: statusShapeId,
955 });
956
957 return createSyntheticShape(catalog, {
958 capability,
959 label: `executionResult:${capability.id}`,
960 title: `${capability.surface.title ?? capability.id} result`,
961 node: {
962 type: "object",
963 fields: {
964 data: {
965 shapeId: dataFieldShapeId,
966 docs: {
967 description: "Successful result payload when available.",
968 },
969 },
970 error: {
971 shapeId: errorFieldShapeId,
972 docs: {
973 description: "Error payload when the remote execution completed but failed.",
974 },
975 },
976 headers: {
977 shapeId: headersShapeId,
978 docs: {
979 description: "Response headers when available.",
980 },
981 },
982 status: {
983 shapeId: statusFieldShapeId,
984 docs: {
985 description: "Transport status code when available.",
986 },
987 },
988 },
989 required: ["data", "error", "headers", "status"],
990 additionalProperties: false,
991 },
992 diagnostic: {
993 level: "info",
994 code: "projection_result_shape_synthesized",
995 message: `Synthesized execution result envelope for ${capability.id}`,
996 relatedSymbolIds: unique([
997 dataFieldShapeId,
998 errorFieldShapeId,
999 headersShapeId,
1000 statusFieldShapeId,
1001 ]),
1002 },
1003 });
1004};
1005
1006const groupFieldShape = (
1007 catalog: CatalogV1,
1008 capability: Capability,
1009 location: "path" | "query" | "headers" | "cookies",
1010 fields: Record<string, { shapeId: ShapeSymbolId; docs?: DocumentationBlock }>,
1011 required: string[],
1012): ShapeSymbolId =>
1013 createSyntheticShape(catalog, {
1014 capability,
1015 label: `group:${capability.id}:${location}`,
1016 title: `${location} parameters`,
1017 node: {
1018 type: "object",
1019 fields: Object.fromEntries(
1020 Object.entries(fields).map(([name, field]) => [
1021 name,
1022 {
1023 shapeId: field.shapeId,
1024 ...(field.docs ? { docs: field.docs } : {}),
1025 },
1026 ]),
1027 ),
1028 ...(required.length > 0 ? { required } : {}),
1029 additionalProperties: false,
1030 },
1031 });
1032
1033const projectHttpCallShape = (
1034 catalog: CatalogV1,
1035 capability: Capability,
1036 executable: HttpExecutable,
1037): ShapeSymbolId => {
1038 const chain = scopeChain(catalog, executable.scopeId);
1039 const defaults = mergeScopeDefaults(chain);
1040
1041 const byLocation: Record<
1042 "path" | "query" | "header" | "cookie",
1043 ParameterSymbol[]
1044 > = {
1045 path: [],
1046 query: [],
1047 header: [],
1048 cookie: [],
1049 };
1050
1051 const pushParameter = (parameterId: ParameterSymbolId) => {
1052 const parameter = getParameter(catalog, parameterId);
1053 if (parameter) {
1054 byLocation[parameter.location].push(parameter);
1055 }
1056 };
1057
1058 for (const parameterId of defaults.parameterIds ?? []) {
1059 pushParameter(parameterId);
1060 }
1061 for (const parameterId of executable.pathParameterIds ?? []) {
1062 pushParameter(parameterId);
1063 }
1064 for (const parameterId of executable.queryParameterIds ?? []) {
1065 pushParameter(parameterId);
1066 }
1067 for (const parameterId of executable.headerParameterIds ?? []) {
1068 pushParameter(parameterId);
1069 }
1070 for (const parameterId of executable.cookieParameterIds ?? []) {
1071 pushParameter(parameterId);
1072 }
1073
1074 const explicitHeaders = unique([
1075 ...(defaults.headerIds ?? []),
1076 ])
1077 .map((headerId: HeaderSymbolId) => getHeader(catalog, headerId))
1078 .filter((symbol): symbol is HeaderSymbol => symbol !== undefined);
1079
1080 const reservedTopLevel = new Set(["body", "args", "select", "path", "query", "headers", "cookies"]);
1081 const allNames = [
1082 ...byLocation.path.map((parameter) => parameter.name),
1083 ...byLocation.query.map((parameter) => parameter.name),
1084 ...byLocation.header.map((parameter) => parameter.name),
1085 ...byLocation.cookie.map((parameter) => parameter.name),
1086 ];
1087
1088 const hasCollision = allNames.some((name, index) =>
1089 reservedTopLevel.has(name) || allNames.indexOf(name) !== index
1090 );
1091
1092 const topLevelFields: Record<string, { shapeId: ShapeSymbolId; docs?: DocumentationBlock }> = {};
1093 const topLevelRequired: string[] = [];
1094
1095 const groupedFieldNames: Array<keyof typeof byLocation> = hasCollision
1096 ? ["path", "query", "header", "cookie"]
1097 : [];
1098
1099 if (hasCollision) {
1100 createDiagnostic(catalog, {
1101 idSeed: {
1102 code: "projection_collision_grouped_fields",
1103 capabilityId: capability.id,
1104 },
1105 level: "info",
1106 code: "projection_collision_grouped_fields",
1107 message: `Grouped parameter fields for ${capability.id} to avoid collisions`,
1108 provenance: capability.provenance,
1109 });
1110 }
1111
1112 for (const location of Object.keys(byLocation) as Array<keyof typeof byLocation>) {
1113 const parameters = byLocation[location];
1114 if (parameters.length === 0) {
1115 continue;
1116 }
1117
1118 if (groupedFieldNames.includes(location)) {
1119 const groupFields: Record<string, { shapeId: ShapeSymbolId; docs?: DocumentationBlock }> = {};
1120 const groupRequired: string[] = [];
1121 for (const parameter of parameters) {
1122 groupFields[parameter.name] = {
1123 shapeId: parameterShapeId(catalog, capability, parameter),
1124 docs: parameter.docs,
1125 };
1126 if (parameter.required) {
1127 groupRequired.push(parameter.name);
1128 }
1129 }
1130
1131 const groupKey = location === "header" ? "headers" : location === "cookie" ? "cookies" : location;
1132 topLevelFields[groupKey] = {
1133 shapeId: groupFieldShape(catalog, capability, groupKey, groupFields, groupRequired),
1134 };
1135 if (groupRequired.length > 0) {
1136 topLevelRequired.push(groupKey);
1137 }
1138 continue;
1139 }
1140
1141 for (const parameter of parameters) {
1142 topLevelFields[parameter.name] = {
1143 shapeId: parameterShapeId(catalog, capability, parameter),
1144 docs: parameter.docs,
1145 };
1146 if (parameter.required) {
1147 topLevelRequired.push(parameter.name);
1148 }
1149 }
1150 }
1151
1152 for (const header of explicitHeaders) {
1153 if (hasCollision) {
1154 const existing = topLevelFields.headers
1155 ? getShape(catalog, topLevelFields.headers.shapeId)
1156 : undefined;
1157 const fields = existing && existing.node.type === "object"
1158 ? ({ ...existing.node.fields } as Record<string, { shapeId: ShapeSymbolId; docs?: DocumentationBlock }>)
1159 : {};
1160 fields[header.name] = {
1161 shapeId: header.schemaShapeId
1162 ?? createSyntheticShape(catalog, {
1163 capability,
1164 label: `header:${header.id}:unknown`,
1165 title: header.name,
1166 docs: header.docs,
1167 node: {
1168 type: "unknown",
1169 reason: `Missing shape for header ${header.id}`,
1170 },
1171 }),
1172 ...(header.docs ? { docs: header.docs } : {}),
1173 };
1174 topLevelFields.headers = {
1175 shapeId: groupFieldShape(
1176 catalog,
1177 capability,
1178 "headers",
1179 Object.fromEntries(
1180 Object.entries(fields).map(([name, field]) => [name, { shapeId: field.shapeId, docs: field.docs }]),
1181 ),
1182 [],
1183 ),
1184 };
1185 continue;
1186 }
1187
1188 topLevelFields[header.name] = {
1189 shapeId: header.schemaShapeId
1190 ?? createSyntheticShape(catalog, {
1191 capability,
1192 label: `header:${header.id}:unknown`,
1193 title: header.name,
1194 docs: header.docs,
1195 node: {
1196 type: "unknown",
1197 reason: `Missing shape for header ${header.id}`,
1198 },
1199 }),
1200 docs: header.docs,
1201 };
1202 }
1203
1204 const bodyShapeId = requestBodyShapeId(catalog, capability, executable.requestBodyId);
1205 if (bodyShapeId) {
1206 topLevelFields.body = { shapeId: bodyShapeId };
1207 const requestBody = executable.requestBodyId
1208 ? catalog.symbols[executable.requestBodyId]
1209 : undefined;
1210 if (requestBody && requestBody.kind === "requestBody" && requestBody.required) {
1211 topLevelRequired.push("body");
1212 }
1213 }
1214
1215 return createSyntheticShape(catalog, {
1216 capability,
1217 label: `call:${capability.id}`,
1218 title: `${capability.surface.title ?? capability.id} call`,
1219 node: {
1220 type: "object",
1221 fields: Object.fromEntries(
1222 Object.entries(topLevelFields).map(([name, field]) => [
1223 name,
1224 {
1225 shapeId: field.shapeId,
1226 ...(field.docs ? { docs: field.docs } : {}),
1227 },
1228 ]),
1229 ),
1230 ...(topLevelRequired.length > 0 ? { required: unique(topLevelRequired) } : {}),
1231 additionalProperties: false,
1232 },
1233 diagnostic: {
1234 level: "info",
1235 code: "projection_call_shape_synthesized",
1236 message: `Synthesized HTTP call shape for ${capability.id}`,
1237 },
1238 });
1239};
1240
1241const projectGraphqlCallShape = (
1242 catalog: CatalogV1,
1243 capability: Capability,
1244 executable: GraphQLExecutable,
1245): ShapeSymbolId => {
1246 const argumentShape = getShape(catalog, executable.argumentShapeId);
1247 const reserved = new Set(["body", "args", "select", "path", "query", "headers", "cookies"]);
1248
1249 const canFlatten =
1250 argumentShape?.node.type === "object"
1251 && Object.keys(argumentShape.node.fields).every((name) => !reserved.has(name));
1252
1253 const fields: Record<string, { shapeId: ShapeSymbolId; docs?: DocumentationBlock }> = {};
1254 const required: string[] = [];
1255
1256 if (canFlatten && argumentShape.node.type === "object") {
1257 for (const [name, field] of Object.entries(argumentShape.node.fields)) {
1258 fields[name] = {
1259 shapeId: field.shapeId,
1260 docs: field.docs,
1261 };
1262 }
1263 required.push(...(argumentShape.node.required ?? []));
1264 } else {
1265 fields.args = {
1266 shapeId: executable.argumentShapeId,
1267 docs: argumentShape?.docs,
1268 };
1269 required.push("args");
1270 }
1271
1272 if (executable.selectionMode === "caller") {
1273 fields.select = {
1274 shapeId:
1275 executable.selectionShapeId
1276 ?? createSyntheticShape(catalog, {
1277 capability,
1278 label: `select:${capability.id}:unknown`,
1279 title: `${capability.surface.title ?? capability.id} selection`,
1280 node: {
1281 type: "unknown",
1282 reason: `Missing selection shape for ${capability.id}`,
1283 },
1284 diagnostic: {
1285 level: "warning",
1286 code: "selection_shape_missing",
1287 message: `Missing selection shape for ${capability.id}`,
1288 },
1289 }),
1290 };
1291 required.push("select");
1292 }
1293
1294 return createSyntheticShape(catalog, {
1295 capability,
1296 label: `call:${capability.id}`,
1297 title: `${capability.surface.title ?? capability.id} call`,
1298 node: {
1299 type: "object",
1300 fields: Object.fromEntries(
1301 Object.entries(fields).map(([name, field]) => [
1302 name,
1303 {
1304 shapeId: field.shapeId,
1305 ...(field.docs ? { docs: field.docs } : {}),
1306 },
1307 ]),
1308 ),
1309 ...(required.length > 0 ? { required: unique(required) } : {}),
1310 additionalProperties: false,
1311 },
1312 diagnostic: {
1313 level: "info",
1314 code: "projection_call_shape_synthesized",
1315 message: `Synthesized GraphQL call shape for ${capability.id}`,
1316 },
1317 });
1318};
1319
1320const projectMcpCallShape = (
1321 catalog: CatalogV1,
1322 capability: Capability,
1323 executable: McpExecutable,
1324): ShapeSymbolId => {
1325 if (!executable.inputShapeId) {
1326 return createSyntheticShape(catalog, {
1327 capability,
1328 label: `call:${capability.id}:empty`,
1329 title: `${capability.surface.title ?? capability.id} call`,
1330 node: {
1331 type: "object",
1332 fields: {},
1333 additionalProperties: false,
1334 },
1335 diagnostic: {
1336 level: "info",
1337 code: "projection_call_shape_synthesized",
1338 message: `Synthesized empty MCP call shape for ${capability.id}`,
1339 },
1340 });
1341 }
1342
1343 const inputShape = getShape(catalog, executable.inputShapeId);
1344 if (inputShape?.node.type === "object") {
1345 return executable.inputShapeId;
1346 }
1347
1348 return createSyntheticShape(catalog, {
1349 capability,
1350 label: `call:${capability.id}`,
1351 title: `${capability.surface.title ?? capability.id} call`,
1352 node: {
1353 type: "object",
1354 fields: {
1355 input: {
1356 shapeId: executable.inputShapeId,
1357 },
1358 },
1359 required: ["input"],
1360 additionalProperties: false,
1361 },
1362 diagnostic: {
1363 level: "info",
1364 code: "projection_call_shape_synthesized",
1365 message: `Wrapped non-object MCP input shape for ${capability.id}`,
1366 },
1367 });
1368};
1369
1370const projectCapability = (
1371 catalog: CatalogV1,
1372 capability: Capability,
1373): ToolDescriptor => {
1374 const executable = chooseExecutable(catalog, capability);
1375 const responseSet = catalog.responseSets[executable.responseSetId];
1376
1377 if (!responseSet) {
1378 throw new Error(`Missing response set ${executable.responseSetId} for ${capability.id}`);
1379 }
1380
1381 let callShapeId: ShapeSymbolId;
1382 let resultShapeId: ShapeSymbolId | undefined;
1383
1384 switch (executable.protocol) {
1385 case "http":
1386 callShapeId = projectHttpCallShape(catalog, capability, executable);
1387 resultShapeId = projectExecutionResultShape(catalog, capability, executable, responseSet);
1388 break;
1389 case "graphql":
1390 callShapeId = projectGraphqlCallShape(catalog, capability, executable);
1391 resultShapeId = projectExecutionResultShape(catalog, capability, executable, responseSet);
1392 break;
1393 case "mcp":
1394 callShapeId = projectMcpCallShape(catalog, capability, executable);
1395 resultShapeId = projectExecutionResultShape(catalog, capability, executable, responseSet);
1396 break;
1397 }
1398
1399 const diagnosticIds = unique([
1400 ...(capability.diagnosticIds ?? []),
1401 ...(executable.diagnosticIds ?? []),
1402 ...(responseSet.diagnosticIds ?? []),
1403 ...(callShapeId ? catalog.symbols[callShapeId]?.diagnosticIds ?? [] : []),
1404 ...(resultShapeId ? catalog.symbols[resultShapeId]?.diagnosticIds ?? [] : []),
1405 ]);
1406 const diagnostics = diagnosticIds
1407 .map((diagnosticId) => catalog.diagnostics[diagnosticId])
1408 .filter((diagnostic): diagnostic is ImportDiagnostic => diagnostic !== undefined);
1409
1410 return {
1411 toolPath: [...capability.surface.toolPath],
1412 capabilityId: capability.id,
1413 ...(capability.surface.title ? { title: capability.surface.title } : {}),
1414 ...(capability.surface.summary ? { summary: capability.surface.summary } : {}),
1415 effect: capability.semantics.effect,
1416 interaction: {
1417 mayRequireApproval: capability.interaction.approval.mayRequire,
1418 mayElicit: capability.interaction.elicitation.mayRequest,
1419 },
1420 callShapeId,
1421 ...(resultShapeId ? { resultShapeId } : {}),
1422 responseSetId: executable.responseSetId,
1423 diagnosticCounts: {
1424 warning: diagnostics.filter((diagnostic) => diagnostic.level === "warning").length,
1425 error: diagnostics.filter((diagnostic) => diagnostic.level === "error").length,
1426 },
1427 };
1428};
1429
1430const summaryView = (
1431 catalog: CatalogV1,
1432 capability: Capability,
1433 descriptor: ToolDescriptor,
1434): CapabilitySummaryView => ({
1435 capabilityId: capability.id,
1436 toolPath: [...capability.surface.toolPath],
1437 ...(capability.surface.summary ? { summary: capability.surface.summary } : {}),
1438 executableIds: [...capability.executableIds],
1439 auth: capability.auth,
1440 interaction: capability.interaction,
1441 callShapeId: descriptor.callShapeId,
1442 ...(descriptor.resultShapeId ? { resultShapeId: descriptor.resultShapeId } : {}),
1443 responseSetId: descriptor.responseSetId,
1444 ...(capability.diagnosticIds ? { diagnosticIds: [...capability.diagnosticIds] } : {}),
1445});
1446
1447const searchDoc = (
1448 catalog: CatalogV1,
1449 capability: Capability,
1450): ToolSearchDoc => ({
1451 capabilityId: capability.id,
1452 toolPath: [...capability.surface.toolPath],
1453 ...(capability.surface.title ? { title: capability.surface.title } : {}),
1454 ...(capability.surface.summary ? { summary: capability.surface.summary } : {}),
1455 ...(capability.surface.tags ? { tags: [...capability.surface.tags] } : {}),
1456 protocolHints: unique(
1457 capability.executableIds
1458 .map((executableId: ExecutableId) => catalog.executables[executableId]?.protocol)
1459 .filter((protocol): protocol is Executable["protocol"] => protocol !== undefined),
1460 ),
1461 authHints: authHintStrings(catalog, capability.auth),
1462 effect: capability.semantics.effect,
1463});
1464
1465const symbolTitle = (symbol: IrSymbol): string | undefined => {
1466 switch (symbol.kind) {
1467 case "shape":
1468 return symbol.title ?? symbol.docs?.summary;
1469 case "parameter":
1470 case "header":
1471 return symbol.name;
1472 case "example":
1473 return symbol.name ?? symbol.docs?.summary;
1474 case "requestBody":
1475 case "response":
1476 case "securityScheme":
1477 return symbol.docs?.summary;
1478 }
1479};
1480
1481const symbolSummary = (symbol: IrSymbol): string | undefined =>
1482 symbol.docs?.summary ?? docsSummary(symbol.docs);
1483
1484const symbolEdges = (symbol: IrSymbol): Array<{ label: string; targetId: SymbolId }> => {
1485 switch (symbol.kind) {
1486 case "shape": {
1487 const edges: Array<{ label: string; targetId: SymbolId }> = [];
1488 switch (symbol.node.type) {
1489 case "object":
1490 for (const [name, field] of Object.entries(symbol.node.fields)) {
1491 edges.push({ label: `field:${name}`, targetId: field.shapeId });
1492 }
1493 if (typeof symbol.node.additionalProperties !== "boolean" && symbol.node.additionalProperties) {
1494 edges.push({
1495 label: "additionalProperties",
1496 targetId: symbol.node.additionalProperties,
1497 });
1498 }
1499 for (const [pattern, targetId] of Object.entries(symbol.node.patternProperties ?? {})) {
1500 edges.push({ label: `pattern:${pattern}`, targetId });
1501 }
1502 break;
1503 case "array":
1504 edges.push({ label: "item", targetId: symbol.node.itemShapeId });
1505 break;
1506 case "tuple":
1507 symbol.node.itemShapeIds.forEach((targetId, index) => {
1508 edges.push({ label: `item:${String(index)}`, targetId });
1509 });
1510 if (typeof symbol.node.additionalItems !== "boolean" && symbol.node.additionalItems) {
1511 edges.push({ label: "additionalItems", targetId: symbol.node.additionalItems });
1512 }
1513 break;
1514 case "map":
1515 edges.push({ label: "value", targetId: symbol.node.valueShapeId });
1516 break;
1517 case "allOf":
1518 case "anyOf":
1519 case "oneOf":
1520 symbol.node.items.forEach((targetId, index) => {
1521 edges.push({ label: `${symbol.node.type}:${String(index)}`, targetId });
1522 });
1523 if (symbol.node.type === "oneOf") {
1524 for (const [key, targetId] of Object.entries(symbol.node.discriminator?.mapping ?? {})) {
1525 edges.push({ label: `discriminator:${key}`, targetId });
1526 }
1527 }
1528 break;
1529 case "nullable":
1530 edges.push({ label: "item", targetId: symbol.node.itemShapeId });
1531 break;
1532 case "ref":
1533 edges.push({ label: "target", targetId: symbol.node.target });
1534 break;
1535 case "not":
1536 edges.push({ label: "not", targetId: symbol.node.itemShapeId });
1537 break;
1538 case "conditional":
1539 edges.push({ label: "if", targetId: symbol.node.ifShapeId });
1540 if (symbol.node.thenShapeId) {
1541 edges.push({ label: "then", targetId: symbol.node.thenShapeId });
1542 }
1543 if (symbol.node.elseShapeId) {
1544 edges.push({ label: "else", targetId: symbol.node.elseShapeId });
1545 }
1546 break;
1547 case "graphqlInterface":
1548 for (const [name, field] of Object.entries(symbol.node.fields)) {
1549 edges.push({ label: `field:${name}`, targetId: field.shapeId });
1550 }
1551 symbol.node.possibleTypeIds.forEach((targetId) => {
1552 edges.push({ label: "possibleType", targetId });
1553 });
1554 break;
1555 case "graphqlUnion":
1556 symbol.node.memberTypeIds.forEach((targetId) => {
1557 edges.push({ label: "member", targetId });
1558 });
1559 break;
1560 default:
1561 break;
1562 }
1563 return edges;
1564 }
1565 case "parameter":
1566 case "header":
1567 return unique([
1568 ...(symbol.schemaShapeId ? [{ label: "schema", targetId: symbol.schemaShapeId as SymbolId }] : []),
1569 ...((symbol.content ?? []).flatMap((content, index) => [
1570 ...(content.shapeId ? [{ label: `content:${String(index)}`, targetId: content.shapeId as SymbolId }] : []),
1571 ...((content.exampleIds ?? []).map((targetId) => ({
1572 label: `example:${String(index)}`,
1573 targetId: targetId as SymbolId,
1574 }))),
1575 ])),
1576 ...((symbol.exampleIds ?? []).map((targetId) => ({
1577 label: "example",
1578 targetId: targetId as SymbolId,
1579 }))),
1580 ]);
1581 case "requestBody":
1582 return (symbol.contents ?? []).flatMap((content, index) => [
1583 ...(content.shapeId ? [{ label: `content:${String(index)}`, targetId: content.shapeId as SymbolId }] : []),
1584 ...((content.exampleIds ?? []).map((targetId) => ({
1585 label: `example:${String(index)}`,
1586 targetId: targetId as SymbolId,
1587 }))),
1588 ]);
1589 case "response":
1590 return [
1591 ...((symbol.headerIds ?? []).map((targetId) => ({
1592 label: "header",
1593 targetId: targetId as SymbolId,
1594 }))),
1595 ...((symbol.contents ?? []).flatMap((content, index) => [
1596 ...(content.shapeId ? [{ label: `content:${String(index)}`, targetId: content.shapeId as SymbolId }] : []),
1597 ...((content.exampleIds ?? []).map((targetId) => ({
1598 label: `example:${String(index)}`,
1599 targetId: targetId as SymbolId,
1600 }))),
1601 ])),
1602 ];
1603 case "example":
1604 case "securityScheme":
1605 return [];
1606 }
1607};
1608
1609export const decodeCatalogV1 = (input: unknown): CatalogV1 => {
1610 try {
1611 return decodeCatalogSync(input);
1612 } catch (cause) {
1613 throw new Error(ParseResult.TreeFormatter.formatErrorSync(cause as never));
1614 }
1615};
1616
1617export const decodeCatalogFragmentV1 = (input: unknown): CatalogFragmentV1 => {
1618 try {
1619 return decodeCatalogFragmentSync(input);
1620 } catch (cause) {
1621 throw new Error(ParseResult.TreeFormatter.formatErrorSync(cause as never));
1622 }
1623};
1624
1625export const decodeCatalogSnapshotV1 = (input: unknown) => {
1626 try {
1627 const snapshot = decodeCatalogSnapshotSync(input);
1628 assertCatalogInvariants(snapshot.catalog);
1629 return snapshot;
1630 } catch (cause) {
1631 if (cause instanceof Error) {
1632 throw cause;
1633 }
1634 throw new Error(ParseResult.TreeFormatter.formatErrorSync(cause as never));
1635 }
1636};
1637
1638export const createEmptyCatalogV1 = (): CatalogV1 => emptyCatalog();
1639
1640export const createCatalogSnapshotV1 = (input: {
1641 import: ImportMetadata;
1642 catalog: CatalogV1;
1643}) => {
1644 const catalog = assertCatalogInvariants(decodeCatalogV1(input.catalog));
1645
1646 return {
1647 version: "ir.v1.snapshot" as const,
1648 import: input.import,
1649 catalog,
1650 };
1651};
1652
1653export const createCatalogSnapshotV1FromFragments = (input: {
1654 import: ImportMetadata;
1655 fragments: readonly CatalogFragmentV1[];
1656}) =>
1657 createCatalogSnapshotV1({
1658 import: input.import,
1659 catalog: mergeCatalogFragments(input.fragments),
1660 });
1661
1662export const mergeCatalogFragments = (fragments: readonly CatalogFragmentV1[]): CatalogV1 => {
1663 const catalog = emptyCatalog();
1664 const seenByCollection = new Map<string, string>();
1665
1666 const mergeRecord = <T extends { id: string }>(
1667 collectionName: keyof CatalogV1,
1668 entries: Record<string, T> | undefined,
1669 ) => {
1670 if (!entries) {
1671 return;
1672 }
1673
1674 const target = catalog[collectionName] as Record<string, T>;
1675 for (const [id, entry] of Object.entries(entries)) {
1676 const existing = target[id];
1677 if (!existing) {
1678 target[id] = entry;
1679 seenByCollection.set(`${String(collectionName)}:${id}`, stableStringify(entry));
1680 continue;
1681 }
1682
1683 const existingHash = seenByCollection.get(`${String(collectionName)}:${id}`) ?? stableStringify(existing);
1684 const nextHash = stableStringify(entry);
1685 if (existingHash === nextHash) {
1686 continue;
1687 }
1688
1689 if (collectionName !== "diagnostics") {
1690 createDiagnostic(catalog, {
1691 idSeed: {
1692 collectionName,
1693 id,
1694 existingHash,
1695 nextHash,
1696 },
1697 level: "error",
1698 code: "merge_conflict_preserved_first",
1699 message: `Conflicting ${String(collectionName)} entry for ${id}; preserved first value`,
1700 provenance: "provenance" in existing
1701 ? (existing as { provenance: ImportDiagnostic["provenance"] }).provenance
1702 : [],
1703 });
1704 }
1705 }
1706 };
1707
1708 for (const fragment of fragments) {
1709 mergeRecord("documents", fragment.documents);
1710 mergeRecord("resources", fragment.resources);
1711 mergeRecord("scopes", fragment.scopes);
1712 mergeRecord("symbols", fragment.symbols);
1713 mergeRecord("capabilities", fragment.capabilities);
1714 mergeRecord("executables", fragment.executables);
1715 mergeRecord("responseSets", fragment.responseSets);
1716 mergeRecord("diagnostics", fragment.diagnostics);
1717 }
1718
1719 return assertCatalogInvariants(decodeCatalogV1(catalog));
1720};
1721
1722export const validateCatalogInvariants = (
1723 catalog: CatalogV1,
1724): CatalogInvariantViolation[] => {
1725 const violations: CatalogInvariantViolation[] = [];
1726
1727 const checkProvenanceDocuments = (input: {
1728 entityId: string;
1729 provenance: readonly { documentId: DocumentId }[];
1730 }) => {
1731 for (const provenance of input.provenance) {
1732 if (!catalog.documents[provenance.documentId]) {
1733 violations.push({
1734 code: "missing_provenance_document",
1735 entityId: input.entityId,
1736 message: `Entity ${input.entityId} references missing provenance document ${provenance.documentId}`,
1737 });
1738 }
1739 }
1740 };
1741
1742 for (const symbol of Object.values(catalog.symbols)) {
1743 if (symbol.provenance.length === 0) {
1744 violations.push({
1745 code: "missing_symbol_provenance",
1746 entityId: symbol.id,
1747 message: `Symbol ${symbol.id} is missing provenance`,
1748 });
1749 }
1750
1751 if (symbol.kind === "shape") {
1752 if (!symbol.resourceId && !symbol.synthetic) {
1753 violations.push({
1754 code: "missing_resource_context",
1755 entityId: symbol.id,
1756 message: `Shape ${symbol.id} must belong to a resource or be synthetic`,
1757 });
1758 }
1759
1760 if (symbol.node.type === "ref" && !catalog.symbols[symbol.node.target]) {
1761 violations.push({
1762 code: "missing_reference_target",
1763 entityId: symbol.id,
1764 message: `Shape ${symbol.id} references missing shape ${symbol.node.target}`,
1765 });
1766 }
1767
1768 if (symbol.node.type === "unknown" && symbol.node.reason?.includes("unresolved")) {
1769 const hasDiagnostic = (symbol.diagnosticIds ?? [])
1770 .map((diagnosticId) => catalog.diagnostics[diagnosticId])
1771 .some((diagnostic) => diagnostic?.code === "unresolved_ref");
1772
1773 if (!hasDiagnostic) {
1774 violations.push({
1775 code: "missing_unresolved_ref_diagnostic",
1776 entityId: symbol.id,
1777 message: `Shape ${symbol.id} is unresolved but has no unresolved_ref diagnostic`,
1778 });
1779 }
1780 }
1781
1782 if (symbol.synthetic && symbol.resourceId && !catalog.resources[symbol.resourceId]) {
1783 violations.push({
1784 code: "missing_resource_context",
1785 entityId: symbol.id,
1786 message: `Synthetic shape ${symbol.id} references missing resource ${symbol.resourceId}`,
1787 });
1788 }
1789 }
1790
1791 checkProvenanceDocuments({
1792 entityId: symbol.id,
1793 provenance: symbol.provenance,
1794 });
1795 }
1796
1797 for (const collection of [
1798 ...Object.values(catalog.resources),
1799 ...Object.values(catalog.scopes),
1800 ...Object.values(catalog.capabilities),
1801 ...Object.values(catalog.executables),
1802 ...Object.values(catalog.responseSets),
1803 ]) {
1804 if ("provenance" in collection && collection.provenance.length === 0) {
1805 violations.push({
1806 code: "missing_entity_provenance",
1807 entityId: "id" in collection ? collection.id : undefined,
1808 message: `Entity ${"id" in collection ? collection.id : "unknown"} is missing provenance`,
1809 });
1810 }
1811
1812 if ("id" in collection) {
1813 checkProvenanceDocuments({
1814 entityId: collection.id,
1815 provenance: collection.provenance,
1816 });
1817 }
1818 }
1819
1820 for (const resource of Object.values(catalog.resources)) {
1821 if (!catalog.documents[resource.documentId]) {
1822 violations.push({
1823 code: "missing_document",
1824 entityId: resource.id,
1825 message: `Resource ${resource.id} references missing document ${resource.documentId}`,
1826 });
1827 }
1828 }
1829
1830 for (const capability of Object.values(catalog.capabilities)) {
1831 if (!catalog.scopes[capability.serviceScopeId]) {
1832 violations.push({
1833 code: "missing_service_scope",
1834 entityId: capability.id,
1835 message: `Capability ${capability.id} references missing scope ${capability.serviceScopeId}`,
1836 });
1837 }
1838
1839 if (
1840 capability.preferredExecutableId
1841 && !capability.executableIds.includes(capability.preferredExecutableId)
1842 ) {
1843 violations.push({
1844 code: "invalid_preferred_executable",
1845 entityId: capability.id,
1846 message: `Capability ${capability.id} preferred executable is not in executableIds`,
1847 });
1848 }
1849
1850 for (const executableId of capability.executableIds) {
1851 if (!catalog.executables[executableId]) {
1852 violations.push({
1853 code: "missing_executable",
1854 entityId: capability.id,
1855 message: `Capability ${capability.id} references missing executable ${executableId}`,
1856 });
1857 }
1858 }
1859 }
1860
1861 for (const executable of Object.values(catalog.executables)) {
1862 if (!catalog.capabilities[executable.capabilityId]) {
1863 violations.push({
1864 code: "missing_executable",
1865 entityId: executable.id,
1866 message: `Executable ${executable.id} references missing capability ${executable.capabilityId}`,
1867 });
1868 }
1869
1870 if (!catalog.scopes[executable.scopeId]) {
1871 violations.push({
1872 code: "missing_scope",
1873 entityId: executable.id,
1874 message: `Executable ${executable.id} references missing scope ${executable.scopeId}`,
1875 });
1876 }
1877
1878 if (!catalog.responseSets[executable.responseSetId]) {
1879 violations.push({
1880 code: "missing_response_set",
1881 entityId: executable.id,
1882 message: `Executable ${executable.id} references missing response set ${executable.responseSetId}`,
1883 });
1884 }
1885 }
1886
1887 return violations;
1888};
1889
1890const assertCatalogInvariants = (catalog: CatalogV1): CatalogV1 => {
1891 const violations = validateCatalogInvariants(catalog);
1892 if (violations.length === 0) {
1893 return catalog;
1894 }
1895
1896 const preview = violations
1897 .slice(0, 5)
1898 .map((violation) => `${violation.code}: ${violation.message}`)
1899 .join("\n");
1900
1901 throw new Error(
1902 [
1903 `Invalid IR catalog (${violations.length} invariant violation${violations.length === 1 ? "" : "s"}).`,
1904 preview,
1905 ...(violations.length > 5 ? [`...and ${String(violations.length - 5)} more`] : []),
1906 ].join("\n"),
1907 );
1908};
1909
1910export const projectCatalogForAgentSdk = (input: {
1911 catalog: CatalogV1;
1912}): ProjectedCatalog => {
1913 const workingCatalog = cloneCatalog(input.catalog);
1914 const toolDescriptors = {} as Record<CapabilityId, ToolDescriptor>;
1915 const searchDocs = {} as Record<CapabilityId, ToolSearchDoc>;
1916 const capabilityViews = {} as Record<CapabilityId, CapabilitySummaryView>;
1917
1918 const capabilities = Object.values(workingCatalog.capabilities)
1919 .sort((left, right) => left.id.localeCompare(right.id));
1920
1921 for (const capability of capabilities) {
1922 const descriptor = projectCapability(workingCatalog, capability);
1923 toolDescriptors[capability.id] = descriptor;
1924 searchDocs[capability.id] = searchDoc(workingCatalog, capability);
1925 capabilityViews[capability.id] = summaryView(workingCatalog, capability, descriptor);
1926 }
1927
1928 return {
1929 catalog: workingCatalog,
1930 toolDescriptors,
1931 searchDocs,
1932 capabilityViews,
1933 };
1934};
1935
1936export const projectSymbolShallowView = (
1937 catalog: CatalogV1,
1938 symbolId: SymbolId,
1939): SymbolShallowView => {
1940 const symbol = catalog.symbols[symbolId];
1941 if (!symbol) {
1942 throw new Error(`Unknown symbol ${symbolId}`);
1943 }
1944
1945 return {
1946 symbolId,
1947 kind: symbol.kind,
1948 ...(symbolTitle(symbol) ? { title: symbolTitle(symbol) } : {}),
1949 ...(symbolSummary(symbol) ? { summary: symbolSummary(symbol) } : {}),
1950 edges: symbolEdges(symbol),
1951 };
1952};
1953
1954export const projectSymbolExpandedView = (
1955 catalog: CatalogV1,
1956 symbolId: SymbolId,
1957): SymbolExpandedView => {
1958 const symbol = catalog.symbols[symbolId];
1959 if (!symbol) {
1960 throw new Error(`Unknown symbol ${symbolId}`);
1961 }
1962
1963 return {
1964 symbolId,
1965 symbol,
1966 ...(symbol.diagnosticIds
1967 ? {
1968 diagnostics: symbol.diagnosticIds
1969 .map((diagnosticId) => catalog.diagnostics[diagnosticId])
1970 .filter((diagnostic): diagnostic is ImportDiagnostic => diagnostic !== undefined),
1971 }
1972 : {}),
1973 };
1974};
1975
1976export const buildInvocationPlan = (input: {
1977 catalog: CatalogV1;
1978 capabilityId: CapabilityId;
1979 executableId?: ExecutableId;
1980 connectionBindingId: string;
1981 resolvedEndpoint?: string;
1982 resolvedAuth?: unknown;
1983 resolvedInteraction?: Partial<InvocationPlan["resolvedInteraction"]>;
1984 requestPlan: unknown;
1985}): InvocationPlan => {
1986 const capability = input.catalog.capabilities[input.capabilityId];
1987 if (!capability) {
1988 throw new Error(`Unknown capability ${input.capabilityId}`);
1989 }
1990
1991 const executable = input.executableId
1992 ? input.catalog.executables[input.executableId]
1993 : chooseExecutable(input.catalog, capability);
1994 if (!executable) {
1995 throw new Error(`Unknown executable for capability ${input.capabilityId}`);
1996 }
1997
1998 const scopes = scopeChain(input.catalog, executable.scopeId).map((scope) => scope.id);
1999
2000 return {
2001 capabilityId: capability.id,
2002 executableId: executable.id,
2003 connectionBindingId: input.connectionBindingId,
2004 resolvedScopeChain: scopes,
2005 ...(input.resolvedEndpoint ? { resolvedEndpoint: input.resolvedEndpoint } : {}),
2006 ...(input.resolvedAuth !== undefined ? { resolvedAuth: input.resolvedAuth } : {}),
2007 resolvedInteraction: {
2008 requiresApproval: input.resolvedInteraction?.requiresApproval
2009 ?? capability.interaction.approval.mayRequire,
2010 mayPauseForElicitation: input.resolvedInteraction?.mayPauseForElicitation
2011 ?? capability.interaction.elicitation.mayRequest,
2012 resumable: input.resolvedInteraction?.resumable
2013 ?? capability.interaction.resume.supported,
2014 },
2015 requestPlan: input.requestPlan,
2016 provenance: capability.provenance,
2017 };
2018};
packages/kernel/ir/src/ids.ts+58−0
@@ -0,0 +1,58 @@
1import { Schema } from "effect";
2
3export const DocumentIdSchema = Schema.String.pipe(Schema.brand("DocumentId"));
4export const ResourceIdSchema = Schema.String.pipe(Schema.brand("ResourceId"));
5export const ScopeIdSchema = Schema.String.pipe(Schema.brand("ScopeId"));
6export const CapabilityIdSchema = Schema.String.pipe(Schema.brand("CapabilityId"));
7export const ExecutableIdSchema = Schema.String.pipe(Schema.brand("ExecutableId"));
8export const ResponseSetIdSchema = Schema.String.pipe(Schema.brand("ResponseSetId"));
9export const DiagnosticIdSchema = Schema.String.pipe(Schema.brand("DiagnosticId"));
10
11export const ShapeSymbolIdSchema = Schema.String.pipe(Schema.brand("ShapeSymbolId"));
12export const ParameterSymbolIdSchema = Schema.String.pipe(Schema.brand("ParameterSymbolId"));
13export const RequestBodySymbolIdSchema = Schema.String.pipe(
14 Schema.brand("RequestBodySymbolId"),
15);
16export const ResponseSymbolIdSchema = Schema.String.pipe(
17 Schema.brand("ResponseSymbolId"),
18);
19export const HeaderSymbolIdSchema = Schema.String.pipe(Schema.brand("HeaderSymbolId"));
20export const ExampleSymbolIdSchema = Schema.String.pipe(Schema.brand("ExampleSymbolId"));
21export const SecuritySchemeSymbolIdSchema = Schema.String.pipe(
22 Schema.brand("SecuritySchemeSymbolId"),
23);
24
25export type DocumentId = typeof DocumentIdSchema.Type;
26export type ResourceId = typeof ResourceIdSchema.Type;
27export type ScopeId = typeof ScopeIdSchema.Type;
28export type CapabilityId = typeof CapabilityIdSchema.Type;
29export type ExecutableId = typeof ExecutableIdSchema.Type;
30export type ResponseSetId = typeof ResponseSetIdSchema.Type;
31export type DiagnosticId = typeof DiagnosticIdSchema.Type;
32
33export type ShapeSymbolId = typeof ShapeSymbolIdSchema.Type;
34export type ParameterSymbolId = typeof ParameterSymbolIdSchema.Type;
35export type RequestBodySymbolId = typeof RequestBodySymbolIdSchema.Type;
36export type ResponseSymbolId = typeof ResponseSymbolIdSchema.Type;
37export type HeaderSymbolId = typeof HeaderSymbolIdSchema.Type;
38export type ExampleSymbolId = typeof ExampleSymbolIdSchema.Type;
39export type SecuritySchemeSymbolId = typeof SecuritySchemeSymbolIdSchema.Type;
40
41export type SymbolId =
42 | ShapeSymbolId
43 | ParameterSymbolId
44 | RequestBodySymbolId
45 | ResponseSymbolId
46 | HeaderSymbolId
47 | ExampleSymbolId
48 | SecuritySchemeSymbolId;
49
50export const SymbolIdSchema = Schema.Union(
51 ShapeSymbolIdSchema,
52 ParameterSymbolIdSchema,
53 RequestBodySymbolIdSchema,
54 ResponseSymbolIdSchema,
55 HeaderSymbolIdSchema,
56 ExampleSymbolIdSchema,
57 SecuritySchemeSymbolIdSchema,
58);
packages/kernel/ir/src/index.ts+3−0
@@ -0,0 +1,3 @@
1export * from "./ids";
2export * from "./model";
3export * from "./catalog";
packages/kernel/ir/src/model.ts+971−0
@@ -0,0 +1,971 @@
1import { Schema } from "effect";
2
3import {
4 CapabilityIdSchema,
5 type CapabilityId,
6 DiagnosticIdSchema,
7 type DiagnosticId,
8 DocumentIdSchema,
9 type DocumentId,
10 ExecutableIdSchema,
11 type ExecutableId,
12 ExampleSymbolIdSchema,
13 type ExampleSymbolId,
14 HeaderSymbolIdSchema,
15 type HeaderSymbolId,
16 ParameterSymbolIdSchema,
17 type ParameterSymbolId,
18 RequestBodySymbolIdSchema,
19 type RequestBodySymbolId,
20 ResourceIdSchema,
21 type ResourceId,
22 ResponseSetIdSchema,
23 type ResponseSetId,
24 ResponseSymbolIdSchema,
25 type ResponseSymbolId,
26 ScopeIdSchema,
27 type ScopeId,
28 SecuritySchemeSymbolIdSchema,
29 type SecuritySchemeSymbolId,
30 ShapeSymbolIdSchema,
31 type ShapeSymbolId,
32 SymbolIdSchema,
33 type SymbolId,
34} from "./ids";
35
36export const SourceKindSchema = Schema.Literal(
37 "openapi",
38 "graphql-schema",
39 "google-discovery",
40 "mcp",
41 "custom",
42);
43
44export const ScopeKindSchema = Schema.Literal(
45 "service",
46 "document",
47 "resource",
48 "pathItem",
49 "operation",
50 "folder",
51);
52
53export const EffectKindSchema = Schema.Literal(
54 "read",
55 "write",
56 "delete",
57 "action",
58 "subscribe",
59);
60
61export const ParameterLocationSchema = Schema.Literal(
62 "path",
63 "query",
64 "header",
65 "cookie",
66);
67
68export const ResponseTraitSchema = Schema.Literal(
69 "success",
70 "redirect",
71 "stream",
72 "download",
73 "upload",
74 "longRunning",
75);
76
77export const SecuritySchemeTypeSchema = Schema.Literal(
78 "oauth2",
79 "http",
80 "apiKey",
81 "basic",
82 "bearer",
83 "custom",
84);
85
86export const SelectionModeSchema = Schema.Literal(
87 "caller",
88 "fixed",
89 "persisted",
90);
91
92export const GraphQLToolKindSchema = Schema.Literal(
93 "request",
94 "field",
95);
96
97export const StatusRangeSchema = Schema.Literal(
98 "1XX",
99 "2XX",
100 "3XX",
101 "4XX",
102 "5XX",
103);
104
105export const PaginationHintKindSchema = Schema.Literal(
106 "cursor",
107 "offset",
108 "token",
109 "unknown",
110);
111
112export const DiagnosticLevelSchema = Schema.Literal(
113 "info",
114 "warning",
115 "error",
116);
117
118export const DiagnosticCodeSchema = Schema.Literal(
119 "external_ref_bundled",
120 "relative_ref_rebased",
121 "schema_hoisted",
122 "selection_shape_synthesized",
123 "opaque_hook_imported",
124 "discriminator_lost",
125 "multi_response_union_synthesized",
126 "unsupported_link_preserved_native",
127 "unsupported_callback_preserved_native",
128 "unresolved_ref",
129 "merge_conflict_preserved_first",
130 "projection_call_shape_synthesized",
131 "projection_result_shape_synthesized",
132 "projection_collision_grouped_fields",
133 "synthetic_resource_context_created",
134 "selection_shape_missing",
135);
136
137export const NativeEncodingSchema = Schema.Literal(
138 "json",
139 "yaml",
140 "graphql",
141 "text",
142 "unknown",
143);
144
145export const ProvenanceRelationSchema = Schema.Literal(
146 "declared",
147 "hoisted",
148 "derived",
149 "merged",
150 "projected",
151);
152
153export const DocumentationBlockSchema = Schema.Struct({
154 summary: Schema.optional(Schema.String),
155 description: Schema.optional(Schema.String),
156 externalDocsUrl: Schema.optional(Schema.String),
157});
158
159export const ProvenanceRefSchema = Schema.Struct({
160 relation: ProvenanceRelationSchema,
161 documentId: DocumentIdSchema,
162 resourceId: Schema.optional(ResourceIdSchema),
163 pointer: Schema.optional(Schema.String),
164 line: Schema.optional(Schema.Number),
165 column: Schema.optional(Schema.Number),
166});
167
168export const NativeBlobSchema = Schema.Struct({
169 sourceKind: SourceKindSchema,
170 kind: Schema.String,
171 pointer: Schema.optional(Schema.String),
172 encoding: Schema.optional(NativeEncodingSchema),
173 summary: Schema.optional(Schema.String),
174 value: Schema.optional(Schema.Unknown),
175});
176
177export const EntityBaseSchema = Schema.Struct({
178 synthetic: Schema.Boolean,
179 provenance: Schema.Array(ProvenanceRefSchema),
180 diagnosticIds: Schema.optional(Schema.Array(DiagnosticIdSchema)),
181 native: Schema.optional(Schema.Array(NativeBlobSchema)),
182});
183
184export const ImportMetadataSchema = Schema.Struct({
185 sourceKind: SourceKindSchema,
186 adapterKey: Schema.String,
187 importerVersion: Schema.String,
188 importedAt: Schema.String,
189 sourceConfigHash: Schema.String,
190});
191
192export const SourceDocumentSchema = Schema.Struct({
193 id: DocumentIdSchema,
194 kind: SourceKindSchema,
195 title: Schema.optional(Schema.String),
196 versionHint: Schema.optional(Schema.String),
197 fetchedAt: Schema.String,
198 rawRef: Schema.String,
199 entryUri: Schema.optional(Schema.String),
200 native: Schema.optional(Schema.Array(NativeBlobSchema)),
201});
202
203export const FieldSpecSchema = Schema.Struct({
204 shapeId: ShapeSymbolIdSchema,
205 docs: Schema.optional(DocumentationBlockSchema),
206 deprecated: Schema.optional(Schema.Boolean),
207 exampleIds: Schema.optional(Schema.Array(ExampleSymbolIdSchema)),
208});
209
210export const DiscriminatorSpecSchema = Schema.Struct({
211 propertyName: Schema.String,
212 mapping: Schema.optional(
213 Schema.Record({
214 key: Schema.String,
215 value: ShapeSymbolIdSchema,
216 }),
217 ),
218});
219
220export const ScalarShapeSchema = Schema.Struct({
221 type: Schema.Literal("scalar"),
222 scalar: Schema.Literal("string", "number", "integer", "boolean", "null", "bytes"),
223 format: Schema.optional(Schema.String),
224 constraints: Schema.optional(
225 Schema.Record({
226 key: Schema.String,
227 value: Schema.Unknown,
228 }),
229 ),
230});
231
232export const UnknownShapeSchema = Schema.Struct({
233 type: Schema.Literal("unknown"),
234 reason: Schema.optional(Schema.String),
235});
236
237export const ConstShapeSchema = Schema.Struct({
238 type: Schema.Literal("const"),
239 value: Schema.Unknown,
240});
241
242export const EnumShapeSchema = Schema.Struct({
243 type: Schema.Literal("enum"),
244 values: Schema.Array(Schema.Unknown),
245});
246
247export const ObjectShapeSchema = Schema.Struct({
248 type: Schema.Literal("object"),
249 fields: Schema.Record({
250 key: Schema.String,
251 value: FieldSpecSchema,
252 }),
253 required: Schema.optional(Schema.Array(Schema.String)),
254 additionalProperties: Schema.optional(
255 Schema.Union(Schema.Boolean, ShapeSymbolIdSchema),
256 ),
257 patternProperties: Schema.optional(
258 Schema.Record({
259 key: Schema.String,
260 value: ShapeSymbolIdSchema,
261 }),
262 ),
263});
264
265export const ArrayShapeSchema = Schema.Struct({
266 type: Schema.Literal("array"),
267 itemShapeId: ShapeSymbolIdSchema,
268 minItems: Schema.optional(Schema.Number),
269 maxItems: Schema.optional(Schema.Number),
270});
271
272export const TupleShapeSchema = Schema.Struct({
273 type: Schema.Literal("tuple"),
274 itemShapeIds: Schema.Array(ShapeSymbolIdSchema),
275 additionalItems: Schema.optional(
276 Schema.Union(Schema.Boolean, ShapeSymbolIdSchema),
277 ),
278});
279
280export const MapShapeSchema = Schema.Struct({
281 type: Schema.Literal("map"),
282 valueShapeId: ShapeSymbolIdSchema,
283});
284
285export const AllOfShapeSchema = Schema.Struct({
286 type: Schema.Literal("allOf"),
287 items: Schema.Array(ShapeSymbolIdSchema),
288});
289
290export const AnyOfShapeSchema = Schema.Struct({
291 type: Schema.Literal("anyOf"),
292 items: Schema.Array(ShapeSymbolIdSchema),
293});
294
295export const OneOfShapeSchema = Schema.Struct({
296 type: Schema.Literal("oneOf"),
297 items: Schema.Array(ShapeSymbolIdSchema),
298 discriminator: Schema.optional(DiscriminatorSpecSchema),
299});
300
301export const NullableShapeSchema = Schema.Struct({
302 type: Schema.Literal("nullable"),
303 itemShapeId: ShapeSymbolIdSchema,
304});
305
306export const RefShapeSchema = Schema.Struct({
307 type: Schema.Literal("ref"),
308 target: ShapeSymbolIdSchema,
309});
310
311export const NotShapeSchema = Schema.Struct({
312 type: Schema.Literal("not"),
313 itemShapeId: ShapeSymbolIdSchema,
314});
315
316export const ConditionalShapeSchema = Schema.Struct({
317 type: Schema.Literal("conditional"),
318 ifShapeId: ShapeSymbolIdSchema,
319 thenShapeId: Schema.optional(ShapeSymbolIdSchema),
320 elseShapeId: Schema.optional(ShapeSymbolIdSchema),
321});
322
323export const GraphQLInterfaceShapeSchema = Schema.Struct({
324 type: Schema.Literal("graphqlInterface"),
325 fields: Schema.Record({
326 key: Schema.String,
327 value: FieldSpecSchema,
328 }),
329 possibleTypeIds: Schema.Array(ShapeSymbolIdSchema),
330});
331
332export const GraphQLUnionShapeSchema = Schema.Struct({
333 type: Schema.Literal("graphqlUnion"),
334 memberTypeIds: Schema.Array(ShapeSymbolIdSchema),
335});
336
337export const ShapeNodeSchema = Schema.Union(
338 UnknownShapeSchema,
339 ScalarShapeSchema,
340 ConstShapeSchema,
341 EnumShapeSchema,
342 ObjectShapeSchema,
343 ArrayShapeSchema,
344 TupleShapeSchema,
345 MapShapeSchema,
346 AllOfShapeSchema,
347 AnyOfShapeSchema,
348 OneOfShapeSchema,
349 NullableShapeSchema,
350 RefShapeSchema,
351 NotShapeSchema,
352 ConditionalShapeSchema,
353 GraphQLInterfaceShapeSchema,
354 GraphQLUnionShapeSchema,
355);
356
357export const AnchorTargetSchema = Schema.Struct({
358 shapeId: ShapeSymbolIdSchema,
359 pointer: Schema.optional(Schema.String),
360});
361
362export const SchemaResourceSchema = Schema.extend(
363 Schema.Struct({
364 id: ResourceIdSchema,
365 documentId: DocumentIdSchema,
366 canonicalUri: Schema.String,
367 baseUri: Schema.String,
368 dialectUri: Schema.optional(Schema.String),
369 rootShapeId: Schema.optional(ShapeSymbolIdSchema),
370 anchors: Schema.Record({
371 key: Schema.String,
372 value: AnchorTargetSchema,
373 }),
374 dynamicAnchors: Schema.Record({
375 key: Schema.String,
376 value: AnchorTargetSchema,
377 }),
378 }),
379 EntityBaseSchema,
380);
381
382export const RequestPlacementHintSchema = Schema.Union(
383 Schema.Struct({
384 location: Schema.Literal("header", "query", "cookie"),
385 name: Schema.String,
386 }),
387 Schema.Struct({
388 location: Schema.Literal("body"),
389 path: Schema.String,
390 }),
391);
392
393export const OAuthFlowSchema = Schema.Struct({
394 authorizationUrl: Schema.optional(Schema.String),
395 tokenUrl: Schema.optional(Schema.String),
396 refreshUrl: Schema.optional(Schema.String),
397 scopes: Schema.optional(
398 Schema.Record({
399 key: Schema.String,
400 value: Schema.String,
401 }),
402 ),
403});
404
405export const SecurityPlacementSchema = Schema.Struct({
406 in: Schema.optional(Schema.Literal("header", "query", "cookie")),
407 name: Schema.optional(Schema.String),
408});
409
410export const SecuritySchemeSymbolSchema = Schema.extend(
411 Schema.Struct({
412 id: SecuritySchemeSymbolIdSchema,
413 kind: Schema.Literal("securityScheme"),
414 schemeType: SecuritySchemeTypeSchema,
415 docs: Schema.optional(DocumentationBlockSchema),
416 placement: Schema.optional(SecurityPlacementSchema),
417 http: Schema.optional(
418 Schema.Struct({
419 scheme: Schema.String,
420 bearerFormat: Schema.optional(Schema.String),
421 }),
422 ),
423 apiKey: Schema.optional(
424 Schema.Struct({
425 in: Schema.Literal("header", "query", "cookie"),
426 name: Schema.String,
427 }),
428 ),
429 oauth: Schema.optional(
430 Schema.Struct({
431 flows: Schema.optional(
432 Schema.Record({
433 key: Schema.String,
434 value: OAuthFlowSchema,
435 }),
436 ),
437 scopes: Schema.optional(
438 Schema.Record({
439 key: Schema.String,
440 value: Schema.String,
441 }),
442 ),
443 }),
444 ),
445 custom: Schema.optional(
446 Schema.Struct({
447 placementHints: Schema.optional(Schema.Array(RequestPlacementHintSchema)),
448 }),
449 ),
450 }),
451 EntityBaseSchema,
452);
453
454export const EncodingSpecSchema = Schema.Struct({
455 contentType: Schema.optional(Schema.String),
456 style: Schema.optional(Schema.String),
457 explode: Schema.optional(Schema.Boolean),
458 allowReserved: Schema.optional(Schema.Boolean),
459 headers: Schema.optional(Schema.Array(HeaderSymbolIdSchema)),
460});
461
462export const ContentSpecSchema = Schema.Struct({
463 mediaType: Schema.String,
464 shapeId: Schema.optional(ShapeSymbolIdSchema),
465 exampleIds: Schema.optional(Schema.Array(ExampleSymbolIdSchema)),
466 encoding: Schema.optional(
467 Schema.Record({
468 key: Schema.String,
469 value: EncodingSpecSchema,
470 }),
471 ),
472});
473
474export const ShapeSymbolSchema = Schema.extend(
475 Schema.Struct({
476 id: ShapeSymbolIdSchema,
477 kind: Schema.Literal("shape"),
478 resourceId: Schema.optional(ResourceIdSchema),
479 title: Schema.optional(Schema.String),
480 docs: Schema.optional(DocumentationBlockSchema),
481 deprecated: Schema.optional(Schema.Boolean),
482 node: ShapeNodeSchema,
483 }),
484 EntityBaseSchema,
485);
486
487export const ParameterSymbolSchema = Schema.extend(
488 Schema.Struct({
489 id: ParameterSymbolIdSchema,
490 kind: Schema.Literal("parameter"),
491 name: Schema.String,
492 location: ParameterLocationSchema,
493 required: Schema.optional(Schema.Boolean),
494 docs: Schema.optional(DocumentationBlockSchema),
495 deprecated: Schema.optional(Schema.Boolean),
496 exampleIds: Schema.optional(Schema.Array(ExampleSymbolIdSchema)),
497 schemaShapeId: Schema.optional(ShapeSymbolIdSchema),
498 content: Schema.optional(Schema.Array(ContentSpecSchema)),
499 style: Schema.optional(Schema.String),
500 explode: Schema.optional(Schema.Boolean),
501 allowReserved: Schema.optional(Schema.Boolean),
502 }),
503 EntityBaseSchema,
504);
505
506export const HeaderSymbolSchema = Schema.extend(
507 Schema.Struct({
508 id: HeaderSymbolIdSchema,
509 kind: Schema.Literal("header"),
510 name: Schema.String,
511 docs: Schema.optional(DocumentationBlockSchema),
512 deprecated: Schema.optional(Schema.Boolean),
513 exampleIds: Schema.optional(Schema.Array(ExampleSymbolIdSchema)),
514 schemaShapeId: Schema.optional(ShapeSymbolIdSchema),
515 content: Schema.optional(Schema.Array(ContentSpecSchema)),
516 style: Schema.optional(Schema.String),
517 explode: Schema.optional(Schema.Boolean),
518 }),
519 EntityBaseSchema,
520);
521
522export const RequestBodySymbolSchema = Schema.extend(
523 Schema.Struct({
524 id: RequestBodySymbolIdSchema,
525 kind: Schema.Literal("requestBody"),
526 docs: Schema.optional(DocumentationBlockSchema),
527 required: Schema.optional(Schema.Boolean),
528 contents: Schema.Array(ContentSpecSchema),
529 }),
530 EntityBaseSchema,
531);
532
533export const ResponseSymbolSchema = Schema.extend(
534 Schema.Struct({
535 id: ResponseSymbolIdSchema,
536 kind: Schema.Literal("response"),
537 docs: Schema.optional(DocumentationBlockSchema),
538 headerIds: Schema.optional(Schema.Array(HeaderSymbolIdSchema)),
539 contents: Schema.optional(Schema.Array(ContentSpecSchema)),
540 }),
541 EntityBaseSchema,
542);
543
544export const ExampleSymbolSchema = Schema.extend(
545 Schema.Struct({
546 id: ExampleSymbolIdSchema,
547 kind: Schema.Literal("example"),
548 name: Schema.optional(Schema.String),
549 docs: Schema.optional(DocumentationBlockSchema),
550 exampleKind: Schema.Literal("value", "call"),
551 value: Schema.optional(Schema.Unknown),
552 externalValue: Schema.optional(Schema.String),
553 call: Schema.optional(
554 Schema.Struct({
555 args: Schema.Record({
556 key: Schema.String,
557 value: Schema.Unknown,
558 }),
559 result: Schema.optional(Schema.Unknown),
560 }),
561 ),
562 }),
563 EntityBaseSchema,
564);
565
566export const SymbolSchema = Schema.Union(
567 ShapeSymbolSchema,
568 ParameterSymbolSchema,
569 RequestBodySymbolSchema,
570 ResponseSymbolSchema,
571 HeaderSymbolSchema,
572 ExampleSymbolSchema,
573 SecuritySchemeSymbolSchema,
574);
575
576export type AuthRequirement =
577 | {
578 readonly kind: "none";
579 }
580 | {
581 readonly kind: "scheme";
582 readonly schemeId: SecuritySchemeSymbolId;
583 readonly scopes?: readonly string[];
584 }
585 | {
586 readonly kind: "allOf";
587 readonly items: readonly AuthRequirement[];
588 }
589 | {
590 readonly kind: "anyOf";
591 readonly items: readonly AuthRequirement[];
592 };
593
594export const AuthRequirementSchema = Schema.suspend(() =>
595 Schema.Union(
596 Schema.Struct({
597 kind: Schema.Literal("none"),
598 }),
599 Schema.Struct({
600 kind: Schema.Literal("scheme"),
601 schemeId: SecuritySchemeSymbolIdSchema,
602 scopes: Schema.optional(Schema.Array(Schema.String)),
603 }),
604 Schema.Struct({
605 kind: Schema.Literal("allOf"),
606 items: Schema.Array(AuthRequirementSchema),
607 }),
608 Schema.Struct({
609 kind: Schema.Literal("anyOf"),
610 items: Schema.Array(AuthRequirementSchema),
611 }),
612 )
613) as Schema.Schema<AuthRequirement, AuthRequirement, never>;
614
615export const ServerSpecSchema = Schema.Struct({
616 url: Schema.String,
617 description: Schema.optional(Schema.String),
618 variables: Schema.optional(
619 Schema.Record({
620 key: Schema.String,
621 value: Schema.String,
622 }),
623 ),
624});
625
626export const ScopeDefaultsSchema = Schema.Struct({
627 servers: Schema.optional(Schema.Array(ServerSpecSchema)),
628 auth: Schema.optional(AuthRequirementSchema),
629 parameterIds: Schema.optional(Schema.Array(ParameterSymbolIdSchema)),
630 headerIds: Schema.optional(Schema.Array(HeaderSymbolIdSchema)),
631 variables: Schema.optional(
632 Schema.Record({
633 key: Schema.String,
634 value: Schema.String,
635 }),
636 ),
637});
638
639export const ScopeSchema = Schema.extend(
640 Schema.Struct({
641 id: ScopeIdSchema,
642 kind: ScopeKindSchema,
643 parentId: Schema.optional(ScopeIdSchema),
644 name: Schema.optional(Schema.String),
645 namespace: Schema.optional(Schema.String),
646 docs: Schema.optional(DocumentationBlockSchema),
647 defaults: Schema.optional(ScopeDefaultsSchema),
648 }),
649 EntityBaseSchema,
650);
651
652export const InteractionSpecSchema = Schema.Struct({
653 approval: Schema.Struct({
654 mayRequire: Schema.Boolean,
655 reasons: Schema.optional(
656 Schema.Array(
657 Schema.Literal("write", "delete", "sensitive", "externalSideEffect"),
658 ),
659 ),
660 }),
661 elicitation: Schema.Struct({
662 mayRequest: Schema.Boolean,
663 shapeId: Schema.optional(ShapeSymbolIdSchema),
664 }),
665 resume: Schema.Struct({
666 supported: Schema.Boolean,
667 }),
668});
669
670export const CapabilitySchema = Schema.extend(
671 Schema.Struct({
672 id: CapabilityIdSchema,
673 serviceScopeId: ScopeIdSchema,
674 surface: Schema.Struct({
675 toolPath: Schema.Array(Schema.String),
676 title: Schema.optional(Schema.String),
677 summary: Schema.optional(Schema.String),
678 description: Schema.optional(Schema.String),
679 aliases: Schema.optional(Schema.Array(Schema.String)),
680 tags: Schema.optional(Schema.Array(Schema.String)),
681 }),
682 semantics: Schema.Struct({
683 effect: EffectKindSchema,
684 safe: Schema.optional(Schema.Boolean),
685 idempotent: Schema.optional(Schema.Boolean),
686 destructive: Schema.optional(Schema.Boolean),
687 }),
688 docs: Schema.optional(DocumentationBlockSchema),
689 auth: AuthRequirementSchema,
690 interaction: InteractionSpecSchema,
691 executableIds: Schema.Array(ExecutableIdSchema),
692 preferredExecutableId: Schema.optional(ExecutableIdSchema),
693 exampleIds: Schema.optional(Schema.Array(ExampleSymbolIdSchema)),
694 }),
695 EntityBaseSchema,
696);
697
698export const HttpExecutableSchema = Schema.extend(
699 Schema.Struct({
700 id: ExecutableIdSchema,
701 protocol: Schema.Literal("http"),
702 capabilityId: CapabilityIdSchema,
703 scopeId: ScopeIdSchema,
704 method: Schema.String,
705 pathTemplate: Schema.String,
706 pathParameterIds: Schema.optional(Schema.Array(ParameterSymbolIdSchema)),
707 queryParameterIds: Schema.optional(Schema.Array(ParameterSymbolIdSchema)),
708 headerParameterIds: Schema.optional(Schema.Array(ParameterSymbolIdSchema)),
709 cookieParameterIds: Schema.optional(Schema.Array(ParameterSymbolIdSchema)),
710 requestBodyId: Schema.optional(RequestBodySymbolIdSchema),
711 responseSetId: ResponseSetIdSchema,
712 }),
713 EntityBaseSchema,
714);
715
716export const GraphQLExecutableSchema = Schema.extend(
717 Schema.Struct({
718 id: ExecutableIdSchema,
719 protocol: Schema.Literal("graphql"),
720 capabilityId: CapabilityIdSchema,
721 scopeId: ScopeIdSchema,
722 toolKind: GraphQLToolKindSchema,
723 operationType: Schema.Literal("query", "mutation", "subscription"),
724 rootField: Schema.String,
725 operationName: Schema.optional(Schema.String),
726 operationDocument: Schema.optional(Schema.String),
727 argumentShapeId: ShapeSymbolIdSchema,
728 resultShapeId: ShapeSymbolIdSchema,
729 selectionShapeId: Schema.optional(ShapeSymbolIdSchema),
730 selectionMode: SelectionModeSchema,
731 responseSetId: ResponseSetIdSchema,
732 }),
733 EntityBaseSchema,
734);
735
736export const McpExecutableSchema = Schema.extend(
737 Schema.Struct({
738 id: ExecutableIdSchema,
739 protocol: Schema.Literal("mcp"),
740 capabilityId: CapabilityIdSchema,
741 scopeId: ScopeIdSchema,
742 serverRef: Schema.String,
743 toolName: Schema.String,
744 inputShapeId: Schema.optional(ShapeSymbolIdSchema),
745 outputShapeId: Schema.optional(ShapeSymbolIdSchema),
746 responseSetId: ResponseSetIdSchema,
747 }),
748 EntityBaseSchema,
749);
750
751export const ExecutableSchema = Schema.Union(
752 HttpExecutableSchema,
753 GraphQLExecutableSchema,
754 McpExecutableSchema,
755);
756
757export const PaginationHintSchema = Schema.Struct({
758 kind: PaginationHintKindSchema,
759 tokenParamName: Schema.optional(Schema.String),
760 nextFieldPath: Schema.optional(Schema.String),
761});
762
763export const StatusMatchSchema = Schema.Union(
764 Schema.Struct({
765 kind: Schema.Literal("exact"),
766 status: Schema.Number,
767 }),
768 Schema.Struct({
769 kind: Schema.Literal("range"),
770 value: StatusRangeSchema,
771 }),
772 Schema.Struct({
773 kind: Schema.Literal("default"),
774 }),
775);
776
777export const ResponseVariantSchema = Schema.Struct({
778 match: StatusMatchSchema,
779 responseId: ResponseSymbolIdSchema,
780 traits: Schema.optional(Schema.Array(ResponseTraitSchema)),
781 pagination: Schema.optional(PaginationHintSchema),
782});
783
784export const ResponseSetSchema = Schema.extend(
785 Schema.Struct({
786 id: ResponseSetIdSchema,
787 variants: Schema.Array(ResponseVariantSchema),
788 }),
789 EntityBaseSchema,
790);
791
792export const ImportDiagnosticSchema = Schema.Struct({
793 id: DiagnosticIdSchema,
794 level: DiagnosticLevelSchema,
795 code: DiagnosticCodeSchema,
796 message: Schema.String,
797 relatedSymbolIds: Schema.optional(Schema.Array(SymbolIdSchema)),
798 provenance: Schema.Array(ProvenanceRefSchema),
799});
800
801export const CatalogV1Schema = Schema.Struct({
802 version: Schema.Literal("ir.v1"),
803 documents: Schema.Record({
804 key: DocumentIdSchema,
805 value: SourceDocumentSchema,
806 }),
807 resources: Schema.Record({
808 key: ResourceIdSchema,
809 value: SchemaResourceSchema,
810 }),
811 scopes: Schema.Record({
812 key: ScopeIdSchema,
813 value: ScopeSchema,
814 }),
815 symbols: Schema.Record({
816 key: Schema.String,
817 value: SymbolSchema,
818 }),
819 capabilities: Schema.Record({
820 key: CapabilityIdSchema,
821 value: CapabilitySchema,
822 }),
823 executables: Schema.Record({
824 key: ExecutableIdSchema,
825 value: ExecutableSchema,
826 }),
827 responseSets: Schema.Record({
828 key: ResponseSetIdSchema,
829 value: ResponseSetSchema,
830 }),
831 diagnostics: Schema.Record({
832 key: DiagnosticIdSchema,
833 value: ImportDiagnosticSchema,
834 }),
835});
836
837export const CatalogFragmentV1Schema = Schema.Struct({
838 version: Schema.Literal("ir.v1.fragment"),
839 documents: Schema.optional(
840 Schema.Record({
841 key: DocumentIdSchema,
842 value: SourceDocumentSchema,
843 }),
844 ),
845 resources: Schema.optional(
846 Schema.Record({
847 key: ResourceIdSchema,
848 value: SchemaResourceSchema,
849 }),
850 ),
851 scopes: Schema.optional(
852 Schema.Record({
853 key: ScopeIdSchema,
854 value: ScopeSchema,
855 }),
856 ),
857 symbols: Schema.optional(
858 Schema.Record({
859 key: Schema.String,
860 value: SymbolSchema,
861 }),
862 ),
863 capabilities: Schema.optional(
864 Schema.Record({
865 key: CapabilityIdSchema,
866 value: CapabilitySchema,
867 }),
868 ),
869 executables: Schema.optional(
870 Schema.Record({
871 key: ExecutableIdSchema,
872 value: ExecutableSchema,
873 }),
874 ),
875 responseSets: Schema.optional(
876 Schema.Record({
877 key: ResponseSetIdSchema,
878 value: ResponseSetSchema,
879 }),
880 ),
881 diagnostics: Schema.optional(
882 Schema.Record({
883 key: DiagnosticIdSchema,
884 value: ImportDiagnosticSchema,
885 }),
886 ),
887});
888
889export const CatalogSnapshotV1Schema = Schema.Struct({
890 version: Schema.Literal("ir.v1.snapshot"),
891 import: ImportMetadataSchema,
892 catalog: CatalogV1Schema,
893});
894
895export type SourceKind = typeof SourceKindSchema.Type;
896export type ScopeKind = typeof ScopeKindSchema.Type;
897export type EffectKind = typeof EffectKindSchema.Type;
898export type ParameterLocation = typeof ParameterLocationSchema.Type;
899export type ResponseTrait = typeof ResponseTraitSchema.Type;
900export type SecuritySchemeType = typeof SecuritySchemeTypeSchema.Type;
901export type SelectionMode = typeof SelectionModeSchema.Type;
902export type GraphQLToolKind = typeof GraphQLToolKindSchema.Type;
903export type PaginationHintKind = typeof PaginationHintKindSchema.Type;
904export type DiagnosticCode = typeof DiagnosticCodeSchema.Type;
905export type DiagnosticLevel = typeof DiagnosticLevelSchema.Type;
906
907export type DocumentationBlock = typeof DocumentationBlockSchema.Type;
908export type ProvenanceRef = typeof ProvenanceRefSchema.Type;
909export type NativeBlob = typeof NativeBlobSchema.Type;
910export type EntityBase = typeof EntityBaseSchema.Type;
911export type ImportMetadata = typeof ImportMetadataSchema.Type;
912export type SourceDocument = typeof SourceDocumentSchema.Type;
913export type FieldSpec = typeof FieldSpecSchema.Type;
914export type DiscriminatorSpec = typeof DiscriminatorSpecSchema.Type;
915export type ScalarShape = typeof ScalarShapeSchema.Type;
916export type UnknownShape = typeof UnknownShapeSchema.Type;
917export type ConstShape = typeof ConstShapeSchema.Type;
918export type EnumShape = typeof EnumShapeSchema.Type;
919export type ObjectShape = typeof ObjectShapeSchema.Type;
920export type ArrayShape = typeof ArrayShapeSchema.Type;
921export type TupleShape = typeof TupleShapeSchema.Type;
922export type MapShape = typeof MapShapeSchema.Type;
923export type AllOfShape = typeof AllOfShapeSchema.Type;
924export type AnyOfShape = typeof AnyOfShapeSchema.Type;
925export type OneOfShape = typeof OneOfShapeSchema.Type;
926export type NullableShape = typeof NullableShapeSchema.Type;
927export type RefShape = typeof RefShapeSchema.Type;
928export type NotShape = typeof NotShapeSchema.Type;
929export type ConditionalShape = typeof ConditionalShapeSchema.Type;
930export type GraphQLInterfaceShape = typeof GraphQLInterfaceShapeSchema.Type;
931export type GraphQLUnionShape = typeof GraphQLUnionShapeSchema.Type;
932export type ShapeNode = typeof ShapeNodeSchema.Type;
933export type AnchorTarget = typeof AnchorTargetSchema.Type;
934export type SchemaResource = typeof SchemaResourceSchema.Type;
935export type RequestPlacementHint = typeof RequestPlacementHintSchema.Type;
936export type OAuthFlow = typeof OAuthFlowSchema.Type;
937export type SecurityPlacement = typeof SecurityPlacementSchema.Type;
938export type SecuritySchemeSymbol = typeof SecuritySchemeSymbolSchema.Type;
939export type EncodingSpec = typeof EncodingSpecSchema.Type;
940export type ContentSpec = typeof ContentSpecSchema.Type;
941export type ShapeSymbol = typeof ShapeSymbolSchema.Type;
942export type ParameterSymbol = typeof ParameterSymbolSchema.Type;
943export type HeaderSymbol = typeof HeaderSymbolSchema.Type;
944export type RequestBodySymbol = typeof RequestBodySymbolSchema.Type;
945export type ResponseSymbol = typeof ResponseSymbolSchema.Type;
946export type ExampleSymbol = typeof ExampleSymbolSchema.Type;
947export type Symbol =
948 | ShapeSymbol
949 | ParameterSymbol
950 | RequestBodySymbol
951 | ResponseSymbol
952 | HeaderSymbol
953 | ExampleSymbol
954 | SecuritySchemeSymbol;
955export type ServerSpec = typeof ServerSpecSchema.Type;
956export type ScopeDefaults = typeof ScopeDefaultsSchema.Type;
957export type Scope = typeof ScopeSchema.Type;
958export type InteractionSpec = typeof InteractionSpecSchema.Type;
959export type Capability = typeof CapabilitySchema.Type;
960export type HttpExecutable = typeof HttpExecutableSchema.Type;
961export type GraphQLExecutable = typeof GraphQLExecutableSchema.Type;
962export type McpExecutable = typeof McpExecutableSchema.Type;
963export type Executable = typeof ExecutableSchema.Type;
964export type PaginationHint = typeof PaginationHintSchema.Type;
965export type StatusMatch = typeof StatusMatchSchema.Type;
966export type ResponseVariant = typeof ResponseVariantSchema.Type;
967export type ResponseSet = typeof ResponseSetSchema.Type;
968export type ImportDiagnostic = typeof ImportDiagnosticSchema.Type;
969export type CatalogV1 = typeof CatalogV1Schema.Type;
970export type CatalogFragmentV1 = typeof CatalogFragmentV1Schema.Type;
971export type CatalogSnapshotV1 = typeof CatalogSnapshotV1Schema.Type;
packages/kernel/ir/tsconfig.json+15−0
@@ -0,0 +1,15 @@
1{
2 "compilerOptions": {
3 "target": "ES2022",
4 "module": "ESNext",
5 "moduleResolution": "Bundler",
6 "strict": true,
7 "skipLibCheck": true,
8 "noEmit": true,
9 "lib": ["ES2022"],
10 "types": ["bun-types", "node"]
11 },
12 "include": [
13 "src/**/*.ts"
14 ]
15}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t