History

match sketch

Rhys Sullivan committed 8 months ago

Commit 3934a4e1 parent d029a7917 files · +5667 −647Browse files

Unified · Split

17 files
  1. packages/control-plane/drizzle/20260307090915_blue_bullseye/migration.sql +70 −0
  2. packages/control-plane/drizzle/20260307090915_blue_bullseye/snapshot.json +3122 −0
  3. packages/control-plane/src/persistence/index.ts +2 −0
  4. packages/control-plane/src/persistence/repos/index.ts +1 −0
  5. packages/control-plane/src/persistence/repos/sources-repo.ts +61 −1
  6. packages/control-plane/src/persistence/repos/tool-artifacts-repo.ts +441 −0
  7. packages/control-plane/src/persistence/schema.ts +139 −0
  8. packages/control-plane/src/runtime/source-auth-service.ts +79 −14
  9. packages/control-plane/src/runtime/sources-operations.ts +92 −6
  10. packages/control-plane/src/runtime/tool-artifacts.ts +393 −0
  11. packages/control-plane/src/runtime/workspace-execution-environment.test.ts +410 −0
  12. packages/control-plane/src/runtime/workspace-execution-environment.ts +433 −216
  13. packages/control-plane/src/schema/index.ts +1 −0
  14. packages/control-plane/src/schema/models/tool-artifact.ts +159 −0
  15. packages/kitchen-sink/package.json +1 −0
  16. packages/kitchen-sink/src/source-runtime-sketch-types.ts +0 −128
  17. packages/kitchen-sink/src/source-runtime.test.ts +263 −282
packages/control-plane/drizzle/20260307090915_blue_bullseye/migration.sql+70−0
@@ -0,0 +1,70 @@
1CREATE TABLE "tool_artifact_parameters" (
2 "workspace_id" text,
3 "path" text,
4 "position" bigint,
5 "name" text NOT NULL,
6 "location" text NOT NULL,
7 "required" boolean NOT NULL,
8 CONSTRAINT "tool_artifact_parameters_pkey" PRIMARY KEY("workspace_id","path","position"),
9 CONSTRAINT "tool_artifact_parameters_location_check" CHECK ("location" in ('path', 'query', 'header', 'cookie'))
10);
11--> statement-breakpoint
12CREATE TABLE "tool_artifact_ref_hint_keys" (
13 "workspace_id" text,
14 "path" text,
15 "position" bigint,
16 "ref_hint_key" text NOT NULL,
17 CONSTRAINT "tool_artifact_ref_hint_keys_pkey" PRIMARY KEY("workspace_id","path","position")
18);
19--> statement-breakpoint
20CREATE TABLE "tool_artifact_request_body_content_types" (
21 "workspace_id" text,
22 "path" text,
23 "position" bigint,
24 "content_type" text NOT NULL,
25 CONSTRAINT "tool_artifact_request_body_content_types_pkey" PRIMARY KEY("workspace_id","path","position")
26);
27--> statement-breakpoint
28CREATE TABLE "tool_artifacts" (
29 "workspace_id" text,
30 "path" text,
31 "tool_id" text NOT NULL,
32 "source_id" text NOT NULL,
33 "title" text,
34 "description" text,
35 "search_namespace" text NOT NULL,
36 "search_text" text NOT NULL,
37 "input_schema_json" text,
38 "output_schema_json" text,
39 "provider_kind" text NOT NULL,
40 "mcp_tool_name" text,
41 "openapi_method" text,
42 "openapi_path_template" text,
43 "openapi_operation_hash" text,
44 "openapi_request_body_required" boolean,
45 "created_at" bigint NOT NULL,
46 "updated_at" bigint NOT NULL,
47 CONSTRAINT "tool_artifacts_pkey" PRIMARY KEY("workspace_id","path"),
48 CONSTRAINT "tool_artifacts_provider_kind_check" CHECK ("provider_kind" in ('mcp', 'openapi')),
49 CONSTRAINT "tool_artifacts_mcp_shape_check" CHECK ("provider_kind" <> 'mcp'
50 or (
51 "mcp_tool_name" is not null
52 and "openapi_method" is null
53 and "openapi_path_template" is null
54 and "openapi_operation_hash" is null
55 and "openapi_request_body_required" is null
56 )),
57 CONSTRAINT "tool_artifacts_openapi_shape_check" CHECK ("provider_kind" <> 'openapi'
58 or (
59 "mcp_tool_name" is null
60 and "openapi_method" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
61 and "openapi_path_template" is not null
62 and "openapi_operation_hash" is not null
63 ))
64);
65--> statement-breakpoint
66CREATE INDEX "tool_artifact_parameters_lookup_idx" ON "tool_artifact_parameters" ("workspace_id","path","position");--> statement-breakpoint
67CREATE INDEX "tool_artifact_ref_hint_keys_lookup_idx" ON "tool_artifact_ref_hint_keys" ("workspace_id","path","position");--> statement-breakpoint
68CREATE INDEX "tool_artifact_request_body_content_types_lookup_idx" ON "tool_artifact_request_body_content_types" ("workspace_id","path","position");--> statement-breakpoint
69CREATE INDEX "tool_artifacts_workspace_source_idx" ON "tool_artifacts" ("workspace_id","source_id","updated_at","path");--> statement-breakpoint
70CREATE INDEX "tool_artifacts_workspace_namespace_idx" ON "tool_artifacts" ("workspace_id","search_namespace","path");
packages/control-plane/drizzle/20260307090915_blue_bullseye/snapshot.json+3122−0
@@ -0,0 +1,3122 @@
1{
2 "version": "8",
3 "dialect": "postgres",
4 "id": "3e9aa0ec-194c-4364-9e57-4539db8fb2ad",
5 "prevIds": [
6 "1141497b-c500-44ff-87db-6a440062becb"
7 ],
8 "ddl": [
9 {
10 "isRlsEnabled": false,
11 "name": "accounts",
12 "entityType": "tables",
13 "schema": "public"
14 },
15 {
16 "isRlsEnabled": false,
17 "name": "execution_interactions",
18 "entityType": "tables",
19 "schema": "public"
20 },
21 {
22 "isRlsEnabled": false,
23 "name": "executions",
24 "entityType": "tables",
25 "schema": "public"
26 },
27 {
28 "isRlsEnabled": false,
29 "name": "local_installations",
30 "entityType": "tables",
31 "schema": "public"
32 },
33 {
34 "isRlsEnabled": false,
35 "name": "organization_memberships",
36 "entityType": "tables",
37 "schema": "public"
38 },
39 {
40 "isRlsEnabled": false,
41 "name": "organizations",
42 "entityType": "tables",
43 "schema": "public"
44 },
45 {
46 "isRlsEnabled": false,
47 "name": "policies",
48 "entityType": "tables",
49 "schema": "public"
50 },
51 {
52 "isRlsEnabled": false,
53 "name": "secret_materials",
54 "entityType": "tables",
55 "schema": "public"
56 },
57 {
58 "isRlsEnabled": false,
59 "name": "source_auth_sessions",
60 "entityType": "tables",
61 "schema": "public"
62 },
63 {
64 "isRlsEnabled": false,
65 "name": "source_credential_bindings",
66 "entityType": "tables",
67 "schema": "public"
68 },
69 {
70 "isRlsEnabled": false,
71 "name": "sources",
72 "entityType": "tables",
73 "schema": "public"
74 },
75 {
76 "isRlsEnabled": false,
77 "name": "tool_artifact_parameters",
78 "entityType": "tables",
79 "schema": "public"
80 },
81 {
82 "isRlsEnabled": false,
83 "name": "tool_artifact_ref_hint_keys",
84 "entityType": "tables",
85 "schema": "public"
86 },
87 {
88 "isRlsEnabled": false,
89 "name": "tool_artifact_request_body_content_types",
90 "entityType": "tables",
91 "schema": "public"
92 },
93 {
94 "isRlsEnabled": false,
95 "name": "tool_artifacts",
96 "entityType": "tables",
97 "schema": "public"
98 },
99 {
100 "isRlsEnabled": false,
101 "name": "workspaces",
102 "entityType": "tables",
103 "schema": "public"
104 },
105 {
106 "type": "text",
107 "typeSchema": null,
108 "notNull": true,
109 "dimensions": 0,
110 "default": null,
111 "generated": null,
112 "identity": null,
113 "name": "id",
114 "entityType": "columns",
115 "schema": "public",
116 "table": "accounts"
117 },
118 {
119 "type": "text",
120 "typeSchema": null,
121 "notNull": true,
122 "dimensions": 0,
123 "default": null,
124 "generated": null,
125 "identity": null,
126 "name": "provider",
127 "entityType": "columns",
128 "schema": "public",
129 "table": "accounts"
130 },
131 {
132 "type": "text",
133 "typeSchema": null,
134 "notNull": true,
135 "dimensions": 0,
136 "default": null,
137 "generated": null,
138 "identity": null,
139 "name": "subject",
140 "entityType": "columns",
141 "schema": "public",
142 "table": "accounts"
143 },
144 {
145 "type": "text",
146 "typeSchema": null,
147 "notNull": false,
148 "dimensions": 0,
149 "default": null,
150 "generated": null,
151 "identity": null,
152 "name": "email",
153 "entityType": "columns",
154 "schema": "public",
155 "table": "accounts"
156 },
157 {
158 "type": "text",
159 "typeSchema": null,
160 "notNull": false,
161 "dimensions": 0,
162 "default": null,
163 "generated": null,
164 "identity": null,
165 "name": "display_name",
166 "entityType": "columns",
167 "schema": "public",
168 "table": "accounts"
169 },
170 {
171 "type": "bigint",
172 "typeSchema": null,
173 "notNull": true,
174 "dimensions": 0,
175 "default": null,
176 "generated": null,
177 "identity": null,
178 "name": "created_at",
179 "entityType": "columns",
180 "schema": "public",
181 "table": "accounts"
182 },
183 {
184 "type": "bigint",
185 "typeSchema": null,
186 "notNull": true,
187 "dimensions": 0,
188 "default": null,
189 "generated": null,
190 "identity": null,
191 "name": "updated_at",
192 "entityType": "columns",
193 "schema": "public",
194 "table": "accounts"
195 },
196 {
197 "type": "text",
198 "typeSchema": null,
199 "notNull": true,
200 "dimensions": 0,
201 "default": null,
202 "generated": null,
203 "identity": null,
204 "name": "id",
205 "entityType": "columns",
206 "schema": "public",
207 "table": "execution_interactions"
208 },
209 {
210 "type": "text",
211 "typeSchema": null,
212 "notNull": true,
213 "dimensions": 0,
214 "default": null,
215 "generated": null,
216 "identity": null,
217 "name": "execution_id",
218 "entityType": "columns",
219 "schema": "public",
220 "table": "execution_interactions"
221 },
222 {
223 "type": "text",
224 "typeSchema": null,
225 "notNull": true,
226 "dimensions": 0,
227 "default": null,
228 "generated": null,
229 "identity": null,
230 "name": "status",
231 "entityType": "columns",
232 "schema": "public",
233 "table": "execution_interactions"
234 },
235 {
236 "type": "text",
237 "typeSchema": null,
238 "notNull": true,
239 "dimensions": 0,
240 "default": null,
241 "generated": null,
242 "identity": null,
243 "name": "kind",
244 "entityType": "columns",
245 "schema": "public",
246 "table": "execution_interactions"
247 },
248 {
249 "type": "text",
250 "typeSchema": null,
251 "notNull": true,
252 "dimensions": 0,
253 "default": null,
254 "generated": null,
255 "identity": null,
256 "name": "payload_json",
257 "entityType": "columns",
258 "schema": "public",
259 "table": "execution_interactions"
260 },
261 {
262 "type": "text",
263 "typeSchema": null,
264 "notNull": false,
265 "dimensions": 0,
266 "default": null,
267 "generated": null,
268 "identity": null,
269 "name": "response_json",
270 "entityType": "columns",
271 "schema": "public",
272 "table": "execution_interactions"
273 },
274 {
275 "type": "bigint",
276 "typeSchema": null,
277 "notNull": true,
278 "dimensions": 0,
279 "default": null,
280 "generated": null,
281 "identity": null,
282 "name": "created_at",
283 "entityType": "columns",
284 "schema": "public",
285 "table": "execution_interactions"
286 },
287 {
288 "type": "bigint",
289 "typeSchema": null,
290 "notNull": true,
291 "dimensions": 0,
292 "default": null,
293 "generated": null,
294 "identity": null,
295 "name": "updated_at",
296 "entityType": "columns",
297 "schema": "public",
298 "table": "execution_interactions"
299 },
300 {
301 "type": "text",
302 "typeSchema": null,
303 "notNull": true,
304 "dimensions": 0,
305 "default": null,
306 "generated": null,
307 "identity": null,
308 "name": "id",
309 "entityType": "columns",
310 "schema": "public",
311 "table": "executions"
312 },
313 {
314 "type": "text",
315 "typeSchema": null,
316 "notNull": true,
317 "dimensions": 0,
318 "default": null,
319 "generated": null,
320 "identity": null,
321 "name": "workspace_id",
322 "entityType": "columns",
323 "schema": "public",
324 "table": "executions"
325 },
326 {
327 "type": "text",
328 "typeSchema": null,
329 "notNull": true,
330 "dimensions": 0,
331 "default": null,
332 "generated": null,
333 "identity": null,
334 "name": "created_by_account_id",
335 "entityType": "columns",
336 "schema": "public",
337 "table": "executions"
338 },
339 {
340 "type": "text",
341 "typeSchema": null,
342 "notNull": true,
343 "dimensions": 0,
344 "default": null,
345 "generated": null,
346 "identity": null,
347 "name": "status",
348 "entityType": "columns",
349 "schema": "public",
350 "table": "executions"
351 },
352 {
353 "type": "text",
354 "typeSchema": null,
355 "notNull": true,
356 "dimensions": 0,
357 "default": null,
358 "generated": null,
359 "identity": null,
360 "name": "code",
361 "entityType": "columns",
362 "schema": "public",
363 "table": "executions"
364 },
365 {
366 "type": "text",
367 "typeSchema": null,
368 "notNull": false,
369 "dimensions": 0,
370 "default": null,
371 "generated": null,
372 "identity": null,
373 "name": "result_json",
374 "entityType": "columns",
375 "schema": "public",
376 "table": "executions"
377 },
378 {
379 "type": "text",
380 "typeSchema": null,
381 "notNull": false,
382 "dimensions": 0,
383 "default": null,
384 "generated": null,
385 "identity": null,
386 "name": "error_text",
387 "entityType": "columns",
388 "schema": "public",
389 "table": "executions"
390 },
391 {
392 "type": "text",
393 "typeSchema": null,
394 "notNull": false,
395 "dimensions": 0,
396 "default": null,
397 "generated": null,
398 "identity": null,
399 "name": "logs_json",
400 "entityType": "columns",
401 "schema": "public",
402 "table": "executions"
403 },
404 {
405 "type": "bigint",
406 "typeSchema": null,
407 "notNull": false,
408 "dimensions": 0,
409 "default": null,
410 "generated": null,
411 "identity": null,
412 "name": "started_at",
413 "entityType": "columns",
414 "schema": "public",
415 "table": "executions"
416 },
417 {
418 "type": "bigint",
419 "typeSchema": null,
420 "notNull": false,
421 "dimensions": 0,
422 "default": null,
423 "generated": null,
424 "identity": null,
425 "name": "completed_at",
426 "entityType": "columns",
427 "schema": "public",
428 "table": "executions"
429 },
430 {
431 "type": "bigint",
432 "typeSchema": null,
433 "notNull": true,
434 "dimensions": 0,
435 "default": null,
436 "generated": null,
437 "identity": null,
438 "name": "created_at",
439 "entityType": "columns",
440 "schema": "public",
441 "table": "executions"
442 },
443 {
444 "type": "bigint",
445 "typeSchema": null,
446 "notNull": true,
447 "dimensions": 0,
448 "default": null,
449 "generated": null,
450 "identity": null,
451 "name": "updated_at",
452 "entityType": "columns",
453 "schema": "public",
454 "table": "executions"
455 },
456 {
457 "type": "text",
458 "typeSchema": null,
459 "notNull": true,
460 "dimensions": 0,
461 "default": null,
462 "generated": null,
463 "identity": null,
464 "name": "id",
465 "entityType": "columns",
466 "schema": "public",
467 "table": "local_installations"
468 },
469 {
470 "type": "text",
471 "typeSchema": null,
472 "notNull": true,
473 "dimensions": 0,
474 "default": null,
475 "generated": null,
476 "identity": null,
477 "name": "account_id",
478 "entityType": "columns",
479 "schema": "public",
480 "table": "local_installations"
481 },
482 {
483 "type": "text",
484 "typeSchema": null,
485 "notNull": true,
486 "dimensions": 0,
487 "default": null,
488 "generated": null,
489 "identity": null,
490 "name": "organization_id",
491 "entityType": "columns",
492 "schema": "public",
493 "table": "local_installations"
494 },
495 {
496 "type": "text",
497 "typeSchema": null,
498 "notNull": true,
499 "dimensions": 0,
500 "default": null,
501 "generated": null,
502 "identity": null,
503 "name": "workspace_id",
504 "entityType": "columns",
505 "schema": "public",
506 "table": "local_installations"
507 },
508 {
509 "type": "bigint",
510 "typeSchema": null,
511 "notNull": true,
512 "dimensions": 0,
513 "default": null,
514 "generated": null,
515 "identity": null,
516 "name": "created_at",
517 "entityType": "columns",
518 "schema": "public",
519 "table": "local_installations"
520 },
521 {
522 "type": "bigint",
523 "typeSchema": null,
524 "notNull": true,
525 "dimensions": 0,
526 "default": null,
527 "generated": null,
528 "identity": null,
529 "name": "updated_at",
530 "entityType": "columns",
531 "schema": "public",
532 "table": "local_installations"
533 },
534 {
535 "type": "text",
536 "typeSchema": null,
537 "notNull": true,
538 "dimensions": 0,
539 "default": null,
540 "generated": null,
541 "identity": null,
542 "name": "id",
543 "entityType": "columns",
544 "schema": "public",
545 "table": "organization_memberships"
546 },
547 {
548 "type": "text",
549 "typeSchema": null,
550 "notNull": true,
551 "dimensions": 0,
552 "default": null,
553 "generated": null,
554 "identity": null,
555 "name": "organization_id",
556 "entityType": "columns",
557 "schema": "public",
558 "table": "organization_memberships"
559 },
560 {
561 "type": "text",
562 "typeSchema": null,
563 "notNull": true,
564 "dimensions": 0,
565 "default": null,
566 "generated": null,
567 "identity": null,
568 "name": "account_id",
569 "entityType": "columns",
570 "schema": "public",
571 "table": "organization_memberships"
572 },
573 {
574 "type": "text",
575 "typeSchema": null,
576 "notNull": true,
577 "dimensions": 0,
578 "default": null,
579 "generated": null,
580 "identity": null,
581 "name": "role",
582 "entityType": "columns",
583 "schema": "public",
584 "table": "organization_memberships"
585 },
586 {
587 "type": "text",
588 "typeSchema": null,
589 "notNull": true,
590 "dimensions": 0,
591 "default": null,
592 "generated": null,
593 "identity": null,
594 "name": "status",
595 "entityType": "columns",
596 "schema": "public",
597 "table": "organization_memberships"
598 },
599 {
600 "type": "boolean",
601 "typeSchema": null,
602 "notNull": true,
603 "dimensions": 0,
604 "default": null,
605 "generated": null,
606 "identity": null,
607 "name": "billable",
608 "entityType": "columns",
609 "schema": "public",
610 "table": "organization_memberships"
611 },
612 {
613 "type": "text",
614 "typeSchema": null,
615 "notNull": false,
616 "dimensions": 0,
617 "default": null,
618 "generated": null,
619 "identity": null,
620 "name": "invited_by_account_id",
621 "entityType": "columns",
622 "schema": "public",
623 "table": "organization_memberships"
624 },
625 {
626 "type": "bigint",
627 "typeSchema": null,
628 "notNull": false,
629 "dimensions": 0,
630 "default": null,
631 "generated": null,
632 "identity": null,
633 "name": "joined_at",
634 "entityType": "columns",
635 "schema": "public",
636 "table": "organization_memberships"
637 },
638 {
639 "type": "bigint",
640 "typeSchema": null,
641 "notNull": true,
642 "dimensions": 0,
643 "default": null,
644 "generated": null,
645 "identity": null,
646 "name": "created_at",
647 "entityType": "columns",
648 "schema": "public",
649 "table": "organization_memberships"
650 },
651 {
652 "type": "bigint",
653 "typeSchema": null,
654 "notNull": true,
655 "dimensions": 0,
656 "default": null,
657 "generated": null,
658 "identity": null,
659 "name": "updated_at",
660 "entityType": "columns",
661 "schema": "public",
662 "table": "organization_memberships"
663 },
664 {
665 "type": "text",
666 "typeSchema": null,
667 "notNull": true,
668 "dimensions": 0,
669 "default": null,
670 "generated": null,
671 "identity": null,
672 "name": "id",
673 "entityType": "columns",
674 "schema": "public",
675 "table": "organizations"
676 },
677 {
678 "type": "text",
679 "typeSchema": null,
680 "notNull": true,
681 "dimensions": 0,
682 "default": null,
683 "generated": null,
684 "identity": null,
685 "name": "slug",
686 "entityType": "columns",
687 "schema": "public",
688 "table": "organizations"
689 },
690 {
691 "type": "text",
692 "typeSchema": null,
693 "notNull": true,
694 "dimensions": 0,
695 "default": null,
696 "generated": null,
697 "identity": null,
698 "name": "name",
699 "entityType": "columns",
700 "schema": "public",
701 "table": "organizations"
702 },
703 {
704 "type": "text",
705 "typeSchema": null,
706 "notNull": true,
707 "dimensions": 0,
708 "default": null,
709 "generated": null,
710 "identity": null,
711 "name": "status",
712 "entityType": "columns",
713 "schema": "public",
714 "table": "organizations"
715 },
716 {
717 "type": "text",
718 "typeSchema": null,
719 "notNull": false,
720 "dimensions": 0,
721 "default": null,
722 "generated": null,
723 "identity": null,
724 "name": "created_by_account_id",
725 "entityType": "columns",
726 "schema": "public",
727 "table": "organizations"
728 },
729 {
730 "type": "bigint",
731 "typeSchema": null,
732 "notNull": true,
733 "dimensions": 0,
734 "default": null,
735 "generated": null,
736 "identity": null,
737 "name": "created_at",
738 "entityType": "columns",
739 "schema": "public",
740 "table": "organizations"
741 },
742 {
743 "type": "bigint",
744 "typeSchema": null,
745 "notNull": true,
746 "dimensions": 0,
747 "default": null,
748 "generated": null,
749 "identity": null,
750 "name": "updated_at",
751 "entityType": "columns",
752 "schema": "public",
753 "table": "organizations"
754 },
755 {
756 "type": "text",
757 "typeSchema": null,
758 "notNull": true,
759 "dimensions": 0,
760 "default": null,
761 "generated": null,
762 "identity": null,
763 "name": "id",
764 "entityType": "columns",
765 "schema": "public",
766 "table": "policies"
767 },
768 {
769 "type": "text",
770 "typeSchema": null,
771 "notNull": true,
772 "dimensions": 0,
773 "default": null,
774 "generated": null,
775 "identity": null,
776 "name": "workspace_id",
777 "entityType": "columns",
778 "schema": "public",
779 "table": "policies"
780 },
781 {
782 "type": "text",
783 "typeSchema": null,
784 "notNull": false,
785 "dimensions": 0,
786 "default": null,
787 "generated": null,
788 "identity": null,
789 "name": "target_account_id",
790 "entityType": "columns",
791 "schema": "public",
792 "table": "policies"
793 },
794 {
795 "type": "text",
796 "typeSchema": null,
797 "notNull": false,
798 "dimensions": 0,
799 "default": null,
800 "generated": null,
801 "identity": null,
802 "name": "client_id",
803 "entityType": "columns",
804 "schema": "public",
805 "table": "policies"
806 },
807 {
808 "type": "text",
809 "typeSchema": null,
810 "notNull": true,
811 "dimensions": 0,
812 "default": null,
813 "generated": null,
814 "identity": null,
815 "name": "resource_type",
816 "entityType": "columns",
817 "schema": "public",
818 "table": "policies"
819 },
820 {
821 "type": "text",
822 "typeSchema": null,
823 "notNull": true,
824 "dimensions": 0,
825 "default": null,
826 "generated": null,
827 "identity": null,
828 "name": "resource_pattern",
829 "entityType": "columns",
830 "schema": "public",
831 "table": "policies"
832 },
833 {
834 "type": "text",
835 "typeSchema": null,
836 "notNull": true,
837 "dimensions": 0,
838 "default": null,
839 "generated": null,
840 "identity": null,
841 "name": "match_type",
842 "entityType": "columns",
843 "schema": "public",
844 "table": "policies"
845 },
846 {
847 "type": "text",
848 "typeSchema": null,
849 "notNull": true,
850 "dimensions": 0,
851 "default": null,
852 "generated": null,
853 "identity": null,
854 "name": "effect",
855 "entityType": "columns",
856 "schema": "public",
857 "table": "policies"
858 },
859 {
860 "type": "text",
861 "typeSchema": null,
862 "notNull": true,
863 "dimensions": 0,
864 "default": null,
865 "generated": null,
866 "identity": null,
867 "name": "approval_mode",
868 "entityType": "columns",
869 "schema": "public",
870 "table": "policies"
871 },
872 {
873 "type": "text",
874 "typeSchema": null,
875 "notNull": false,
876 "dimensions": 0,
877 "default": null,
878 "generated": null,
879 "identity": null,
880 "name": "argument_conditions_json",
881 "entityType": "columns",
882 "schema": "public",
883 "table": "policies"
884 },
885 {
886 "type": "bigint",
887 "typeSchema": null,
888 "notNull": true,
889 "dimensions": 0,
890 "default": null,
891 "generated": null,
892 "identity": null,
893 "name": "priority",
894 "entityType": "columns",
895 "schema": "public",
896 "table": "policies"
897 },
898 {
899 "type": "boolean",
900 "typeSchema": null,
901 "notNull": true,
902 "dimensions": 0,
903 "default": null,
904 "generated": null,
905 "identity": null,
906 "name": "enabled",
907 "entityType": "columns",
908 "schema": "public",
909 "table": "policies"
910 },
911 {
912 "type": "bigint",
913 "typeSchema": null,
914 "notNull": true,
915 "dimensions": 0,
916 "default": null,
917 "generated": null,
918 "identity": null,
919 "name": "created_at",
920 "entityType": "columns",
921 "schema": "public",
922 "table": "policies"
923 },
924 {
925 "type": "bigint",
926 "typeSchema": null,
927 "notNull": true,
928 "dimensions": 0,
929 "default": null,
930 "generated": null,
931 "identity": null,
932 "name": "updated_at",
933 "entityType": "columns",
934 "schema": "public",
935 "table": "policies"
936 },
937 {
938 "type": "text",
939 "typeSchema": null,
940 "notNull": true,
941 "dimensions": 0,
942 "default": null,
943 "generated": null,
944 "identity": null,
945 "name": "id",
946 "entityType": "columns",
947 "schema": "public",
948 "table": "secret_materials"
949 },
950 {
951 "type": "text",
952 "typeSchema": null,
953 "notNull": true,
954 "dimensions": 0,
955 "default": null,
956 "generated": null,
957 "identity": null,
958 "name": "purpose",
959 "entityType": "columns",
960 "schema": "public",
961 "table": "secret_materials"
962 },
963 {
964 "type": "text",
965 "typeSchema": null,
966 "notNull": true,
967 "dimensions": 0,
968 "default": null,
969 "generated": null,
970 "identity": null,
971 "name": "value",
972 "entityType": "columns",
973 "schema": "public",
974 "table": "secret_materials"
975 },
976 {
977 "type": "bigint",
978 "typeSchema": null,
979 "notNull": true,
980 "dimensions": 0,
981 "default": null,
982 "generated": null,
983 "identity": null,
984 "name": "created_at",
985 "entityType": "columns",
986 "schema": "public",
987 "table": "secret_materials"
988 },
989 {
990 "type": "bigint",
991 "typeSchema": null,
992 "notNull": true,
993 "dimensions": 0,
994 "default": null,
995 "generated": null,
996 "identity": null,
997 "name": "updated_at",
998 "entityType": "columns",
999 "schema": "public",
1000 "table": "secret_materials"
1001 },
1002 {
1003 "type": "text",
1004 "typeSchema": null,
1005 "notNull": true,
1006 "dimensions": 0,
1007 "default": null,
1008 "generated": null,
1009 "identity": null,
1010 "name": "id",
1011 "entityType": "columns",
1012 "schema": "public",
1013 "table": "source_auth_sessions"
1014 },
1015 {
1016 "type": "text",
1017 "typeSchema": null,
1018 "notNull": true,
1019 "dimensions": 0,
1020 "default": null,
1021 "generated": null,
1022 "identity": null,
1023 "name": "workspace_id",
1024 "entityType": "columns",
1025 "schema": "public",
1026 "table": "source_auth_sessions"
1027 },
1028 {
1029 "type": "text",
1030 "typeSchema": null,
1031 "notNull": true,
1032 "dimensions": 0,
1033 "default": null,
1034 "generated": null,
1035 "identity": null,
1036 "name": "source_id",
1037 "entityType": "columns",
1038 "schema": "public",
1039 "table": "source_auth_sessions"
1040 },
1041 {
1042 "type": "text",
1043 "typeSchema": null,
1044 "notNull": false,
1045 "dimensions": 0,
1046 "default": null,
1047 "generated": null,
1048 "identity": null,
1049 "name": "execution_id",
1050 "entityType": "columns",
1051 "schema": "public",
1052 "table": "source_auth_sessions"
1053 },
1054 {
1055 "type": "text",
1056 "typeSchema": null,
1057 "notNull": false,
1058 "dimensions": 0,
1059 "default": null,
1060 "generated": null,
1061 "identity": null,
1062 "name": "interaction_id",
1063 "entityType": "columns",
1064 "schema": "public",
1065 "table": "source_auth_sessions"
1066 },
1067 {
1068 "type": "text",
1069 "typeSchema": null,
1070 "notNull": true,
1071 "dimensions": 0,
1072 "default": null,
1073 "generated": null,
1074 "identity": null,
1075 "name": "strategy",
1076 "entityType": "columns",
1077 "schema": "public",
1078 "table": "source_auth_sessions"
1079 },
1080 {
1081 "type": "text",
1082 "typeSchema": null,
1083 "notNull": true,
1084 "dimensions": 0,
1085 "default": null,
1086 "generated": null,
1087 "identity": null,
1088 "name": "status",
1089 "entityType": "columns",
1090 "schema": "public",
1091 "table": "source_auth_sessions"
1092 },
1093 {
1094 "type": "text",
1095 "typeSchema": null,
1096 "notNull": true,
1097 "dimensions": 0,
1098 "default": null,
1099 "generated": null,
1100 "identity": null,
1101 "name": "endpoint",
1102 "entityType": "columns",
1103 "schema": "public",
1104 "table": "source_auth_sessions"
1105 },
1106 {
1107 "type": "text",
1108 "typeSchema": null,
1109 "notNull": true,
1110 "dimensions": 0,
1111 "default": null,
1112 "generated": null,
1113 "identity": null,
1114 "name": "state",
1115 "entityType": "columns",
1116 "schema": "public",
1117 "table": "source_auth_sessions"
1118 },
1119 {
1120 "type": "text",
1121 "typeSchema": null,
1122 "notNull": true,
1123 "dimensions": 0,
1124 "default": null,
1125 "generated": null,
1126 "identity": null,
1127 "name": "redirect_uri",
1128 "entityType": "columns",
1129 "schema": "public",
1130 "table": "source_auth_sessions"
1131 },
1132 {
1133 "type": "text",
1134 "typeSchema": null,
1135 "notNull": false,
1136 "dimensions": 0,
1137 "default": null,
1138 "generated": null,
1139 "identity": null,
1140 "name": "scope",
1141 "entityType": "columns",
1142 "schema": "public",
1143 "table": "source_auth_sessions"
1144 },
1145 {
1146 "type": "text",
1147 "typeSchema": null,
1148 "notNull": false,
1149 "dimensions": 0,
1150 "default": null,
1151 "generated": null,
1152 "identity": null,
1153 "name": "resource_metadata_url",
1154 "entityType": "columns",
1155 "schema": "public",
1156 "table": "source_auth_sessions"
1157 },
1158 {
1159 "type": "text",
1160 "typeSchema": null,
1161 "notNull": false,
1162 "dimensions": 0,
1163 "default": null,
1164 "generated": null,
1165 "identity": null,
1166 "name": "authorization_server_url",
1167 "entityType": "columns",
1168 "schema": "public",
1169 "table": "source_auth_sessions"
1170 },
1171 {
1172 "type": "text",
1173 "typeSchema": null,
1174 "notNull": false,
1175 "dimensions": 0,
1176 "default": null,
1177 "generated": null,
1178 "identity": null,
1179 "name": "resource_metadata_json",
1180 "entityType": "columns",
1181 "schema": "public",
1182 "table": "source_auth_sessions"
1183 },
1184 {
1185 "type": "text",
1186 "typeSchema": null,
1187 "notNull": false,
1188 "dimensions": 0,
1189 "default": null,
1190 "generated": null,
1191 "identity": null,
1192 "name": "authorization_server_metadata_json",
1193 "entityType": "columns",
1194 "schema": "public",
1195 "table": "source_auth_sessions"
1196 },
1197 {
1198 "type": "text",
1199 "typeSchema": null,
1200 "notNull": false,
1201 "dimensions": 0,
1202 "default": null,
1203 "generated": null,
1204 "identity": null,
1205 "name": "client_information_json",
1206 "entityType": "columns",
1207 "schema": "public",
1208 "table": "source_auth_sessions"
1209 },
1210 {
1211 "type": "text",
1212 "typeSchema": null,
1213 "notNull": false,
1214 "dimensions": 0,
1215 "default": null,
1216 "generated": null,
1217 "identity": null,
1218 "name": "code_verifier",
1219 "entityType": "columns",
1220 "schema": "public",
1221 "table": "source_auth_sessions"
1222 },
1223 {
1224 "type": "text",
1225 "typeSchema": null,
1226 "notNull": false,
1227 "dimensions": 0,
1228 "default": null,
1229 "generated": null,
1230 "identity": null,
1231 "name": "authorization_url",
1232 "entityType": "columns",
1233 "schema": "public",
1234 "table": "source_auth_sessions"
1235 },
1236 {
1237 "type": "text",
1238 "typeSchema": null,
1239 "notNull": false,
1240 "dimensions": 0,
1241 "default": null,
1242 "generated": null,
1243 "identity": null,
1244 "name": "error_text",
1245 "entityType": "columns",
1246 "schema": "public",
1247 "table": "source_auth_sessions"
1248 },
1249 {
1250 "type": "bigint",
1251 "typeSchema": null,
1252 "notNull": false,
1253 "dimensions": 0,
1254 "default": null,
1255 "generated": null,
1256 "identity": null,
1257 "name": "completed_at",
1258 "entityType": "columns",
1259 "schema": "public",
1260 "table": "source_auth_sessions"
1261 },
1262 {
1263 "type": "bigint",
1264 "typeSchema": null,
1265 "notNull": true,
1266 "dimensions": 0,
1267 "default": null,
1268 "generated": null,
1269 "identity": null,
1270 "name": "created_at",
1271 "entityType": "columns",
1272 "schema": "public",
1273 "table": "source_auth_sessions"
1274 },
1275 {
1276 "type": "bigint",
1277 "typeSchema": null,
1278 "notNull": true,
1279 "dimensions": 0,
1280 "default": null,
1281 "generated": null,
1282 "identity": null,
1283 "name": "updated_at",
1284 "entityType": "columns",
1285 "schema": "public",
1286 "table": "source_auth_sessions"
1287 },
1288 {
1289 "type": "text",
1290 "typeSchema": null,
1291 "notNull": true,
1292 "dimensions": 0,
1293 "default": null,
1294 "generated": null,
1295 "identity": null,
1296 "name": "workspace_id",
1297 "entityType": "columns",
1298 "schema": "public",
1299 "table": "source_credential_bindings"
1300 },
1301 {
1302 "type": "text",
1303 "typeSchema": null,
1304 "notNull": true,
1305 "dimensions": 0,
1306 "default": null,
1307 "generated": null,
1308 "identity": null,
1309 "name": "source_id",
1310 "entityType": "columns",
1311 "schema": "public",
1312 "table": "source_credential_bindings"
1313 },
1314 {
1315 "type": "text",
1316 "typeSchema": null,
1317 "notNull": false,
1318 "dimensions": 0,
1319 "default": null,
1320 "generated": null,
1321 "identity": null,
1322 "name": "token_provider_id",
1323 "entityType": "columns",
1324 "schema": "public",
1325 "table": "source_credential_bindings"
1326 },
1327 {
1328 "type": "text",
1329 "typeSchema": null,
1330 "notNull": false,
1331 "dimensions": 0,
1332 "default": null,
1333 "generated": null,
1334 "identity": null,
1335 "name": "token_handle",
1336 "entityType": "columns",
1337 "schema": "public",
1338 "table": "source_credential_bindings"
1339 },
1340 {
1341 "type": "text",
1342 "typeSchema": null,
1343 "notNull": false,
1344 "dimensions": 0,
1345 "default": null,
1346 "generated": null,
1347 "identity": null,
1348 "name": "refresh_token_provider_id",
1349 "entityType": "columns",
1350 "schema": "public",
1351 "table": "source_credential_bindings"
1352 },
1353 {
1354 "type": "text",
1355 "typeSchema": null,
1356 "notNull": false,
1357 "dimensions": 0,
1358 "default": null,
1359 "generated": null,
1360 "identity": null,
1361 "name": "refresh_token_handle",
1362 "entityType": "columns",
1363 "schema": "public",
1364 "table": "source_credential_bindings"
1365 },
1366 {
1367 "type": "bigint",
1368 "typeSchema": null,
1369 "notNull": true,
1370 "dimensions": 0,
1371 "default": null,
1372 "generated": null,
1373 "identity": null,
1374 "name": "created_at",
1375 "entityType": "columns",
1376 "schema": "public",
1377 "table": "source_credential_bindings"
1378 },
1379 {
1380 "type": "bigint",
1381 "typeSchema": null,
1382 "notNull": true,
1383 "dimensions": 0,
1384 "default": null,
1385 "generated": null,
1386 "identity": null,
1387 "name": "updated_at",
1388 "entityType": "columns",
1389 "schema": "public",
1390 "table": "source_credential_bindings"
1391 },
1392 {
1393 "type": "text",
1394 "typeSchema": null,
1395 "notNull": true,
1396 "dimensions": 0,
1397 "default": null,
1398 "generated": null,
1399 "identity": null,
1400 "name": "workspace_id",
1401 "entityType": "columns",
1402 "schema": "public",
1403 "table": "sources"
1404 },
1405 {
1406 "type": "text",
1407 "typeSchema": null,
1408 "notNull": true,
1409 "dimensions": 0,
1410 "default": null,
1411 "generated": null,
1412 "identity": null,
1413 "name": "source_id",
1414 "entityType": "columns",
1415 "schema": "public",
1416 "table": "sources"
1417 },
1418 {
1419 "type": "text",
1420 "typeSchema": null,
1421 "notNull": true,
1422 "dimensions": 0,
1423 "default": null,
1424 "generated": null,
1425 "identity": null,
1426 "name": "name",
1427 "entityType": "columns",
1428 "schema": "public",
1429 "table": "sources"
1430 },
1431 {
1432 "type": "text",
1433 "typeSchema": null,
1434 "notNull": true,
1435 "dimensions": 0,
1436 "default": null,
1437 "generated": null,
1438 "identity": null,
1439 "name": "kind",
1440 "entityType": "columns",
1441 "schema": "public",
1442 "table": "sources"
1443 },
1444 {
1445 "type": "text",
1446 "typeSchema": null,
1447 "notNull": true,
1448 "dimensions": 0,
1449 "default": null,
1450 "generated": null,
1451 "identity": null,
1452 "name": "endpoint",
1453 "entityType": "columns",
1454 "schema": "public",
1455 "table": "sources"
1456 },
1457 {
1458 "type": "text",
1459 "typeSchema": null,
1460 "notNull": true,
1461 "dimensions": 0,
1462 "default": null,
1463 "generated": null,
1464 "identity": null,
1465 "name": "status",
1466 "entityType": "columns",
1467 "schema": "public",
1468 "table": "sources"
1469 },
1470 {
1471 "type": "boolean",
1472 "typeSchema": null,
1473 "notNull": true,
1474 "dimensions": 0,
1475 "default": null,
1476 "generated": null,
1477 "identity": null,
1478 "name": "enabled",
1479 "entityType": "columns",
1480 "schema": "public",
1481 "table": "sources"
1482 },
1483 {
1484 "type": "text",
1485 "typeSchema": null,
1486 "notNull": false,
1487 "dimensions": 0,
1488 "default": null,
1489 "generated": null,
1490 "identity": null,
1491 "name": "namespace",
1492 "entityType": "columns",
1493 "schema": "public",
1494 "table": "sources"
1495 },
1496 {
1497 "type": "text",
1498 "typeSchema": null,
1499 "notNull": false,
1500 "dimensions": 0,
1501 "default": null,
1502 "generated": null,
1503 "identity": null,
1504 "name": "transport",
1505 "entityType": "columns",
1506 "schema": "public",
1507 "table": "sources"
1508 },
1509 {
1510 "type": "text",
1511 "typeSchema": null,
1512 "notNull": false,
1513 "dimensions": 0,
1514 "default": null,
1515 "generated": null,
1516 "identity": null,
1517 "name": "query_params_json",
1518 "entityType": "columns",
1519 "schema": "public",
1520 "table": "sources"
1521 },
1522 {
1523 "type": "text",
1524 "typeSchema": null,
1525 "notNull": false,
1526 "dimensions": 0,
1527 "default": null,
1528 "generated": null,
1529 "identity": null,
1530 "name": "headers_json",
1531 "entityType": "columns",
1532 "schema": "public",
1533 "table": "sources"
1534 },
1535 {
1536 "type": "text",
1537 "typeSchema": null,
1538 "notNull": false,
1539 "dimensions": 0,
1540 "default": null,
1541 "generated": null,
1542 "identity": null,
1543 "name": "spec_url",
1544 "entityType": "columns",
1545 "schema": "public",
1546 "table": "sources"
1547 },
1548 {
1549 "type": "text",
1550 "typeSchema": null,
1551 "notNull": false,
1552 "dimensions": 0,
1553 "default": null,
1554 "generated": null,
1555 "identity": null,
1556 "name": "default_headers_json",
1557 "entityType": "columns",
1558 "schema": "public",
1559 "table": "sources"
1560 },
1561 {
1562 "type": "text",
1563 "typeSchema": null,
1564 "notNull": true,
1565 "dimensions": 0,
1566 "default": null,
1567 "generated": null,
1568 "identity": null,
1569 "name": "auth_kind",
1570 "entityType": "columns",
1571 "schema": "public",
1572 "table": "sources"
1573 },
1574 {
1575 "type": "text",
1576 "typeSchema": null,
1577 "notNull": false,
1578 "dimensions": 0,
1579 "default": null,
1580 "generated": null,
1581 "identity": null,
1582 "name": "auth_header_name",
1583 "entityType": "columns",
1584 "schema": "public",
1585 "table": "sources"
1586 },
1587 {
1588 "type": "text",
1589 "typeSchema": null,
1590 "notNull": false,
1591 "dimensions": 0,
1592 "default": null,
1593 "generated": null,
1594 "identity": null,
1595 "name": "auth_prefix",
1596 "entityType": "columns",
1597 "schema": "public",
1598 "table": "sources"
1599 },
1600 {
1601 "type": "text",
1602 "typeSchema": null,
1603 "notNull": false,
1604 "dimensions": 0,
1605 "default": null,
1606 "generated": null,
1607 "identity": null,
1608 "name": "source_hash",
1609 "entityType": "columns",
1610 "schema": "public",
1611 "table": "sources"
1612 },
1613 {
1614 "type": "text",
1615 "typeSchema": null,
1616 "notNull": false,
1617 "dimensions": 0,
1618 "default": null,
1619 "generated": null,
1620 "identity": null,
1621 "name": "last_error",
1622 "entityType": "columns",
1623 "schema": "public",
1624 "table": "sources"
1625 },
1626 {
1627 "type": "bigint",
1628 "typeSchema": null,
1629 "notNull": true,
1630 "dimensions": 0,
1631 "default": null,
1632 "generated": null,
1633 "identity": null,
1634 "name": "created_at",
1635 "entityType": "columns",
1636 "schema": "public",
1637 "table": "sources"
1638 },
1639 {
1640 "type": "bigint",
1641 "typeSchema": null,
1642 "notNull": true,
1643 "dimensions": 0,
1644 "default": null,
1645 "generated": null,
1646 "identity": null,
1647 "name": "updated_at",
1648 "entityType": "columns",
1649 "schema": "public",
1650 "table": "sources"
1651 },
1652 {
1653 "type": "text",
1654 "typeSchema": null,
1655 "notNull": true,
1656 "dimensions": 0,
1657 "default": null,
1658 "generated": null,
1659 "identity": null,
1660 "name": "workspace_id",
1661 "entityType": "columns",
1662 "schema": "public",
1663 "table": "tool_artifact_parameters"
1664 },
1665 {
1666 "type": "text",
1667 "typeSchema": null,
1668 "notNull": true,
1669 "dimensions": 0,
1670 "default": null,
1671 "generated": null,
1672 "identity": null,
1673 "name": "path",
1674 "entityType": "columns",
1675 "schema": "public",
1676 "table": "tool_artifact_parameters"
1677 },
1678 {
1679 "type": "bigint",
1680 "typeSchema": null,
1681 "notNull": true,
1682 "dimensions": 0,
1683 "default": null,
1684 "generated": null,
1685 "identity": null,
1686 "name": "position",
1687 "entityType": "columns",
1688 "schema": "public",
1689 "table": "tool_artifact_parameters"
1690 },
1691 {
1692 "type": "text",
1693 "typeSchema": null,
1694 "notNull": true,
1695 "dimensions": 0,
1696 "default": null,
1697 "generated": null,
1698 "identity": null,
1699 "name": "name",
1700 "entityType": "columns",
1701 "schema": "public",
1702 "table": "tool_artifact_parameters"
1703 },
1704 {
1705 "type": "text",
1706 "typeSchema": null,
1707 "notNull": true,
1708 "dimensions": 0,
1709 "default": null,
1710 "generated": null,
1711 "identity": null,
1712 "name": "location",
1713 "entityType": "columns",
1714 "schema": "public",
1715 "table": "tool_artifact_parameters"
1716 },
1717 {
1718 "type": "boolean",
1719 "typeSchema": null,
1720 "notNull": true,
1721 "dimensions": 0,
1722 "default": null,
1723 "generated": null,
1724 "identity": null,
1725 "name": "required",
1726 "entityType": "columns",
1727 "schema": "public",
1728 "table": "tool_artifact_parameters"
1729 },
1730 {
1731 "type": "text",
1732 "typeSchema": null,
1733 "notNull": true,
1734 "dimensions": 0,
1735 "default": null,
1736 "generated": null,
1737 "identity": null,
1738 "name": "workspace_id",
1739 "entityType": "columns",
1740 "schema": "public",
1741 "table": "tool_artifact_ref_hint_keys"
1742 },
1743 {
1744 "type": "text",
1745 "typeSchema": null,
1746 "notNull": true,
1747 "dimensions": 0,
1748 "default": null,
1749 "generated": null,
1750 "identity": null,
1751 "name": "path",
1752 "entityType": "columns",
1753 "schema": "public",
1754 "table": "tool_artifact_ref_hint_keys"
1755 },
1756 {
1757 "type": "bigint",
1758 "typeSchema": null,
1759 "notNull": true,
1760 "dimensions": 0,
1761 "default": null,
1762 "generated": null,
1763 "identity": null,
1764 "name": "position",
1765 "entityType": "columns",
1766 "schema": "public",
1767 "table": "tool_artifact_ref_hint_keys"
1768 },
1769 {
1770 "type": "text",
1771 "typeSchema": null,
1772 "notNull": true,
1773 "dimensions": 0,
1774 "default": null,
1775 "generated": null,
1776 "identity": null,
1777 "name": "ref_hint_key",
1778 "entityType": "columns",
1779 "schema": "public",
1780 "table": "tool_artifact_ref_hint_keys"
1781 },
1782 {
1783 "type": "text",
1784 "typeSchema": null,
1785 "notNull": true,
1786 "dimensions": 0,
1787 "default": null,
1788 "generated": null,
1789 "identity": null,
1790 "name": "workspace_id",
1791 "entityType": "columns",
1792 "schema": "public",
1793 "table": "tool_artifact_request_body_content_types"
1794 },
1795 {
1796 "type": "text",
1797 "typeSchema": null,
1798 "notNull": true,
1799 "dimensions": 0,
1800 "default": null,
1801 "generated": null,
1802 "identity": null,
1803 "name": "path",
1804 "entityType": "columns",
1805 "schema": "public",
1806 "table": "tool_artifact_request_body_content_types"
1807 },
1808 {
1809 "type": "bigint",
1810 "typeSchema": null,
1811 "notNull": true,
1812 "dimensions": 0,
1813 "default": null,
1814 "generated": null,
1815 "identity": null,
1816 "name": "position",
1817 "entityType": "columns",
1818 "schema": "public",
1819 "table": "tool_artifact_request_body_content_types"
1820 },
1821 {
1822 "type": "text",
1823 "typeSchema": null,
1824 "notNull": true,
1825 "dimensions": 0,
1826 "default": null,
1827 "generated": null,
1828 "identity": null,
1829 "name": "content_type",
1830 "entityType": "columns",
1831 "schema": "public",
1832 "table": "tool_artifact_request_body_content_types"
1833 },
1834 {
1835 "type": "text",
1836 "typeSchema": null,
1837 "notNull": true,
1838 "dimensions": 0,
1839 "default": null,
1840 "generated": null,
1841 "identity": null,
1842 "name": "workspace_id",
1843 "entityType": "columns",
1844 "schema": "public",
1845 "table": "tool_artifacts"
1846 },
1847 {
1848 "type": "text",
1849 "typeSchema": null,
1850 "notNull": true,
1851 "dimensions": 0,
1852 "default": null,
1853 "generated": null,
1854 "identity": null,
1855 "name": "path",
1856 "entityType": "columns",
1857 "schema": "public",
1858 "table": "tool_artifacts"
1859 },
1860 {
1861 "type": "text",
1862 "typeSchema": null,
1863 "notNull": true,
1864 "dimensions": 0,
1865 "default": null,
1866 "generated": null,
1867 "identity": null,
1868 "name": "tool_id",
1869 "entityType": "columns",
1870 "schema": "public",
1871 "table": "tool_artifacts"
1872 },
1873 {
1874 "type": "text",
1875 "typeSchema": null,
1876 "notNull": true,
1877 "dimensions": 0,
1878 "default": null,
1879 "generated": null,
1880 "identity": null,
1881 "name": "source_id",
1882 "entityType": "columns",
1883 "schema": "public",
1884 "table": "tool_artifacts"
1885 },
1886 {
1887 "type": "text",
1888 "typeSchema": null,
1889 "notNull": false,
1890 "dimensions": 0,
1891 "default": null,
1892 "generated": null,
1893 "identity": null,
1894 "name": "title",
1895 "entityType": "columns",
1896 "schema": "public",
1897 "table": "tool_artifacts"
1898 },
1899 {
1900 "type": "text",
1901 "typeSchema": null,
1902 "notNull": false,
1903 "dimensions": 0,
1904 "default": null,
1905 "generated": null,
1906 "identity": null,
1907 "name": "description",
1908 "entityType": "columns",
1909 "schema": "public",
1910 "table": "tool_artifacts"
1911 },
1912 {
1913 "type": "text",
1914 "typeSchema": null,
1915 "notNull": true,
1916 "dimensions": 0,
1917 "default": null,
1918 "generated": null,
1919 "identity": null,
1920 "name": "search_namespace",
1921 "entityType": "columns",
1922 "schema": "public",
1923 "table": "tool_artifacts"
1924 },
1925 {
1926 "type": "text",
1927 "typeSchema": null,
1928 "notNull": true,
1929 "dimensions": 0,
1930 "default": null,
1931 "generated": null,
1932 "identity": null,
1933 "name": "search_text",
1934 "entityType": "columns",
1935 "schema": "public",
1936 "table": "tool_artifacts"
1937 },
1938 {
1939 "type": "text",
1940 "typeSchema": null,
1941 "notNull": false,
1942 "dimensions": 0,
1943 "default": null,
1944 "generated": null,
1945 "identity": null,
1946 "name": "input_schema_json",
1947 "entityType": "columns",
1948 "schema": "public",
1949 "table": "tool_artifacts"
1950 },
1951 {
1952 "type": "text",
1953 "typeSchema": null,
1954 "notNull": false,
1955 "dimensions": 0,
1956 "default": null,
1957 "generated": null,
1958 "identity": null,
1959 "name": "output_schema_json",
1960 "entityType": "columns",
1961 "schema": "public",
1962 "table": "tool_artifacts"
1963 },
1964 {
1965 "type": "text",
1966 "typeSchema": null,
1967 "notNull": true,
1968 "dimensions": 0,
1969 "default": null,
1970 "generated": null,
1971 "identity": null,
1972 "name": "provider_kind",
1973 "entityType": "columns",
1974 "schema": "public",
1975 "table": "tool_artifacts"
1976 },
1977 {
1978 "type": "text",
1979 "typeSchema": null,
1980 "notNull": false,
1981 "dimensions": 0,
1982 "default": null,
1983 "generated": null,
1984 "identity": null,
1985 "name": "mcp_tool_name",
1986 "entityType": "columns",
1987 "schema": "public",
1988 "table": "tool_artifacts"
1989 },
1990 {
1991 "type": "text",
1992 "typeSchema": null,
1993 "notNull": false,
1994 "dimensions": 0,
1995 "default": null,
1996 "generated": null,
1997 "identity": null,
1998 "name": "openapi_method",
1999 "entityType": "columns",
2000 "schema": "public",
2001 "table": "tool_artifacts"
2002 },
2003 {
2004 "type": "text",
2005 "typeSchema": null,
2006 "notNull": false,
2007 "dimensions": 0,
2008 "default": null,
2009 "generated": null,
2010 "identity": null,
2011 "name": "openapi_path_template",
2012 "entityType": "columns",
2013 "schema": "public",
2014 "table": "tool_artifacts"
2015 },
2016 {
2017 "type": "text",
2018 "typeSchema": null,
2019 "notNull": false,
2020 "dimensions": 0,
2021 "default": null,
2022 "generated": null,
2023 "identity": null,
2024 "name": "openapi_operation_hash",
2025 "entityType": "columns",
2026 "schema": "public",
2027 "table": "tool_artifacts"
2028 },
2029 {
2030 "type": "boolean",
2031 "typeSchema": null,
2032 "notNull": false,
2033 "dimensions": 0,
2034 "default": null,
2035 "generated": null,
2036 "identity": null,
2037 "name": "openapi_request_body_required",
2038 "entityType": "columns",
2039 "schema": "public",
2040 "table": "tool_artifacts"
2041 },
2042 {
2043 "type": "bigint",
2044 "typeSchema": null,
2045 "notNull": true,
2046 "dimensions": 0,
2047 "default": null,
2048 "generated": null,
2049 "identity": null,
2050 "name": "created_at",
2051 "entityType": "columns",
2052 "schema": "public",
2053 "table": "tool_artifacts"
2054 },
2055 {
2056 "type": "bigint",
2057 "typeSchema": null,
2058 "notNull": true,
2059 "dimensions": 0,
2060 "default": null,
2061 "generated": null,
2062 "identity": null,
2063 "name": "updated_at",
2064 "entityType": "columns",
2065 "schema": "public",
2066 "table": "tool_artifacts"
2067 },
2068 {
2069 "type": "text",
2070 "typeSchema": null,
2071 "notNull": true,
2072 "dimensions": 0,
2073 "default": null,
2074 "generated": null,
2075 "identity": null,
2076 "name": "id",
2077 "entityType": "columns",
2078 "schema": "public",
2079 "table": "workspaces"
2080 },
2081 {
2082 "type": "text",
2083 "typeSchema": null,
2084 "notNull": true,
2085 "dimensions": 0,
2086 "default": null,
2087 "generated": null,
2088 "identity": null,
2089 "name": "organization_id",
2090 "entityType": "columns",
2091 "schema": "public",
2092 "table": "workspaces"
2093 },
2094 {
2095 "type": "text",
2096 "typeSchema": null,
2097 "notNull": true,
2098 "dimensions": 0,
2099 "default": null,
2100 "generated": null,
2101 "identity": null,
2102 "name": "name",
2103 "entityType": "columns",
2104 "schema": "public",
2105 "table": "workspaces"
2106 },
2107 {
2108 "type": "text",
2109 "typeSchema": null,
2110 "notNull": false,
2111 "dimensions": 0,
2112 "default": null,
2113 "generated": null,
2114 "identity": null,
2115 "name": "created_by_account_id",
2116 "entityType": "columns",
2117 "schema": "public",
2118 "table": "workspaces"
2119 },
2120 {
2121 "type": "bigint",
2122 "typeSchema": null,
2123 "notNull": true,
2124 "dimensions": 0,
2125 "default": null,
2126 "generated": null,
2127 "identity": null,
2128 "name": "created_at",
2129 "entityType": "columns",
2130 "schema": "public",
2131 "table": "workspaces"
2132 },
2133 {
2134 "type": "bigint",
2135 "typeSchema": null,
2136 "notNull": true,
2137 "dimensions": 0,
2138 "default": null,
2139 "generated": null,
2140 "identity": null,
2141 "name": "updated_at",
2142 "entityType": "columns",
2143 "schema": "public",
2144 "table": "workspaces"
2145 },
2146 {
2147 "nameExplicit": true,
2148 "columns": [
2149 {
2150 "value": "provider",
2151 "isExpression": false,
2152 "asc": true,
2153 "nullsFirst": false,
2154 "opclass": null
2155 },
2156 {
2157 "value": "subject",
2158 "isExpression": false,
2159 "asc": true,
2160 "nullsFirst": false,
2161 "opclass": null
2162 }
2163 ],
2164 "isUnique": true,
2165 "where": null,
2166 "with": "",
2167 "method": "btree",
2168 "concurrently": false,
2169 "name": "accounts_provider_subject_idx",
2170 "entityType": "indexes",
2171 "schema": "public",
2172 "table": "accounts"
2173 },
2174 {
2175 "nameExplicit": true,
2176 "columns": [
2177 {
2178 "value": "updated_at",
2179 "isExpression": false,
2180 "asc": true,
2181 "nullsFirst": false,
2182 "opclass": null
2183 },
2184 {
2185 "value": "id",
2186 "isExpression": false,
2187 "asc": true,
2188 "nullsFirst": false,
2189 "opclass": null
2190 }
2191 ],
2192 "isUnique": false,
2193 "where": null,
2194 "with": "",
2195 "method": "btree",
2196 "concurrently": false,
2197 "name": "accounts_updated_idx",
2198 "entityType": "indexes",
2199 "schema": "public",
2200 "table": "accounts"
2201 },
2202 {
2203 "nameExplicit": true,
2204 "columns": [
2205 {
2206 "value": "execution_id",
2207 "isExpression": false,
2208 "asc": true,
2209 "nullsFirst": false,
2210 "opclass": null
2211 },
2212 {
2213 "value": "updated_at",
2214 "isExpression": false,
2215 "asc": true,
2216 "nullsFirst": false,
2217 "opclass": null
2218 },
2219 {
2220 "value": "id",
2221 "isExpression": false,
2222 "asc": true,
2223 "nullsFirst": false,
2224 "opclass": null
2225 }
2226 ],
2227 "isUnique": false,
2228 "where": null,
2229 "with": "",
2230 "method": "btree",
2231 "concurrently": false,
2232 "name": "execution_interactions_execution_idx",
2233 "entityType": "indexes",
2234 "schema": "public",
2235 "table": "execution_interactions"
2236 },
2237 {
2238 "nameExplicit": true,
2239 "columns": [
2240 {
2241 "value": "workspace_id",
2242 "isExpression": false,
2243 "asc": true,
2244 "nullsFirst": false,
2245 "opclass": null
2246 },
2247 {
2248 "value": "updated_at",
2249 "isExpression": false,
2250 "asc": true,
2251 "nullsFirst": false,
2252 "opclass": null
2253 },
2254 {
2255 "value": "id",
2256 "isExpression": false,
2257 "asc": true,
2258 "nullsFirst": false,
2259 "opclass": null
2260 }
2261 ],
2262 "isUnique": false,
2263 "where": null,
2264 "with": "",
2265 "method": "btree",
2266 "concurrently": false,
2267 "name": "executions_workspace_idx",
2268 "entityType": "indexes",
2269 "schema": "public",
2270 "table": "executions"
2271 },
2272 {
2273 "nameExplicit": true,
2274 "columns": [
2275 {
2276 "value": "organization_id",
2277 "isExpression": false,
2278 "asc": true,
2279 "nullsFirst": false,
2280 "opclass": null
2281 }
2282 ],
2283 "isUnique": false,
2284 "where": null,
2285 "with": "",
2286 "method": "btree",
2287 "concurrently": false,
2288 "name": "organization_memberships_org_idx",
2289 "entityType": "indexes",
2290 "schema": "public",
2291 "table": "organization_memberships"
2292 },
2293 {
2294 "nameExplicit": true,
2295 "columns": [
2296 {
2297 "value": "account_id",
2298 "isExpression": false,
2299 "asc": true,
2300 "nullsFirst": false,
2301 "opclass": null
2302 }
2303 ],
2304 "isUnique": false,
2305 "where": null,
2306 "with": "",
2307 "method": "btree",
2308 "concurrently": false,
2309 "name": "organization_memberships_account_idx",
2310 "entityType": "indexes",
2311 "schema": "public",
2312 "table": "organization_memberships"
2313 },
2314 {
2315 "nameExplicit": true,
2316 "columns": [
2317 {
2318 "value": "organization_id",
2319 "isExpression": false,
2320 "asc": true,
2321 "nullsFirst": false,
2322 "opclass": null
2323 },
2324 {
2325 "value": "account_id",
2326 "isExpression": false,
2327 "asc": true,
2328 "nullsFirst": false,
2329 "opclass": null
2330 }
2331 ],
2332 "isUnique": true,
2333 "where": null,
2334 "with": "",
2335 "method": "btree",
2336 "concurrently": false,
2337 "name": "organization_memberships_org_account_idx",
2338 "entityType": "indexes",
2339 "schema": "public",
2340 "table": "organization_memberships"
2341 },
2342 {
2343 "nameExplicit": true,
2344 "columns": [
2345 {
2346 "value": "slug",
2347 "isExpression": false,
2348 "asc": true,
2349 "nullsFirst": false,
2350 "opclass": null
2351 }
2352 ],
2353 "isUnique": true,
2354 "where": null,
2355 "with": "",
2356 "method": "btree",
2357 "concurrently": false,
2358 "name": "organizations_slug_idx",
2359 "entityType": "indexes",
2360 "schema": "public",
2361 "table": "organizations"
2362 },
2363 {
2364 "nameExplicit": true,
2365 "columns": [
2366 {
2367 "value": "updated_at",
2368 "isExpression": false,
2369 "asc": true,
2370 "nullsFirst": false,
2371 "opclass": null
2372 },
2373 {
2374 "value": "id",
2375 "isExpression": false,
2376 "asc": true,
2377 "nullsFirst": false,
2378 "opclass": null
2379 }
2380 ],
2381 "isUnique": false,
2382 "where": null,
2383 "with": "",
2384 "method": "btree",
2385 "concurrently": false,
2386 "name": "organizations_updated_idx",
2387 "entityType": "indexes",
2388 "schema": "public",
2389 "table": "organizations"
2390 },
2391 {
2392 "nameExplicit": true,
2393 "columns": [
2394 {
2395 "value": "workspace_id",
2396 "isExpression": false,
2397 "asc": true,
2398 "nullsFirst": false,
2399 "opclass": null
2400 },
2401 {
2402 "value": "updated_at",
2403 "isExpression": false,
2404 "asc": true,
2405 "nullsFirst": false,
2406 "opclass": null
2407 },
2408 {
2409 "value": "id",
2410 "isExpression": false,
2411 "asc": true,
2412 "nullsFirst": false,
2413 "opclass": null
2414 }
2415 ],
2416 "isUnique": false,
2417 "where": null,
2418 "with": "",
2419 "method": "btree",
2420 "concurrently": false,
2421 "name": "policies_workspace_idx",
2422 "entityType": "indexes",
2423 "schema": "public",
2424 "table": "policies"
2425 },
2426 {
2427 "nameExplicit": true,
2428 "columns": [
2429 {
2430 "value": "updated_at",
2431 "isExpression": false,
2432 "asc": true,
2433 "nullsFirst": false,
2434 "opclass": null
2435 },
2436 {
2437 "value": "id",
2438 "isExpression": false,
2439 "asc": true,
2440 "nullsFirst": false,
2441 "opclass": null
2442 }
2443 ],
2444 "isUnique": false,
2445 "where": null,
2446 "with": "",
2447 "method": "btree",
2448 "concurrently": false,
2449 "name": "secret_materials_updated_idx",
2450 "entityType": "indexes",
2451 "schema": "public",
2452 "table": "secret_materials"
2453 },
2454 {
2455 "nameExplicit": true,
2456 "columns": [
2457 {
2458 "value": "workspace_id",
2459 "isExpression": false,
2460 "asc": true,
2461 "nullsFirst": false,
2462 "opclass": null
2463 },
2464 {
2465 "value": "updated_at",
2466 "isExpression": false,
2467 "asc": true,
2468 "nullsFirst": false,
2469 "opclass": null
2470 },
2471 {
2472 "value": "id",
2473 "isExpression": false,
2474 "asc": true,
2475 "nullsFirst": false,
2476 "opclass": null
2477 }
2478 ],
2479 "isUnique": false,
2480 "where": null,
2481 "with": "",
2482 "method": "btree",
2483 "concurrently": false,
2484 "name": "source_auth_sessions_workspace_idx",
2485 "entityType": "indexes",
2486 "schema": "public",
2487 "table": "source_auth_sessions"
2488 },
2489 {
2490 "nameExplicit": true,
2491 "columns": [
2492 {
2493 "value": "state",
2494 "isExpression": false,
2495 "asc": true,
2496 "nullsFirst": false,
2497 "opclass": null
2498 }
2499 ],
2500 "isUnique": true,
2501 "where": null,
2502 "with": "",
2503 "method": "btree",
2504 "concurrently": false,
2505 "name": "source_auth_sessions_state_idx",
2506 "entityType": "indexes",
2507 "schema": "public",
2508 "table": "source_auth_sessions"
2509 },
2510 {
2511 "nameExplicit": true,
2512 "columns": [
2513 {
2514 "value": "workspace_id",
2515 "isExpression": false,
2516 "asc": true,
2517 "nullsFirst": false,
2518 "opclass": null
2519 },
2520 {
2521 "value": "updated_at",
2522 "isExpression": false,
2523 "asc": true,
2524 "nullsFirst": false,
2525 "opclass": null
2526 },
2527 {
2528 "value": "source_id",
2529 "isExpression": false,
2530 "asc": true,
2531 "nullsFirst": false,
2532 "opclass": null
2533 }
2534 ],
2535 "isUnique": false,
2536 "where": null,
2537 "with": "",
2538 "method": "btree",
2539 "concurrently": false,
2540 "name": "source_credential_bindings_workspace_idx",
2541 "entityType": "indexes",
2542 "schema": "public",
2543 "table": "source_credential_bindings"
2544 },
2545 {
2546 "nameExplicit": true,
2547 "columns": [
2548 {
2549 "value": "workspace_id",
2550 "isExpression": false,
2551 "asc": true,
2552 "nullsFirst": false,
2553 "opclass": null
2554 },
2555 {
2556 "value": "name",
2557 "isExpression": false,
2558 "asc": true,
2559 "nullsFirst": false,
2560 "opclass": null
2561 }
2562 ],
2563 "isUnique": true,
2564 "where": null,
2565 "with": "",
2566 "method": "btree",
2567 "concurrently": false,
2568 "name": "sources_workspace_name_idx",
2569 "entityType": "indexes",
2570 "schema": "public",
2571 "table": "sources"
2572 },
2573 {
2574 "nameExplicit": true,
2575 "columns": [
2576 {
2577 "value": "workspace_id",
2578 "isExpression": false,
2579 "asc": true,
2580 "nullsFirst": false,
2581 "opclass": null
2582 },
2583 {
2584 "value": "path",
2585 "isExpression": false,
2586 "asc": true,
2587 "nullsFirst": false,
2588 "opclass": null
2589 },
2590 {
2591 "value": "position",
2592 "isExpression": false,
2593 "asc": true,
2594 "nullsFirst": false,
2595 "opclass": null
2596 }
2597 ],
2598 "isUnique": false,
2599 "where": null,
2600 "with": "",
2601 "method": "btree",
2602 "concurrently": false,
2603 "name": "tool_artifact_parameters_lookup_idx",
2604 "entityType": "indexes",
2605 "schema": "public",
2606 "table": "tool_artifact_parameters"
2607 },
2608 {
2609 "nameExplicit": true,
2610 "columns": [
2611 {
2612 "value": "workspace_id",
2613 "isExpression": false,
2614 "asc": true,
2615 "nullsFirst": false,
2616 "opclass": null
2617 },
2618 {
2619 "value": "path",
2620 "isExpression": false,
2621 "asc": true,
2622 "nullsFirst": false,
2623 "opclass": null
2624 },
2625 {
2626 "value": "position",
2627 "isExpression": false,
2628 "asc": true,
2629 "nullsFirst": false,
2630 "opclass": null
2631 }
2632 ],
2633 "isUnique": false,
2634 "where": null,
2635 "with": "",
2636 "method": "btree",
2637 "concurrently": false,
2638 "name": "tool_artifact_ref_hint_keys_lookup_idx",
2639 "entityType": "indexes",
2640 "schema": "public",
2641 "table": "tool_artifact_ref_hint_keys"
2642 },
2643 {
2644 "nameExplicit": true,
2645 "columns": [
2646 {
2647 "value": "workspace_id",
2648 "isExpression": false,
2649 "asc": true,
2650 "nullsFirst": false,
2651 "opclass": null
2652 },
2653 {
2654 "value": "path",
2655 "isExpression": false,
2656 "asc": true,
2657 "nullsFirst": false,
2658 "opclass": null
2659 },
2660 {
2661 "value": "position",
2662 "isExpression": false,
2663 "asc": true,
2664 "nullsFirst": false,
2665 "opclass": null
2666 }
2667 ],
2668 "isUnique": false,
2669 "where": null,
2670 "with": "",
2671 "method": "btree",
2672 "concurrently": false,
2673 "name": "tool_artifact_request_body_content_types_lookup_idx",
2674 "entityType": "indexes",
2675 "schema": "public",
2676 "table": "tool_artifact_request_body_content_types"
2677 },
2678 {
2679 "nameExplicit": true,
2680 "columns": [
2681 {
2682 "value": "workspace_id",
2683 "isExpression": false,
2684 "asc": true,
2685 "nullsFirst": false,
2686 "opclass": null
2687 },
2688 {
2689 "value": "source_id",
2690 "isExpression": false,
2691 "asc": true,
2692 "nullsFirst": false,
2693 "opclass": null
2694 },
2695 {
2696 "value": "updated_at",
2697 "isExpression": false,
2698 "asc": true,
2699 "nullsFirst": false,
2700 "opclass": null
2701 },
2702 {
2703 "value": "path",
2704 "isExpression": false,
2705 "asc": true,
2706 "nullsFirst": false,
2707 "opclass": null
2708 }
2709 ],
2710 "isUnique": false,
2711 "where": null,
2712 "with": "",
2713 "method": "btree",
2714 "concurrently": false,
2715 "name": "tool_artifacts_workspace_source_idx",
2716 "entityType": "indexes",
2717 "schema": "public",
2718 "table": "tool_artifacts"
2719 },
2720 {
2721 "nameExplicit": true,
2722 "columns": [
2723 {
2724 "value": "workspace_id",
2725 "isExpression": false,
2726 "asc": true,
2727 "nullsFirst": false,
2728 "opclass": null
2729 },
2730 {
2731 "value": "search_namespace",
2732 "isExpression": false,
2733 "asc": true,
2734 "nullsFirst": false,
2735 "opclass": null
2736 },
2737 {
2738 "value": "path",
2739 "isExpression": false,
2740 "asc": true,
2741 "nullsFirst": false,
2742 "opclass": null
2743 }
2744 ],
2745 "isUnique": false,
2746 "where": null,
2747 "with": "",
2748 "method": "btree",
2749 "concurrently": false,
2750 "name": "tool_artifacts_workspace_namespace_idx",
2751 "entityType": "indexes",
2752 "schema": "public",
2753 "table": "tool_artifacts"
2754 },
2755 {
2756 "nameExplicit": true,
2757 "columns": [
2758 {
2759 "value": "organization_id",
2760 "isExpression": false,
2761 "asc": true,
2762 "nullsFirst": false,
2763 "opclass": null
2764 }
2765 ],
2766 "isUnique": false,
2767 "where": null,
2768 "with": "",
2769 "method": "btree",
2770 "concurrently": false,
2771 "name": "workspaces_org_idx",
2772 "entityType": "indexes",
2773 "schema": "public",
2774 "table": "workspaces"
2775 },
2776 {
2777 "nameExplicit": true,
2778 "columns": [
2779 {
2780 "value": "organization_id",
2781 "isExpression": false,
2782 "asc": true,
2783 "nullsFirst": false,
2784 "opclass": null
2785 },
2786 {
2787 "value": "name",
2788 "isExpression": false,
2789 "asc": true,
2790 "nullsFirst": false,
2791 "opclass": null
2792 }
2793 ],
2794 "isUnique": true,
2795 "where": null,
2796 "with": "",
2797 "method": "btree",
2798 "concurrently": false,
2799 "name": "workspaces_org_name_idx",
2800 "entityType": "indexes",
2801 "schema": "public",
2802 "table": "workspaces"
2803 },
2804 {
2805 "columns": [
2806 "workspace_id",
2807 "source_id"
2808 ],
2809 "nameExplicit": false,
2810 "name": "source_credential_bindings_pkey",
2811 "entityType": "pks",
2812 "schema": "public",
2813 "table": "source_credential_bindings"
2814 },
2815 {
2816 "columns": [
2817 "workspace_id",
2818 "source_id"
2819 ],
2820 "nameExplicit": false,
2821 "name": "sources_pkey",
2822 "entityType": "pks",
2823 "schema": "public",
2824 "table": "sources"
2825 },
2826 {
2827 "columns": [
2828 "workspace_id",
2829 "path",
2830 "position"
2831 ],
2832 "nameExplicit": false,
2833 "name": "tool_artifact_parameters_pkey",
2834 "entityType": "pks",
2835 "schema": "public",
2836 "table": "tool_artifact_parameters"
2837 },
2838 {
2839 "columns": [
2840 "workspace_id",
2841 "path",
2842 "position"
2843 ],
2844 "nameExplicit": false,
2845 "name": "tool_artifact_ref_hint_keys_pkey",
2846 "entityType": "pks",
2847 "schema": "public",
2848 "table": "tool_artifact_ref_hint_keys"
2849 },
2850 {
2851 "columns": [
2852 "workspace_id",
2853 "path",
2854 "position"
2855 ],
2856 "nameExplicit": false,
2857 "name": "tool_artifact_request_body_content_types_pkey",
2858 "entityType": "pks",
2859 "schema": "public",
2860 "table": "tool_artifact_request_body_content_types"
2861 },
2862 {
2863 "columns": [
2864 "workspace_id",
2865 "path"
2866 ],
2867 "nameExplicit": false,
2868 "name": "tool_artifacts_pkey",
2869 "entityType": "pks",
2870 "schema": "public",
2871 "table": "tool_artifacts"
2872 },
2873 {
2874 "columns": [
2875 "id"
2876 ],
2877 "nameExplicit": false,
2878 "name": "accounts_pkey",
2879 "schema": "public",
2880 "table": "accounts",
2881 "entityType": "pks"
2882 },
2883 {
2884 "columns": [
2885 "id"
2886 ],
2887 "nameExplicit": false,
2888 "name": "execution_interactions_pkey",
2889 "schema": "public",
2890 "table": "execution_interactions",
2891 "entityType": "pks"
2892 },
2893 {
2894 "columns": [
2895 "id"
2896 ],
2897 "nameExplicit": false,
2898 "name": "executions_pkey",
2899 "schema": "public",
2900 "table": "executions",
2901 "entityType": "pks"
2902 },
2903 {
2904 "columns": [
2905 "id"
2906 ],
2907 "nameExplicit": false,
2908 "name": "local_installations_pkey",
2909 "schema": "public",
2910 "table": "local_installations",
2911 "entityType": "pks"
2912 },
2913 {
2914 "columns": [
2915 "id"
2916 ],
2917 "nameExplicit": false,
2918 "name": "organization_memberships_pkey",
2919 "schema": "public",
2920 "table": "organization_memberships",
2921 "entityType": "pks"
2922 },
2923 {
2924 "columns": [
2925 "id"
2926 ],
2927 "nameExplicit": false,
2928 "name": "organizations_pkey",
2929 "schema": "public",
2930 "table": "organizations",
2931 "entityType": "pks"
2932 },
2933 {
2934 "columns": [
2935 "id"
2936 ],
2937 "nameExplicit": false,
2938 "name": "policies_pkey",
2939 "schema": "public",
2940 "table": "policies",
2941 "entityType": "pks"
2942 },
2943 {
2944 "columns": [
2945 "id"
2946 ],
2947 "nameExplicit": false,
2948 "name": "secret_materials_pkey",
2949 "schema": "public",
2950 "table": "secret_materials",
2951 "entityType": "pks"
2952 },
2953 {
2954 "columns": [
2955 "id"
2956 ],
2957 "nameExplicit": false,
2958 "name": "source_auth_sessions_pkey",
2959 "schema": "public",
2960 "table": "source_auth_sessions",
2961 "entityType": "pks"
2962 },
2963 {
2964 "columns": [
2965 "id"
2966 ],
2967 "nameExplicit": false,
2968 "name": "workspaces_pkey",
2969 "schema": "public",
2970 "table": "workspaces",
2971 "entityType": "pks"
2972 },
2973 {
2974 "value": "\"provider\" in ('local', 'workos', 'service')",
2975 "name": "accounts_provider_check",
2976 "entityType": "checks",
2977 "schema": "public",
2978 "table": "accounts"
2979 },
2980 {
2981 "value": "\"status\" in ('pending', 'resolved', 'cancelled')",
2982 "name": "execution_interactions_status_check",
2983 "entityType": "checks",
2984 "schema": "public",
2985 "table": "execution_interactions"
2986 },
2987 {
2988 "value": "\"status\" in ('pending', 'running', 'waiting_for_interaction', 'completed', 'failed', 'cancelled')",
2989 "name": "executions_status_check",
2990 "entityType": "checks",
2991 "schema": "public",
2992 "table": "executions"
2993 },
2994 {
2995 "value": "\"role\" in ('viewer', 'editor', 'admin', 'owner')",
2996 "name": "organization_memberships_role_check",
2997 "entityType": "checks",
2998 "schema": "public",
2999 "table": "organization_memberships"
3000 },
3001 {
3002 "value": "\"status\" in ('invited', 'active', 'suspended', 'removed')",
3003 "name": "organization_memberships_status_check",
3004 "entityType": "checks",
3005 "schema": "public",
3006 "table": "organization_memberships"
3007 },
3008 {
3009 "value": "\"status\" in ('active', 'suspended', 'archived')",
3010 "name": "organizations_status_check",
3011 "entityType": "checks",
3012 "schema": "public",
3013 "table": "organizations"
3014 },
3015 {
3016 "value": "\"resource_type\" in ('all_tools', 'source', 'namespace', 'tool_path')",
3017 "name": "policies_resource_type_check",
3018 "entityType": "checks",
3019 "schema": "public",
3020 "table": "policies"
3021 },
3022 {
3023 "value": "\"match_type\" in ('glob', 'exact')",
3024 "name": "policies_match_type_check",
3025 "entityType": "checks",
3026 "schema": "public",
3027 "table": "policies"
3028 },
3029 {
3030 "value": "\"effect\" in ('allow', 'deny')",
3031 "name": "policies_effect_check",
3032 "entityType": "checks",
3033 "schema": "public",
3034 "table": "policies"
3035 },
3036 {
3037 "value": "\"approval_mode\" in ('auto', 'required')",
3038 "name": "policies_approval_mode_check",
3039 "entityType": "checks",
3040 "schema": "public",
3041 "table": "policies"
3042 },
3043 {
3044 "value": "\"purpose\" in ('auth_material', 'oauth_access_token', 'oauth_refresh_token', 'oauth_client_info')",
3045 "name": "secret_materials_purpose_check",
3046 "entityType": "checks",
3047 "schema": "public",
3048 "table": "secret_materials"
3049 },
3050 {
3051 "value": "\"strategy\" in ('oauth2_authorization_code')",
3052 "name": "source_auth_sessions_strategy_check",
3053 "entityType": "checks",
3054 "schema": "public",
3055 "table": "source_auth_sessions"
3056 },
3057 {
3058 "value": "\"status\" in ('pending', 'completed', 'failed', 'cancelled')",
3059 "name": "source_auth_sessions_status_check",
3060 "entityType": "checks",
3061 "schema": "public",
3062 "table": "source_auth_sessions"
3063 },
3064 {
3065 "value": "\"kind\" in ('mcp', 'openapi', 'graphql', 'internal')",
3066 "name": "sources_kind_check",
3067 "entityType": "checks",
3068 "schema": "public",
3069 "table": "sources"
3070 },
3071 {
3072 "value": "\"status\" in ('draft', 'probing', 'auth_required', 'connected', 'error')",
3073 "name": "sources_status_check",
3074 "entityType": "checks",
3075 "schema": "public",
3076 "table": "sources"
3077 },
3078 {
3079 "value": "\"transport\" is null or \"transport\" in ('auto', 'streamable-http', 'sse')",
3080 "name": "sources_transport_check",
3081 "entityType": "checks",
3082 "schema": "public",
3083 "table": "sources"
3084 },
3085 {
3086 "value": "\"auth_kind\" in ('none', 'bearer', 'oauth2')",
3087 "name": "sources_auth_kind_check",
3088 "entityType": "checks",
3089 "schema": "public",
3090 "table": "sources"
3091 },
3092 {
3093 "value": "\"location\" in ('path', 'query', 'header', 'cookie')",
3094 "name": "tool_artifact_parameters_location_check",
3095 "entityType": "checks",
3096 "schema": "public",
3097 "table": "tool_artifact_parameters"
3098 },
3099 {
3100 "value": "\"provider_kind\" in ('mcp', 'openapi')",
3101 "name": "tool_artifacts_provider_kind_check",
3102 "entityType": "checks",
3103 "schema": "public",
3104 "table": "tool_artifacts"
3105 },
3106 {
3107 "value": "\"provider_kind\" <> 'mcp'\n or (\n \"mcp_tool_name\" is not null\n and \"openapi_method\" is null\n and \"openapi_path_template\" is null\n and \"openapi_operation_hash\" is null\n and \"openapi_request_body_required\" is null\n )",
3108 "name": "tool_artifacts_mcp_shape_check",
3109 "entityType": "checks",
3110 "schema": "public",
3111 "table": "tool_artifacts"
3112 },
3113 {
3114 "value": "\"provider_kind\" <> 'openapi'\n or (\n \"mcp_tool_name\" is null\n and \"openapi_method\" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')\n and \"openapi_path_template\" is not null\n and \"openapi_operation_hash\" is not null\n )",
3115 "name": "tool_artifacts_openapi_shape_check",
3116 "entityType": "checks",
3117 "schema": "public",
3118 "table": "tool_artifacts"
3119 }
3120 ],
3121 "renames": []
3122}
packages/control-plane/src/persistence/index.ts+2−0
@@ -16,6 +16,7 @@ import { · 15 unmodified lines
16 createSourceAuthSessionsRepo,16 createSourceAuthSessionsRepo,
17 createSourceCredentialBindingsRepo,17 createSourceCredentialBindingsRepo,
18 createSourcesRepo,18 createSourcesRepo,
19 createToolArtifactsRepo,
19 createWorkspacesRepo,20 createWorkspacesRepo,
20} from "./repos";21} from "./repos";
21import { drizzleSchema, tableNames, type DrizzleTables } from "./schema";22import { drizzleSchema, tableNames, type DrizzleTables } from "./schema";
@@ -50,6 +51,7 @@ const createRows = (client: DrizzleClient, tables: DrizzleTables = drizzleSchema · 28 unmodified lines
50 organizationMemberships: createOrganizationMembershipsRepo(client, tables),51 organizationMemberships: createOrganizationMembershipsRepo(client, tables),
51 workspaces: createWorkspacesRepo(client, tables),52 workspaces: createWorkspacesRepo(client, tables),
52 sources: createSourcesRepo(client, tables),53 sources: createSourcesRepo(client, tables),
54 toolArtifacts: createToolArtifactsRepo(client, tables),
53 sourceCredentialBindings: createSourceCredentialBindingsRepo(client, tables),55 sourceCredentialBindings: createSourceCredentialBindingsRepo(client, tables),
54 secretMaterials: createSecretMaterialsRepo(client, tables),56 secretMaterials: createSecretMaterialsRepo(client, tables),
55 sourceAuthSessions: createSourceAuthSessionsRepo(client, tables),57 sourceAuthSessions: createSourceAuthSessionsRepo(client, tables),
packages/control-plane/src/persistence/repos/index.ts+1−0
@@ -3,6 +3,7 @@ export { createOrganizationsRepo } from "./organizations-repo"; · 2 unmodified lines
3export { createOrganizationMembershipsRepo } from "./organization-memberships-repo";3export { createOrganizationMembershipsRepo } from "./organization-memberships-repo";
4export { createWorkspacesRepo } from "./workspaces-repo";4export { createWorkspacesRepo } from "./workspaces-repo";
5export { createSourcesRepo } from "./sources-repo";5export { createSourcesRepo } from "./sources-repo";
6export { createToolArtifactsRepo } from "./tool-artifacts-repo";
6export { createSourceCredentialBindingsRepo } from "./source-credential-bindings-repo";7export { createSourceCredentialBindingsRepo } from "./source-credential-bindings-repo";
7export { createSecretMaterialsRepo } from "./secret-materials-repo";8export { createSecretMaterialsRepo } from "./secret-materials-repo";
8export { createSourceAuthSessionsRepo } from "./source-auth-sessions-repo";9export { createSourceAuthSessionsRepo } from "./source-auth-sessions-repo";
packages/control-plane/src/persistence/repos/sources-repo.ts+61−1
@@ -4,7 +4,7 @@ import { · 3 unmodified lines
4} from "#schema";4} from "#schema";
5import * as Option from "effect/Option";5import * as Option from "effect/Option";
6import { Schema } from "effect";6import { Schema } from "effect";
7import { and, asc, eq } from "drizzle-orm";7import { and, asc, eq, or } from "drizzle-orm";
8 8
9import type { DrizzleClient } from "../client";9import type { DrizzleClient } from "../client";
10import type { DrizzleTables } from "../schema";10import type { DrizzleTables } from "../schema";
@@ -111,6 +111,66 @@ export const createSourcesRepo = ( · 100 unmodified lines
111 sourceId: StoredSourceRecord["id"],111 sourceId: StoredSourceRecord["id"],
112 ) =>112 ) =>
113 client.useTx("rows.sources.remove", async (tx) => {113 client.useTx("rows.sources.remove", async (tx) => {
114 const existingToolPaths = (
115 await tx
116 .select({
117 path: tables.toolArtifactsTable.path,
118 })
119 .from(tables.toolArtifactsTable)
120 .where(
121 and(
122 eq(tables.toolArtifactsTable.workspaceId, workspaceId),
123 eq(tables.toolArtifactsTable.sourceId, sourceId),
124 ),
125 )
126 ).map((row) => row.path);
127
128 if (existingToolPaths.length > 0) {
129 await tx
130 .delete(tables.toolArtifactParametersTable)
131 .where(
132 and(
133 eq(tables.toolArtifactParametersTable.workspaceId, workspaceId),
134 or(
135 ...existingToolPaths.map((path) => eq(tables.toolArtifactParametersTable.path, path)),
136 ),
137 ),
138 );
139
140 await tx
141 .delete(tables.toolArtifactRequestBodyContentTypesTable)
142 .where(
143 and(
144 eq(tables.toolArtifactRequestBodyContentTypesTable.workspaceId, workspaceId),
145 or(
146 ...existingToolPaths.map((path) =>
147 eq(tables.toolArtifactRequestBodyContentTypesTable.path, path)
148 ),
149 ),
150 ),
151 );
152
153 await tx
154 .delete(tables.toolArtifactRefHintKeysTable)
155 .where(
156 and(
157 eq(tables.toolArtifactRefHintKeysTable.workspaceId, workspaceId),
158 or(
159 ...existingToolPaths.map((path) => eq(tables.toolArtifactRefHintKeysTable.path, path)),
160 ),
161 ),
162 );
163 }
164
165 await tx
166 .delete(tables.toolArtifactsTable)
167 .where(
168 and(
169 eq(tables.toolArtifactsTable.workspaceId, workspaceId),
170 eq(tables.toolArtifactsTable.sourceId, sourceId),
171 ),
172 );
173
114 await tx174 await tx
115 .delete(tables.sourceCredentialBindingsTable)175 .delete(tables.sourceCredentialBindingsTable)
116 .where(176 .where(
packages/control-plane/src/persistence/repos/tool-artifacts-repo.ts+441−0
@@ -0,0 +1,441 @@
1import {
2 type StoredToolArtifactParameterRecord,
3 StoredToolArtifactParameterRecordSchema,
4 type StoredToolArtifactRecord,
5 StoredToolArtifactRecordSchema,
6 type StoredToolArtifactRefHintKeyRecord,
7 StoredToolArtifactRefHintKeyRecordSchema,
8 type StoredToolArtifactRequestBodyContentTypeRecord,
9 StoredToolArtifactRequestBodyContentTypeRecordSchema,
10} from "#schema";
11import * as Option from "effect/Option";
12import { Schema } from "effect";
13import {
14 and,
15 asc,
16 count,
17 eq,
18 ilike,
19 or,
20} from "drizzle-orm";
21
22import type { DrizzleClient } from "../client";
23import type { DrizzleTables } from "../schema";
24import { firstOption } from "./shared";
25
26const decodeStoredToolArtifactRecord = Schema.decodeUnknownSync(
27 StoredToolArtifactRecordSchema,
28);
29const encodeStoredToolArtifactRecord = Schema.encodeSync(
30 StoredToolArtifactRecordSchema,
31);
32const decodeStoredToolArtifactParameterRecord = Schema.decodeUnknownSync(
33 StoredToolArtifactParameterRecordSchema,
34);
35const encodeStoredToolArtifactParameterRecord = Schema.encodeSync(
36 StoredToolArtifactParameterRecordSchema,
37);
38const decodeStoredToolArtifactRequestBodyContentTypeRecord = Schema.decodeUnknownSync(
39 StoredToolArtifactRequestBodyContentTypeRecordSchema,
40);
41const encodeStoredToolArtifactRequestBodyContentTypeRecord = Schema.encodeSync(
42 StoredToolArtifactRequestBodyContentTypeRecordSchema,
43);
44const decodeStoredToolArtifactRefHintKeyRecord = Schema.decodeUnknownSync(
45 StoredToolArtifactRefHintKeyRecordSchema,
46);
47const encodeStoredToolArtifactRefHintKeyRecord = Schema.encodeSync(
48 StoredToolArtifactRefHintKeyRecordSchema,
49);
50
51type ReplaceableToolArtifactRecord = {
52 artifact: StoredToolArtifactRecord;
53 parameters?: readonly StoredToolArtifactParameterRecord[];
54 requestBodyContentTypes?: readonly StoredToolArtifactRequestBodyContentTypeRecord[];
55 refHintKeys?: readonly StoredToolArtifactRefHintKeyRecord[];
56};
57
58const tokenizeQuery = (value: string | undefined): string[] =>
59 value
60 ?.trim()
61 .toLowerCase()
62 .split(/\s+/)
63 .filter(Boolean)
64 ?? [];
65
66const likePattern = (token: string): string =>
67 `%${token.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
68
69const buildListWhereClause = (
70 table: DrizzleTables["toolArtifactsTable"],
71 input: {
72 workspaceId: StoredToolArtifactRecord["workspaceId"];
73 sourceId?: StoredToolArtifactRecord["sourceId"];
74 namespace?: string;
75 query?: string;
76 },
77) => {
78 const queryTokens = tokenizeQuery(input.query);
79
80 return and(
81 eq(table.workspaceId, input.workspaceId),
82 input.sourceId ? eq(table.sourceId, input.sourceId) : undefined,
83 input.namespace ? eq(table.searchNamespace, input.namespace) : undefined,
84 ...queryTokens.map((token) =>
85 ilike(table.searchText, likePattern(token)),
86 ),
87 );
88};
89
90const buildSearchWhereClause = (
91 table: DrizzleTables["toolArtifactsTable"],
92 input: {
93 workspaceId: StoredToolArtifactRecord["workspaceId"];
94 namespace?: string;
95 query: string;
96 },
97) => {
98 const queryTokens = tokenizeQuery(input.query);
99 if (queryTokens.length === 0) {
100 return and(
101 eq(table.workspaceId, input.workspaceId),
102 input.namespace ? eq(table.searchNamespace, input.namespace) : undefined,
103 eq(table.workspaceId, "__never__"),
104 );
105 }
106
107 return and(
108 eq(table.workspaceId, input.workspaceId),
109 input.namespace ? eq(table.searchNamespace, input.namespace) : undefined,
110 or(...queryTokens.map((token) => ilike(table.searchText, likePattern(token)))),
111 );
112};
113
114export const createToolArtifactsRepo = (
115 client: DrizzleClient,
116 tables: DrizzleTables,
117) => ({
118 listByWorkspaceId: (
119 workspaceId: StoredToolArtifactRecord["workspaceId"],
120 input?: {
121 sourceId?: StoredToolArtifactRecord["sourceId"];
122 namespace?: string;
123 query?: string;
124 limit?: number;
125 },
126 ) =>
127 client.use("rows.tool_artifacts.list_by_workspace", async (db) => {
128 const rows = await db
129 .select()
130 .from(tables.toolArtifactsTable)
131 .where(
132 buildListWhereClause(tables.toolArtifactsTable, {
133 workspaceId,
134 sourceId: input?.sourceId,
135 namespace: input?.namespace,
136 query: input?.query,
137 }),
138 )
139 .orderBy(
140 asc(tables.toolArtifactsTable.searchNamespace),
141 asc(tables.toolArtifactsTable.path),
142 )
143 .limit(input?.limit ?? 200);
144
145 return rows.map((row) => decodeStoredToolArtifactRecord(row));
146 }),
147
148 listNamespacesByWorkspaceId: (
149 workspaceId: StoredToolArtifactRecord["workspaceId"],
150 input?: {
151 limit?: number;
152 },
153 ) =>
154 client.use("rows.tool_artifacts.list_namespaces_by_workspace", async (db) => {
155 const rows = await db
156 .select({
157 namespace: tables.toolArtifactsTable.searchNamespace,
158 toolCount: count(),
159 })
160 .from(tables.toolArtifactsTable)
161 .where(eq(tables.toolArtifactsTable.workspaceId, workspaceId))
162 .groupBy(tables.toolArtifactsTable.searchNamespace)
163 .orderBy(asc(tables.toolArtifactsTable.searchNamespace))
164 .limit(input?.limit ?? 200);
165
166 return rows.map((row) => ({
167 namespace: row.namespace,
168 toolCount: Number(row.toolCount),
169 }));
170 }),
171
172 searchByWorkspaceId: (
173 workspaceId: StoredToolArtifactRecord["workspaceId"],
174 input: {
175 namespace?: string;
176 query: string;
177 limit?: number;
178 },
179 ) =>
180 client.use("rows.tool_artifacts.search_by_workspace", async (db) => {
181 const rows = await db
182 .select()
183 .from(tables.toolArtifactsTable)
184 .where(
185 buildSearchWhereClause(tables.toolArtifactsTable, {
186 workspaceId,
187 namespace: input.namespace,
188 query: input.query,
189 }),
190 )
191 .orderBy(
192 asc(tables.toolArtifactsTable.searchNamespace),
193 asc(tables.toolArtifactsTable.path),
194 )
195 .limit(input.limit ?? 500);
196
197 return rows.map((row) => decodeStoredToolArtifactRecord(row));
198 }),
199
200 getByWorkspaceAndPath: (
201 workspaceId: StoredToolArtifactRecord["workspaceId"],
202 path: StoredToolArtifactRecord["path"],
203 ) =>
204 client.use("rows.tool_artifacts.get_by_workspace_and_path", async (db) => {
205 const rows = await db
206 .select()
207 .from(tables.toolArtifactsTable)
208 .where(
209 and(
210 eq(tables.toolArtifactsTable.workspaceId, workspaceId),
211 eq(tables.toolArtifactsTable.path, path),
212 ),
213 )
214 .limit(1);
215
216 const row = firstOption(rows);
217 return Option.isSome(row)
218 ? Option.some(decodeStoredToolArtifactRecord(row.value))
219 : Option.none<StoredToolArtifactRecord>();
220 }),
221
222 listParametersByWorkspaceAndPath: (
223 workspaceId: StoredToolArtifactRecord["workspaceId"],
224 path: StoredToolArtifactRecord["path"],
225 ) =>
226 client.use("rows.tool_artifacts.list_parameters_by_workspace_and_path", async (db) => {
227 const rows = await db
228 .select()
229 .from(tables.toolArtifactParametersTable)
230 .where(
231 and(
232 eq(tables.toolArtifactParametersTable.workspaceId, workspaceId),
233 eq(tables.toolArtifactParametersTable.path, path),
234 ),
235 )
236 .orderBy(asc(tables.toolArtifactParametersTable.position));
237
238 return rows.map((row) => decodeStoredToolArtifactParameterRecord(row));
239 }),
240
241 listRequestBodyContentTypesByWorkspaceAndPath: (
242 workspaceId: StoredToolArtifactRecord["workspaceId"],
243 path: StoredToolArtifactRecord["path"],
244 ) =>
245 client.use(
246 "rows.tool_artifacts.list_request_body_content_types_by_workspace_and_path",
247 async (db) => {
248 const rows = await db
249 .select()
250 .from(tables.toolArtifactRequestBodyContentTypesTable)
251 .where(
252 and(
253 eq(tables.toolArtifactRequestBodyContentTypesTable.workspaceId, workspaceId),
254 eq(tables.toolArtifactRequestBodyContentTypesTable.path, path),
255 ),
256 )
257 .orderBy(asc(tables.toolArtifactRequestBodyContentTypesTable.position));
258
259 return rows.map((row) => decodeStoredToolArtifactRequestBodyContentTypeRecord(row));
260 },
261 ),
262
263 listRefHintKeysByWorkspaceAndPath: (
264 workspaceId: StoredToolArtifactRecord["workspaceId"],
265 path: StoredToolArtifactRecord["path"],
266 ) =>
267 client.use("rows.tool_artifacts.list_ref_hint_keys_by_workspace_and_path", async (db) => {
268 const rows = await db
269 .select()
270 .from(tables.toolArtifactRefHintKeysTable)
271 .where(
272 and(
273 eq(tables.toolArtifactRefHintKeysTable.workspaceId, workspaceId),
274 eq(tables.toolArtifactRefHintKeysTable.path, path),
275 ),
276 )
277 .orderBy(asc(tables.toolArtifactRefHintKeysTable.position));
278
279 return rows.map((row) => decodeStoredToolArtifactRefHintKeyRecord(row));
280 }),
281
282 replaceForSource: (input: {
283 workspaceId: StoredToolArtifactRecord["workspaceId"];
284 sourceId: StoredToolArtifactRecord["sourceId"];
285 artifacts: readonly ReplaceableToolArtifactRecord[];
286 }) =>
287 client.useTx("rows.tool_artifacts.replace_for_source", async (tx) => {
288 const existingPaths = (
289 await tx
290 .select({
291 path: tables.toolArtifactsTable.path,
292 })
293 .from(tables.toolArtifactsTable)
294 .where(
295 and(
296 eq(tables.toolArtifactsTable.workspaceId, input.workspaceId),
297 eq(tables.toolArtifactsTable.sourceId, input.sourceId),
298 ),
299 )
300 ).map((row) => row.path);
301
302 if (existingPaths.length > 0) {
303 await tx
304 .delete(tables.toolArtifactParametersTable)
305 .where(
306 and(
307 eq(tables.toolArtifactParametersTable.workspaceId, input.workspaceId),
308 or(...existingPaths.map((path) => eq(tables.toolArtifactParametersTable.path, path))),
309 ),
310 );
311 await tx
312 .delete(tables.toolArtifactRequestBodyContentTypesTable)
313 .where(
314 and(
315 eq(
316 tables.toolArtifactRequestBodyContentTypesTable.workspaceId,
317 input.workspaceId,
318 ),
319 or(
320 ...existingPaths.map((path) =>
321 eq(tables.toolArtifactRequestBodyContentTypesTable.path, path)
322 ),
323 ),
324 ),
325 );
326 await tx
327 .delete(tables.toolArtifactRefHintKeysTable)
328 .where(
329 and(
330 eq(tables.toolArtifactRefHintKeysTable.workspaceId, input.workspaceId),
331 or(...existingPaths.map((path) => eq(tables.toolArtifactRefHintKeysTable.path, path))),
332 ),
333 );
334 }
335
336 await tx
337 .delete(tables.toolArtifactsTable)
338 .where(
339 and(
340 eq(tables.toolArtifactsTable.workspaceId, input.workspaceId),
341 eq(tables.toolArtifactsTable.sourceId, input.sourceId),
342 ),
343 );
344
345 if (input.artifacts.length === 0) {
346 return;
347 }
348
349 await tx.insert(tables.toolArtifactsTable).values(
350 input.artifacts.map(({ artifact }) => encodeStoredToolArtifactRecord(artifact)),
351 );
352
353 const parameterRows = input.artifacts.flatMap(({ parameters = [] }) => parameters);
354 if (parameterRows.length > 0) {
355 await tx.insert(tables.toolArtifactParametersTable).values(
356 parameterRows.map((record) => encodeStoredToolArtifactParameterRecord(record)),
357 );
358 }
359
360 const requestBodyContentTypeRows = input.artifacts.flatMap(
361 ({ requestBodyContentTypes = [] }) => requestBodyContentTypes,
362 );
363 if (requestBodyContentTypeRows.length > 0) {
364 await tx.insert(tables.toolArtifactRequestBodyContentTypesTable).values(
365 requestBodyContentTypeRows.map((record) =>
366 encodeStoredToolArtifactRequestBodyContentTypeRecord(record)
367 ),
368 );
369 }
370
371 const refHintKeyRows = input.artifacts.flatMap(({ refHintKeys = [] }) => refHintKeys);
372 if (refHintKeyRows.length > 0) {
373 await tx.insert(tables.toolArtifactRefHintKeysTable).values(
374 refHintKeyRows.map((record) => encodeStoredToolArtifactRefHintKeyRecord(record)),
375 );
376 }
377 }),
378
379 removeByWorkspaceAndSourceId: (
380 workspaceId: StoredToolArtifactRecord["workspaceId"],
381 sourceId: StoredToolArtifactRecord["sourceId"],
382 ) =>
383 client.useTx("rows.tool_artifacts.remove_by_workspace_and_source_id", async (tx) => {
384 const existingPaths = (
385 await tx
386 .select({
387 path: tables.toolArtifactsTable.path,
388 })
389 .from(tables.toolArtifactsTable)
390 .where(
391 and(
392 eq(tables.toolArtifactsTable.workspaceId, workspaceId),
393 eq(tables.toolArtifactsTable.sourceId, sourceId),
394 ),
395 )
396 ).map((row) => row.path);
397
398 if (existingPaths.length > 0) {
399 await tx
400 .delete(tables.toolArtifactParametersTable)
401 .where(
402 and(
403 eq(tables.toolArtifactParametersTable.workspaceId, workspaceId),
404 or(...existingPaths.map((path) => eq(tables.toolArtifactParametersTable.path, path))),
405 ),
406 );
407 await tx
408 .delete(tables.toolArtifactRequestBodyContentTypesTable)
409 .where(
410 and(
411 eq(tables.toolArtifactRequestBodyContentTypesTable.workspaceId, workspaceId),
412 or(
413 ...existingPaths.map((path) =>
414 eq(tables.toolArtifactRequestBodyContentTypesTable.path, path)
415 ),
416 ),
417 ),
418 );
419 await tx
420 .delete(tables.toolArtifactRefHintKeysTable)
421 .where(
422 and(
423 eq(tables.toolArtifactRefHintKeysTable.workspaceId, workspaceId),
424 or(...existingPaths.map((path) => eq(tables.toolArtifactRefHintKeysTable.path, path))),
425 ),
426 );
427 }
428
429 const deleted = await tx
430 .delete(tables.toolArtifactsTable)
431 .where(
432 and(
433 eq(tables.toolArtifactsTable.workspaceId, workspaceId),
434 eq(tables.toolArtifactsTable.sourceId, sourceId),
435 ),
436 )
437 .returning();
438
439 return deleted.length;
440 }),
441});
packages/control-plane/src/persistence/schema.ts+139−0
@@ -16,6 +16,10 @@ export const tableNames = { · 15 unmodified lines
16 organizationMemberships: "organization_memberships",16 organizationMemberships: "organization_memberships",
17 workspaces: "workspaces",17 workspaces: "workspaces",
18 sources: "sources",18 sources: "sources",
19 toolArtifacts: "tool_artifacts",
20 toolArtifactParameters: "tool_artifact_parameters",
21 toolArtifactRequestBodyContentTypes: "tool_artifact_request_body_content_types",
22 toolArtifactRefHintKeys: "tool_artifact_ref_hint_keys",
19 sourceCredentialBindings: "source_credential_bindings",23 sourceCredentialBindings: "source_credential_bindings",
20 secretMaterials: "secret_materials",24 secretMaterials: "secret_materials",
21 sourceAuthSessions: "source_auth_sessions",25 sourceAuthSessions: "source_auth_sessions",
@@ -183,6 +187,137 @@ export const sourceCredentialBindingsTable = pgTable( · 161 unmodified lines
183 ],187 ],
184);188);
185 189
190export const toolArtifactsTable = pgTable(
191 tableNames.toolArtifacts,
192 {
193 workspaceId: text("workspace_id").notNull(),
194 path: text("path").notNull(),
195 toolId: text("tool_id").notNull(),
196 sourceId: text("source_id").notNull(),
197 title: text("title"),
198 description: text("description"),
199 searchNamespace: text("search_namespace").notNull(),
200 searchText: text("search_text").notNull(),
201 inputSchemaJson: text("input_schema_json"),
202 outputSchemaJson: text("output_schema_json"),
203 providerKind: text("provider_kind").notNull(),
204 mcpToolName: text("mcp_tool_name"),
205 openApiMethod: text("openapi_method"),
206 openApiPathTemplate: text("openapi_path_template"),
207 openApiOperationHash: text("openapi_operation_hash"),
208 openApiRequestBodyRequired: boolean("openapi_request_body_required"),
209 createdAt: bigint("created_at", { mode: "number" }).notNull(),
210 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
211 },
212 (table) => [
213 primaryKey({
214 columns: [table.workspaceId, table.path],
215 }),
216 index("tool_artifacts_workspace_source_idx").on(
217 table.workspaceId,
218 table.sourceId,
219 table.updatedAt,
220 table.path,
221 ),
222 index("tool_artifacts_workspace_namespace_idx").on(
223 table.workspaceId,
224 table.searchNamespace,
225 table.path,
226 ),
227 check(
228 "tool_artifacts_provider_kind_check",
229 sql`${table.providerKind} in ('mcp', 'openapi')`,
230 ),
231 check(
232 "tool_artifacts_mcp_shape_check",
233 sql`${table.providerKind} <> 'mcp'
234 or (
235 ${table.mcpToolName} is not null
236 and ${table.openApiMethod} is null
237 and ${table.openApiPathTemplate} is null
238 and ${table.openApiOperationHash} is null
239 and ${table.openApiRequestBodyRequired} is null
240 )`,
241 ),
242 check(
243 "tool_artifacts_openapi_shape_check",
244 sql`${table.providerKind} <> 'openapi'
245 or (
246 ${table.mcpToolName} is null
247 and ${table.openApiMethod} in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
248 and ${table.openApiPathTemplate} is not null
249 and ${table.openApiOperationHash} is not null
250 )`,
251 ),
252 ],
253);
254
255export const toolArtifactParametersTable = pgTable(
256 tableNames.toolArtifactParameters,
257 {
258 workspaceId: text("workspace_id").notNull(),
259 path: text("path").notNull(),
260 position: bigint("position", { mode: "number" }).notNull(),
261 name: text("name").notNull(),
262 location: text("location").notNull(),
263 required: boolean("required").notNull(),
264 },
265 (table) => [
266 primaryKey({
267 columns: [table.workspaceId, table.path, table.position],
268 }),
269 index("tool_artifact_parameters_lookup_idx").on(
270 table.workspaceId,
271 table.path,
272 table.position,
273 ),
274 check(
275 "tool_artifact_parameters_location_check",
276 sql`${table.location} in ('path', 'query', 'header', 'cookie')`,
277 ),
278 ],
279);
280
281export const toolArtifactRequestBodyContentTypesTable = pgTable(
282 tableNames.toolArtifactRequestBodyContentTypes,
283 {
284 workspaceId: text("workspace_id").notNull(),
285 path: text("path").notNull(),
286 position: bigint("position", { mode: "number" }).notNull(),
287 contentType: text("content_type").notNull(),
288 },
289 (table) => [
290 primaryKey({
291 columns: [table.workspaceId, table.path, table.position],
292 }),
293 index("tool_artifact_request_body_content_types_lookup_idx").on(
294 table.workspaceId,
295 table.path,
296 table.position,
297 ),
298 ],
299);
300
301export const toolArtifactRefHintKeysTable = pgTable(
302 tableNames.toolArtifactRefHintKeys,
303 {
304 workspaceId: text("workspace_id").notNull(),
305 path: text("path").notNull(),
306 position: bigint("position", { mode: "number" }).notNull(),
307 refHintKey: text("ref_hint_key").notNull(),
308 },
309 (table) => [
310 primaryKey({
311 columns: [table.workspaceId, table.path, table.position],
312 }),
313 index("tool_artifact_ref_hint_keys_lookup_idx").on(
314 table.workspaceId,
315 table.path,
316 table.position,
317 ),
318 ],
319);
320
186export const secretMaterialsTable = pgTable(321export const secretMaterialsTable = pgTable(
187 tableNames.secretMaterials,322 tableNames.secretMaterials,
188 {323 {
@@ -349,6 +484,10 @@ export const drizzleSchema = { · 160 unmodified lines
349 organizationMembershipsTable,484 organizationMembershipsTable,
350 workspacesTable,485 workspacesTable,
351 sourcesTable,486 sourcesTable,
487 toolArtifactsTable,
488 toolArtifactParametersTable,
489 toolArtifactRequestBodyContentTypesTable,
490 toolArtifactRefHintKeysTable,
352 sourceCredentialBindingsTable,491 sourceCredentialBindingsTable,
353 secretMaterialsTable,492 secretMaterialsTable,
354 sourceAuthSessionsTable,493 sourceAuthSessionsTable,
packages/control-plane/src/runtime/source-auth-service.ts+79−14
@@ -27,6 +27,7 @@ import { · 26 unmodified lines
27 type WorkspaceId,27 type WorkspaceId,
28} from "#schema";28} from "#schema";
29import * as Context from "effect/Context";29import * as Context from "effect/Context";
30import * as Either from "effect/Either";
30import * as Effect from "effect/Effect";31import * as Effect from "effect/Effect";
31import * as Layer from "effect/Layer";32import * as Layer from "effect/Layer";
32import * as Option from "effect/Option";33import * as Option from "effect/Option";
@@ -43,6 +44,11 @@ import { · 10 unmodified lines
43 splitSourceForStorage,44 splitSourceForStorage,
44 updateSourceFromPayload,45 updateSourceFromPayload,
45} from "./source-definitions";46} from "./source-definitions";
47import {
48 createEnvSecretMaterialResolver,
49 persistMcpToolArtifactsFromManifest,
50 syncSourceToolArtifacts,
51} from "./tool-artifacts";
46 52
47export const CONTROL_PLANE_SECRET_PROVIDER_ID = "control-plane";53export const CONTROL_PLANE_SECRET_PROVIDER_ID = "control-plane";
48const OAUTH_CALLBACK_PATH = "/v1/local/oauth/callback";54const OAUTH_CALLBACK_PATH = "/v1/local/oauth/callback";
@@ -505,7 +511,13 @@ export const createRuntimeSourceAuthService = (input: { · 456 unmodified lines
505 rows: SqlControlPlaneRows;511 rows: SqlControlPlaneRows;
506 liveExecutionManager: LiveExecutionManager;512 liveExecutionManager: LiveExecutionManager;
507 getLocalServerBaseUrl?: () => string | undefined;513 getLocalServerBaseUrl?: () => string | undefined;
508}) => ({514}) => {
515 const resolveSecretMaterial = createDbBackedSecretMaterialResolver({
516 rows: input.rows,
517 fallback: createEnvSecretMaterialResolver(),
518 });
519
520 return {
509 getSourceById: ({ workspaceId, sourceId }) =>521 getSourceById: ({ workspaceId, sourceId }) =>
510 loadSourceById(input.rows, {522 loadSourceById(input.rows, {
511 workspaceId,523 workspaceId,
@@ -570,22 +582,52 @@ export const createRuntimeSourceAuthService = (input: { · 58 unmodified lines
570 });582 });
571 583
572 const persistedDraft = yield* persistSource(input.rows, draftSource);584 const persistedDraft = yield* persistSource(input.rows, draftSource);
585 yield* syncSourceToolArtifacts({
586 rows: input.rows,
587 source: persistedDraft,
588 resolveSecretMaterial,
589 });
573 590
574 const discovered = yield* Effect.either(591 const discovered = yield* Effect.either(
575 probeMcpSourceWithoutAuth(persistedDraft),592 probeMcpSourceWithoutAuth(persistedDraft),
576 );593 );
577 594
578 if (discovered._tag === "Right") {595 const connectedResult = yield* Either.match(discovered, {
579 const connected = yield* updateSourceStatus(input.rows, persistedDraft, {596 onLeft: () => Effect.succeed(null),
580 status: "connected",597 onRight: (result) =>
581 lastError: null,598 Effect.gen(function* () {
582 auth: { kind: "none" },599 const connected = yield* updateSourceStatus(input.rows, persistedDraft, {
583 });600 status: "connected",
601 lastError: null,
602 auth: { kind: "none" },
603 });
604 const indexed = yield* Effect.either(
605 persistMcpToolArtifactsFromManifest({
606 rows: input.rows,
607 source: connected,
608 manifestEntries: result.manifest.tools,
609 }),
610 );
611
612 return yield* Either.match(indexed, {
613 onLeft: (error) =>
614 updateSourceStatus(input.rows, connected, {
615 status: "error",
616 lastError: error.message,
617 }).pipe(
618 Effect.zipRight(Effect.fail(error)),
619 ),
620 onRight: () =>
621 Effect.succeed({
622 kind: "connected",
623 source: connected,
624 } satisfies ExecutorSourceAddResult),
625 });
626 }),
627 });
584 628
585 return {629 if (connectedResult) {
586 kind: "connected",630 return connectedResult;
587 source: connected,
588 } satisfies ExecutorSourceAddResult;
589 }631 }
590 632
591 const localServerBaseUrl = input.getLocalServerBaseUrl?.();633 const localServerBaseUrl = input.getLocalServerBaseUrl?.();
@@ -676,10 +718,15 @@ export const createRuntimeSourceAuthService = (input: { · 84 unmodified lines
676 completedAt: failedAt,718 completedAt: failedAt,
677 updatedAt: failedAt,719 updatedAt: failedAt,
678 });720 });
679 yield* updateSourceStatus(input.rows, source, {721 const failedSource = yield* updateSourceStatus(input.rows, source, {
680 status: "error",722 status: "error",
681 lastError: reason,723 lastError: reason,
682 });724 });
725 yield* syncSourceToolArtifacts({
726 rows: input.rows,
727 source: failedSource,
728 resolveSecretMaterial,
729 });
683 yield* completeLiveInteraction({730 yield* completeLiveInteraction({
684 liveExecutionManager: input.liveExecutionManager,731 liveExecutionManager: input.liveExecutionManager,
685 session,732 session,
@@ -743,9 +790,26 @@ export const createRuntimeSourceAuthService = (input: { · 57 unmodified lines
743 : {790 : {
744 providerId: CONTROL_PLANE_SECRET_PROVIDER_ID,791 providerId: CONTROL_PLANE_SECRET_PROVIDER_ID,
745 handle: refreshTokenId,792 handle: refreshTokenId,
746 },793 },
747 },794 },
748 });795 });
796 const indexed = yield* Effect.either(
797 syncSourceToolArtifacts({
798 rows: input.rows,
799 source: connectedSource,
800 resolveSecretMaterial,
801 }),
802 );
803 yield* Either.match(indexed, {
804 onLeft: (error) =>
805 updateSourceStatus(input.rows, connectedSource, {
806 status: "error",
807 lastError: error.message,
808 }).pipe(
809 Effect.zipRight(Effect.fail(error)),
810 ),
811 onRight: () => Effect.succeed(undefined),
812 });
749 813
750 yield* input.rows.sourceAuthSessions.update(session.id, {814 yield* input.rows.sourceAuthSessions.update(session.id, {
751 status: "completed",815 status: "completed",
@@ -769,7 +833,8 @@ export const createRuntimeSourceAuthService = (input: { · 17 unmodified lines
769 833
770 return connectedSource;834 return connectedSource;
771 }),835 }),
772} satisfies RuntimeSourceAuthServiceShape);836 } satisfies RuntimeSourceAuthServiceShape;
837};
773 838
774export type RuntimeSourceAuthService = ReturnType<839export type RuntimeSourceAuthService = ReturnType<
775 typeof createRuntimeSourceAuthService840 typeof createRuntimeSourceAuthService
packages/control-plane/src/runtime/sources-operations.ts+92−6
@@ -4,9 +4,11 @@ import type { · 3 unmodified lines
4} from "../api/sources/api";4} from "../api/sources/api";
5import {5import {
6 SourceIdSchema,6 SourceIdSchema,
7 type Source,
7 type SourceId,8 type SourceId,
8 type WorkspaceId,9 type WorkspaceId,
9} from "#schema";10} from "#schema";
11import * as Either from "effect/Either";
10import * as Effect from "effect/Effect";12import * as Effect from "effect/Effect";
11import * as Option from "effect/Option";13import * as Option from "effect/Option";
12 14
@@ -23,7 +25,14 @@ import { · 10 unmodified lines
23import {25import {
24 operationErrors,26 operationErrors,
25} from "./operation-errors";27} from "./operation-errors";
26import { ControlPlaneStore } from "./store";28import {
29 createDbBackedSecretMaterialResolver,
30} from "./source-auth-service";
31import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";
32import {
33 createEnvSecretMaterialResolver,
34 syncSourceToolArtifacts,
35} from "./tool-artifacts";
27 36
28const sourceOps = {37const sourceOps = {
29 list: operationErrors("sources.list"),38 list: operationErrors("sources.list"),
@@ -33,6 +42,67 @@ const sourceOps = { · 3 unmodified lines
33 remove: operationErrors("sources.remove"),42 remove: operationErrors("sources.remove"),
34} as const;43} as const;
35 44
45const syncArtifactsForSource = (input: {
46 store: ControlPlaneStoreShape;
47 source: Source;
48 operation:
49 | typeof sourceOps.create
50 | typeof sourceOps.update;
51}) =>
52 Effect.gen(function* () {
53 const resolveSecretMaterial = createDbBackedSecretMaterialResolver({
54 rows: input.store,
55 fallback: createEnvSecretMaterialResolver(),
56 });
57
58 const synced = yield* Effect.either(
59 syncSourceToolArtifacts({
60 rows: input.store,
61 source: input.source,
62 resolveSecretMaterial,
63 }),
64 );
65
66 return yield* Either.match(synced, {
67 onRight: () => Effect.succeed(input.source),
68 onLeft: (error) =>
69 Effect.gen(function* () {
70 if (input.source.enabled && input.source.status === "connected") {
71 const erroredSource = yield* updateSourceFromPayload({
72 source: input.source,
73 payload: {
74 status: "error",
75 lastError: error.message,
76 },
77 now: Date.now(),
78 }).pipe(
79 Effect.mapError((cause) =>
80 input.operation.badRequest(
81 "Failed indexing source tools",
82 cause instanceof Error ? cause.message : String(cause),
83 ),
84 ),
85 );
86
87 const { sourceRecord } = splitSourceForStorage({
88 source: erroredSource,
89 });
90 yield* mapPersistenceError(
91 input.operation.child("source_error"),
92 input.store.sources.update(input.source.workspaceId, input.source.id, {
93 ...sourceRecord,
94 updatedAt: erroredSource.updatedAt,
95 }),
96 );
97 }
98
99 return yield* Effect.fail(
100 input.operation.unknownStorage(error, "Failed syncing source tools"),
101 );
102 }),
103 });
104 });
105
36export const listSources = (workspaceId: WorkspaceId) =>106export const listSources = (workspaceId: WorkspaceId) =>
37 Effect.flatMap(ControlPlaneStore, (store) =>107 Effect.flatMap(ControlPlaneStore, (store) =>
38 Effect.gen(function* () {108 Effect.gen(function* () {
@@ -93,7 +163,11 @@ export const createSource = (input: { · 54 unmodified lines
93 );163 );
94 }164 }
95 165
96 return source;166 return yield* syncArtifactsForSource({
167 store,
168 source,
169 operation: sourceOps.create,
170 });
97 }));171 }));
98 172
99export const getSource = (input: {173export const getSource = (input: {
@@ -222,7 +296,11 @@ export const updateSource = (input: { · 122 unmodified lines
222 );296 );
223 }297 }
224 298
225 return updatedSource;299 return yield* syncArtifactsForSource({
300 store,
301 source: updatedSource,
302 operation: sourceOps.update,
303 });
226 }));304 }));
227 305
228export const removeSource = (input: {306export const removeSource = (input: {
@@ -230,7 +308,15 @@ export const removeSource = (input: { · 1 unmodified line
230 sourceId: SourceId;308 sourceId: SourceId;
231}) =>309}) =>
232 Effect.flatMap(ControlPlaneStore, (store) =>310 Effect.flatMap(ControlPlaneStore, (store) =>
233 sourceOps.remove.mapStorage(311 Effect.gen(function* () {
234 store.sources.removeByWorkspaceAndId(input.workspaceId, input.sourceId),312 yield* sourceOps.remove.child("artifacts").mapStorage(
235 ).pipe(Effect.map((removed) => ({ removed })))313 store.toolArtifacts.removeByWorkspaceAndSourceId(input.workspaceId, input.sourceId),
314 );
315
316 const removed = yield* sourceOps.remove.mapStorage(
317 store.sources.removeByWorkspaceAndId(input.workspaceId, input.sourceId),
318 );
319
320 return { removed };
321 })
236 );322 );
packages/control-plane/src/runtime/tool-artifacts.ts+393−0
@@ -0,0 +1,393 @@
1import {
2 createSdkMcpConnector,
3 discoverMcpToolsFromConnector,
4 type McpToolManifestEntry,
5} from "@executor-v3/codemode-mcp";
6import {
7 extractOpenApiManifest,
8 fetchOpenApiDocument,
9 type OpenApiExtractedTool,
10} from "@executor-v3/codemode-openapi";
11import type { SqlControlPlaneRows } from "#persistence";
12import {
13 type SecretRef,
14 type Source,
15 type StoredToolArtifactParameterRecord,
16 type StoredToolArtifactRecord,
17 type StoredToolArtifactRefHintKeyRecord,
18 type StoredToolArtifactRequestBodyContentTypeRecord,
19} from "#schema";
20import * as Effect from "effect/Effect";
21
22export type ResolveSourceSecretMaterial = (
23 ref: SecretRef,
24) => Effect.Effect<string, Error, never>;
25
26export type ResolvedSourceAuthMaterial = {
27 headers: Readonly<Record<string, string>>;
28};
29
30const trim = (value: string | undefined): string | undefined => {
31 const candidate = value?.trim();
32 return candidate && candidate.length > 0 ? candidate : undefined;
33};
34
35const joinToolPath = (namespace: string | null, toolId: string): string => {
36 const normalizedNamespace = trim(namespace ?? undefined);
37 return normalizedNamespace ? `${normalizedNamespace}.${toolId}` : toolId;
38};
39
40const normalizeSearchText = (...parts: ReadonlyArray<string | null | undefined>): string =>
41 parts
42 .flatMap((part) => (part ? [part.trim()] : []))
43 .filter(Boolean)
44 .join(" ")
45 .toLowerCase();
46
47const catalogNamespaceFromPath = (path: string): string => {
48 const [first, second] = path.split(".");
49 return second ? `${first}.${second}` : first;
50};
51
52type IndexedToolArtifactRecord = {
53 artifact: StoredToolArtifactRecord;
54 parameters?: readonly StoredToolArtifactParameterRecord[];
55 requestBodyContentTypes?: readonly StoredToolArtifactRequestBodyContentTypeRecord[];
56 refHintKeys?: readonly StoredToolArtifactRefHintKeyRecord[];
57};
58
59const toMcpToolArtifactRecord = (input: {
60 workspaceId: Source["workspaceId"];
61 source: Source;
62 entry: {
63 toolId: string;
64 toolName: string;
65 description: string | null;
66 inputSchemaJson?: string;
67 outputSchemaJson?: string;
68 };
69 now: number;
70}): IndexedToolArtifactRecord => {
71 const sourceNamespace = input.source.namespace ?? namespaceFromSourceName(input.source.name);
72 const path = joinToolPath(sourceNamespace, input.entry.toolId);
73 const searchNamespace = catalogNamespaceFromPath(path);
74
75 return {
76 artifact: {
77 workspaceId: input.workspaceId,
78 path,
79 toolId: input.entry.toolId,
80 sourceId: input.source.id,
81 title: input.entry.toolName,
82 description: input.entry.description ?? null,
83 searchNamespace,
84 searchText: normalizeSearchText(
85 path,
86 searchNamespace,
87 input.entry.toolName,
88 input.entry.description ?? undefined,
89 ),
90 inputSchemaJson: input.entry.inputSchemaJson ?? null,
91 outputSchemaJson: input.entry.outputSchemaJson ?? null,
92 providerKind: "mcp",
93 mcpToolName: input.entry.toolName,
94 openApiMethod: null,
95 openApiPathTemplate: null,
96 openApiOperationHash: null,
97 openApiRequestBodyRequired: null,
98 createdAt: input.now,
99 updatedAt: input.now,
100 },
101 };
102};
103
104const toOpenApiToolArtifactRecord = (input: {
105 workspaceId: Source["workspaceId"];
106 source: Source;
107 extracted: OpenApiExtractedTool;
108 now: number;
109}): IndexedToolArtifactRecord => {
110 const sourceNamespace = input.source.namespace ?? namespaceFromSourceName(input.source.name);
111 const path = joinToolPath(sourceNamespace, input.extracted.toolId);
112 const searchNamespace = catalogNamespaceFromPath(path);
113 const description =
114 input.extracted.description
115 ?? `${input.extracted.method.toUpperCase()} ${input.extracted.path}`;
116
117 return {
118 artifact: {
119 workspaceId: input.workspaceId,
120 path,
121 toolId: input.extracted.toolId,
122 sourceId: input.source.id,
123 title: input.extracted.name,
124 description,
125 searchNamespace,
126 searchText: normalizeSearchText(
127 path,
128 searchNamespace,
129 input.extracted.name,
130 description,
131 input.extracted.method.toUpperCase(),
132 input.extracted.path,
133 ),
134 inputSchemaJson: input.extracted.typing?.inputSchemaJson ?? null,
135 outputSchemaJson: input.extracted.typing?.outputSchemaJson ?? null,
136 providerKind: "openapi",
137 mcpToolName: null,
138 openApiMethod: input.extracted.method,
139 openApiPathTemplate: input.extracted.invocation.pathTemplate,
140 openApiOperationHash: input.extracted.operationHash,
141 openApiRequestBodyRequired: input.extracted.invocation.requestBody?.required ?? null,
142 createdAt: input.now,
143 updatedAt: input.now,
144 },
145 parameters: input.extracted.invocation.parameters.map((parameter, position) => ({
146 workspaceId: input.workspaceId,
147 path,
148 position,
149 name: parameter.name,
150 location: parameter.location,
151 required: parameter.required,
152 })),
153 requestBodyContentTypes:
154 input.extracted.invocation.requestBody?.contentTypes.map((contentType, position) => ({
155 workspaceId: input.workspaceId,
156 path,
157 position,
158 contentType,
159 })) ?? [],
160 refHintKeys:
161 input.extracted.typing?.refHintKeys?.map((refHintKey, position) => ({
162 workspaceId: input.workspaceId,
163 path,
164 position,
165 refHintKey,
166 })) ?? [],
167 };
168};
169
170const shouldIndexSource = (source: Source): boolean =>
171 source.enabled
172 && source.status === "connected"
173 && (source.kind === "mcp" || source.kind === "openapi");
174
175export const namespaceFromSourceName = (name: string): string => {
176 const normalized = name
177 .trim()
178 .toLowerCase()
179 .replace(/[^a-z0-9]+/g, ".")
180 .replace(/^\.+|\.+$/g, "");
181
182 return normalized.length > 0 ? normalized : "source";
183};
184
185export const createEnvSecretMaterialResolver = (): ResolveSourceSecretMaterial =>
186 (ref) =>
187 Effect.gen(function* () {
188 if (ref.providerId !== "env") {
189 return yield* Effect.fail(
190 new Error(`Unsupported secret provider ${ref.providerId}`),
191 );
192 }
193
194 const value = process.env[ref.handle]?.trim();
195 if (!value) {
196 return yield* Effect.fail(
197 new Error(`Environment variable ${ref.handle} is not set`),
198 );
199 }
200
201 return value;
202 });
203
204export const resolveSourceAuthMaterial = (input: {
205 source: Source;
206 resolveSecretMaterial: ResolveSourceSecretMaterial;
207}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
208 Effect.gen(function* () {
209 if (input.source.auth.kind === "none") {
210 return { headers: {} } satisfies ResolvedSourceAuthMaterial;
211 }
212
213 const tokenRef =
214 input.source.auth.kind === "bearer"
215 ? input.source.auth.token
216 : input.source.auth.accessToken;
217
218 const token = yield* input.resolveSecretMaterial(tokenRef);
219
220 return {
221 headers: {
222 [input.source.auth.headerName]: `${input.source.auth.prefix}${token}`,
223 },
224 } satisfies ResolvedSourceAuthMaterial;
225 });
226
227const indexMcpSourceToolArtifacts = (input: {
228 rows: SqlControlPlaneRows;
229 source: Source;
230 manifestEntries: readonly McpToolManifestEntry[];
231}): Effect.Effect<void, Error, never> =>
232 Effect.gen(function* () {
233 const now = Date.now();
234 yield* input.rows.toolArtifacts.replaceForSource({
235 workspaceId: input.source.workspaceId,
236 sourceId: input.source.id,
237 artifacts: input.manifestEntries.map((entry) =>
238 toMcpToolArtifactRecord({
239 workspaceId: input.source.workspaceId,
240 source: input.source,
241 entry,
242 now,
243 })
244 ),
245 }).pipe(
246 Effect.mapError((cause) =>
247 cause instanceof Error ? cause : new Error(String(cause)),
248 ),
249 );
250 });
251
252const discoverAndIndexMcpSourceToolArtifacts = (input: {
253 rows: SqlControlPlaneRows;
254 source: Source;
255 auth: ResolvedSourceAuthMaterial;
256}): Effect.Effect<void, Error, never> =>
257 Effect.gen(function* () {
258 const connector = yield* Effect.try({
259 try: () =>
260 createSdkMcpConnector({
261 endpoint: input.source.endpoint,
262 transport: input.source.transport ?? undefined,
263 queryParams: input.source.queryParams ?? undefined,
264 headers: {
265 ...(input.source.headers ?? {}),
266 ...input.auth.headers,
267 },
268 }),
269 catch: (cause) =>
270 cause instanceof Error ? cause : new Error(String(cause)),
271 });
272
273 const discovered = yield* discoverMcpToolsFromConnector({
274 connect: connector,
275 namespace: input.source.namespace ?? namespaceFromSourceName(input.source.name),
276 sourceKey: input.source.id,
277 }).pipe(
278 Effect.mapError(
279 (cause) =>
280 new Error(
281 `Failed discovering MCP tools for ${input.source.id}: ${cause.message}`,
282 ),
283 ),
284 );
285
286 return yield* indexMcpSourceToolArtifacts({
287 rows: input.rows,
288 source: input.source,
289 manifestEntries: discovered.manifest.tools,
290 });
291 });
292
293const indexOpenApiSourceToolArtifacts = (input: {
294 rows: SqlControlPlaneRows;
295 source: Source;
296}): Effect.Effect<void, Error, never> =>
297 Effect.gen(function* () {
298 if (!input.source.specUrl) {
299 return yield* Effect.fail(
300 new Error(`Missing OpenAPI specUrl for source ${input.source.id}`),
301 );
302 }
303
304 const openApiDocument = yield* Effect.tryPromise({
305 try: () => fetchOpenApiDocument(input.source.specUrl!),
306 catch: (cause) =>
307 cause instanceof Error
308 ? new Error(
309 `Failed fetching OpenAPI spec for ${input.source.id}: ${cause.message}`,
310 )
311 : new Error(`Failed fetching OpenAPI spec for ${input.source.id}: ${String(cause)}`),
312 });
313
314 const manifest = yield* extractOpenApiManifest(
315 input.source.name,
316 openApiDocument,
317 ).pipe(
318 Effect.mapError((cause) =>
319 cause instanceof Error
320 ? cause
321 : new Error(String(cause)),
322 ),
323 );
324
325 const now = Date.now();
326 yield* input.rows.toolArtifacts.replaceForSource({
327 workspaceId: input.source.workspaceId,
328 sourceId: input.source.id,
329 artifacts: manifest.tools.map((extracted) =>
330 toOpenApiToolArtifactRecord({
331 workspaceId: input.source.workspaceId,
332 source: input.source,
333 extracted,
334 now,
335 })
336 ),
337 }).pipe(
338 Effect.mapError((cause) =>
339 cause instanceof Error ? cause : new Error(String(cause)),
340 ),
341 );
342 });
343
344export const syncSourceToolArtifacts = (input: {
345 rows: SqlControlPlaneRows;
346 source: Source;
347 resolveSecretMaterial: ResolveSourceSecretMaterial;
348}): Effect.Effect<void, Error, never> =>
349 Effect.gen(function* () {
350 if (!shouldIndexSource(input.source)) {
351 yield* input.rows.toolArtifacts.removeByWorkspaceAndSourceId(
352 input.source.workspaceId,
353 input.source.id,
354 ).pipe(
355 Effect.mapError((cause) =>
356 cause instanceof Error ? cause : new Error(String(cause)),
357 ),
358 );
359 return;
360 }
361
362 const auth = yield* resolveSourceAuthMaterial({
363 source: input.source,
364 resolveSecretMaterial: input.resolveSecretMaterial,
365 });
366
367 if (input.source.kind === "mcp") {
368 return yield* discoverAndIndexMcpSourceToolArtifacts({
369 rows: input.rows,
370 source: input.source,
371 auth,
372 });
373 }
374
375 if (input.source.kind === "openapi") {
376 return yield* indexOpenApiSourceToolArtifacts({
377 rows: input.rows,
378 source: input.source,
379 });
380 }
381
382 return;
383 });
384
385export const storedToolIdFromArtifact = (artifact: StoredToolArtifactRecord): string =>
386 artifact.toolId;
387
388export const persistMcpToolArtifactsFromManifest = (input: {
389 rows: SqlControlPlaneRows;
390 source: Source;
391 manifestEntries: readonly McpToolManifestEntry[];
392}): Effect.Effect<void, Error, never> =>
393 indexMcpSourceToolArtifacts(input);
packages/control-plane/src/runtime/workspace-execution-environment.test.ts+410−0
@@ -0,0 +1,410 @@
1import { randomUUID } from "node:crypto";
2
3import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
4import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6import { describe, expect, it } from "@effect/vitest";
7import {
8 AccountIdSchema,
9 ExecutionIdSchema,
10 SourceIdSchema,
11 WorkspaceIdSchema,
12} from "#schema";
13import * as Effect from "effect/Effect";
14import { z } from "zod/v4";
15
16import {
17 createSqlControlPlanePersistence,
18 type SqlControlPlanePersistence,
19} from "#persistence";
20import type { SourceId, WorkspaceId } from "#schema";
21
22import type { RuntimeSourceAuthService } from "./source-auth-service";
23import { createWorkspaceExecutionEnvironmentResolver } from "./workspace-execution-environment";
24
25type CountedMcpServer = {
26 endpoint: string;
27 counts: {
28 listTools: number;
29 callTool: number;
30 };
31 close: () => Promise<void>;
32};
33
34const makePersistence = Effect.acquireRelease(
35 createSqlControlPlanePersistence({
36 localDataDir: ":memory:",
37 }),
38 (persistence) =>
39 Effect.tryPromise({
40 try: () => persistence.close(),
41 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
42 }).pipe(Effect.orDie),
43);
44
45const makeCountedMcpServer = Effect.acquireRelease(
46 Effect.promise<CountedMcpServer>(
47 () =>
48 new Promise<CountedMcpServer>((resolve, reject) => {
49 const counts = {
50 listTools: 0,
51 callTool: 0,
52 };
53 let closed = false;
54 const app = createMcpExpressApp({ host: "127.0.0.1" });
55 const transports: Record<string, StreamableHTTPServerTransport> = {};
56 const servers: Record<string, McpServer> = {};
57
58 const createServer = () => {
59 const server = new McpServer(
60 {
61 name: "workspace-execution-environment-test-server",
62 version: "1.0.0",
63 },
64 {
65 capabilities: {
66 tools: {},
67 },
68 },
69 );
70
71 server.registerTool(
72 "echo",
73 {
74 description: "Echo the provided string",
75 inputSchema: {
76 value: z.string(),
77 },
78 },
79 async ({ value }: { value: string }) => ({
80 content: [{ type: "text", text: `echo:${value}` }],
81 }),
82 );
83
84 return server;
85 };
86
87 const countMethods = (body: unknown) => {
88 const payloads = Array.isArray(body) ? body : [body];
89 for (const payload of payloads) {
90 if (!payload || typeof payload !== "object") {
91 continue;
92 }
93
94 const method = "method" in payload ? payload.method : undefined;
95 if (method === "tools/list") {
96 counts.listTools += 1;
97 } else if (method === "tools/call") {
98 counts.callTool += 1;
99 }
100 }
101 };
102
103 app.post("/mcp", async (req: any, res: any) => {
104 countMethods(req.body);
105
106 const sessionIdHeader = req.headers["mcp-session-id"];
107 const sessionId =
108 typeof sessionIdHeader === "string"
109 ? sessionIdHeader
110 : Array.isArray(sessionIdHeader)
111 ? sessionIdHeader[0]
112 : undefined;
113
114 try {
115 let transport: StreamableHTTPServerTransport;
116
117 if (sessionId && transports[sessionId]) {
118 transport = transports[sessionId];
119 } else {
120 transport = new StreamableHTTPServerTransport({
121 sessionIdGenerator: () => randomUUID(),
122 onsessioninitialized: (newSessionId) => {
123 transports[newSessionId] = transport;
124 },
125 });
126
127 transport.onclose = () => {
128 const closedSessionId = transport.sessionId;
129 if (closedSessionId && transports[closedSessionId]) {
130 delete transports[closedSessionId];
131 }
132 if (closedSessionId && servers[closedSessionId]) {
133 void servers[closedSessionId].close().catch(() => undefined);
134 delete servers[closedSessionId];
135 }
136 };
137
138 const server = createServer();
139 await server.connect(transport);
140 const newSessionId = transport.sessionId;
141 if (newSessionId) {
142 servers[newSessionId] = server;
143 }
144 }
145
146 await transport.handleRequest(req, res, req.body);
147 } catch (error) {
148 if (!res.headersSent) {
149 res.status(500).json({
150 jsonrpc: "2.0",
151 error: {
152 code: -32603,
153 message:
154 error instanceof Error ? error.message : "Internal server error",
155 },
156 id: null,
157 });
158 }
159 }
160 });
161
162 app.get("/mcp", async (req: any, res: any) => {
163 const sessionIdHeader = req.headers["mcp-session-id"];
164 const sessionId =
165 typeof sessionIdHeader === "string"
166 ? sessionIdHeader
167 : Array.isArray(sessionIdHeader)
168 ? sessionIdHeader[0]
169 : undefined;
170
171 if (!sessionId || !transports[sessionId]) {
172 res.status(400).send("Invalid or missing session ID");
173 return;
174 }
175
176 await transports[sessionId].handleRequest(req, res);
177 });
178
179 app.delete("/mcp", async (req: any, res: any) => {
180 const sessionIdHeader = req.headers["mcp-session-id"];
181 const sessionId =
182 typeof sessionIdHeader === "string"
183 ? sessionIdHeader
184 : Array.isArray(sessionIdHeader)
185 ? sessionIdHeader[0]
186 : undefined;
187
188 if (!sessionId || !transports[sessionId]) {
189 res.status(400).send("Invalid or missing session ID");
190 return;
191 }
192
193 const transport = transports[sessionId];
194 await transport.handleRequest(req, res, req.body);
195 await transport.close();
196 delete transports[sessionId];
197
198 if (servers[sessionId]) {
199 await servers[sessionId].close().catch(() => undefined);
200 delete servers[sessionId];
201 }
202 });
203
204 const listener = app.listen(0, "127.0.0.1", () => {
205 const address = listener.address();
206 if (!address || typeof address === "string") {
207 reject(new Error("failed to resolve MCP test server address"));
208 return;
209 }
210
211 resolve({
212 endpoint: `http://127.0.0.1:${address.port}/mcp`,
213 counts,
214 close: async () => {
215 if (closed) {
216 return;
217 }
218 closed = true;
219
220 for (const transport of Object.values(transports)) {
221 await transport.close().catch(() => undefined);
222 }
223
224 for (const server of Object.values(servers)) {
225 await server.close().catch(() => undefined);
226 }
227
228 await new Promise<void>((closeResolve, closeReject) => {
229 listener.close((error: Error | undefined) => {
230 if (error) {
231 closeReject(error);
232 return;
233 }
234 closeResolve();
235 });
236 });
237 },
238 });
239 });
240
241 listener.once("error", reject);
242 }),
243 ),
244 (server) =>
245 Effect.tryPromise({
246 try: () => server.close(),
247 catch: (error: unknown) =>
248 error instanceof Error ? error : new Error(String(error)),
249 }).pipe(Effect.orDie),
250);
251
252const persistConnectedEchoTool = (input: {
253 persistence: SqlControlPlanePersistence;
254 endpoint: string;
255 workspaceId: WorkspaceId;
256 sourceId: SourceId;
257}) =>
258 Effect.gen(function* () {
259 const now = Date.now();
260
261 yield* input.persistence.rows.sources.insert({
262 id: input.sourceId,
263 workspaceId: input.workspaceId,
264 name: "Counted MCP",
265 kind: "mcp",
266 endpoint: input.endpoint,
267 status: "connected",
268 enabled: true,
269 namespace: "counted",
270 transport: "auto",
271 queryParamsJson: null,
272 headersJson: null,
273 specUrl: null,
274 defaultHeadersJson: null,
275 authKind: "none",
276 authHeaderName: null,
277 authPrefix: null,
278 sourceHash: null,
279 lastError: null,
280 createdAt: now,
281 updatedAt: now,
282 });
283
284 yield* input.persistence.rows.toolArtifacts.replaceForSource({
285 workspaceId: input.workspaceId,
286 sourceId: input.sourceId,
287 artifacts: [{
288 artifact: {
289 workspaceId: input.workspaceId,
290 path: "counted.echo",
291 toolId: "echo",
292 sourceId: input.sourceId,
293 title: "echo",
294 description: "Echo the provided string",
295 searchNamespace: "counted.echo",
296 searchText: "counted.echo echo provided string",
297 inputSchemaJson: JSON.stringify({
298 type: "object",
299 properties: {
300 value: {
301 type: "string",
302 },
303 },
304 required: ["value"],
305 additionalProperties: false,
306 }),
307 outputSchemaJson: null,
308 providerKind: "mcp",
309 mcpToolName: "echo",
310 openApiMethod: null,
311 openApiPathTemplate: null,
312 openApiOperationHash: null,
313 openApiRequestBodyRequired: null,
314 createdAt: now,
315 updatedAt: now,
316 },
317 }],
318 });
319 });
320
321const makeResolver = (persistence: SqlControlPlanePersistence) =>
322 createWorkspaceExecutionEnvironmentResolver({
323 rows: persistence.rows,
324 sourceAuthService: {
325 getSourceById: () => Effect.fail(new Error("not implemented in test")),
326 addExecutorMcpSource: () => Effect.fail(new Error("not implemented in test")),
327 completeSourceAuthCallback: () => Effect.fail(new Error("not implemented in test")),
328 } as RuntimeSourceAuthService,
329 });
330
331describe("workspace-execution-environment", () => {
332 it.scoped("discovers persisted tools without live MCP listing", () =>
333 Effect.gen(function* () {
334 const server = yield* makeCountedMcpServer;
335 const persistence = yield* makePersistence;
336 const workspaceId = WorkspaceIdSchema.make("ws_catalog");
337 const sourceId = SourceIdSchema.make("src_catalog");
338 yield* persistConnectedEchoTool({
339 persistence,
340 endpoint: server.endpoint,
341 workspaceId,
342 sourceId,
343 });
344
345 yield* Effect.tryPromise({
346 try: () => server.close(),
347 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
348 }).pipe(Effect.orDie);
349
350 const resolveEnvironment = makeResolver(persistence);
351 const environment = yield* resolveEnvironment({
352 workspaceId,
353 accountId: AccountIdSchema.make("acc_catalog"),
354 executionId: ExecutionIdSchema.make("exec_catalog"),
355 });
356
357 const discovered = (yield* environment.toolInvoker.invoke({
358 path: "discover",
359 args: {
360 query: "echo string",
361 limit: 5,
362 },
363 })) as {
364 bestPath: string | null;
365 total: number;
366 results: Array<{ path: string }>;
367 };
368
369 expect(discovered.bestPath).toBe("counted.echo");
370 expect(discovered.total).toBeGreaterThan(0);
371 expect(server.counts.listTools).toBe(0);
372 expect(server.counts.callTool).toBe(0);
373 }),
374 );
375
376 it.scoped("invokes persisted MCP tools on demand without re-listing", () =>
377 Effect.gen(function* () {
378 const server = yield* makeCountedMcpServer;
379 const persistence = yield* makePersistence;
380 const workspaceId = WorkspaceIdSchema.make("ws_invoke");
381 const sourceId = SourceIdSchema.make("src_invoke");
382 yield* persistConnectedEchoTool({
383 persistence,
384 endpoint: server.endpoint,
385 workspaceId,
386 sourceId,
387 });
388
389 const resolveEnvironment = makeResolver(persistence);
390 const environment = yield* resolveEnvironment({
391 workspaceId,
392 accountId: AccountIdSchema.make("acc_invoke"),
393 executionId: ExecutionIdSchema.make("exec_invoke"),
394 });
395
396 const result = (yield* environment.toolInvoker.invoke({
397 path: "counted.echo",
398 args: {
399 value: "hello",
400 },
401 })) as {
402 content?: Array<{ text?: string }>;
403 };
404
405 expect(server.counts.listTools).toBe(0);
406 expect(server.counts.callTool).toBe(1);
407 expect(result.content?.[0]?.text).toBe("echo:hello");
408 }),
409 );
410});
packages/control-plane/src/runtime/workspace-execution-environment.ts+433−216
@@ -1,17 +1,20 @@
1import {1import {
2 type ToolMap,
3 createToolCatalogFromTools,
4 createSystemToolMap,2 createSystemToolMap,
3 createToolCatalogFromTools,
5 makeToolInvokerFromTools,4 makeToolInvokerFromTools,
6 mergeToolMaps,5 type SearchHit,
6 type ToolCatalog,
7 type ToolDescriptor,
8 type ToolInvoker,
9 type ToolNamespace,
10 type ToolPath,
7} from "@executor-v3/codemode-core";11} from "@executor-v3/codemode-core";
8import {12import {
9 createSdkMcpConnector,13 createSdkMcpConnector,
10 discoverMcpToolsFromConnector,14 createMcpToolsFromManifest,
11} from "@executor-v3/codemode-mcp";15} from "@executor-v3/codemode-mcp";
12import {16import {
13 createOpenApiToolsFromSpec,17 createOpenApiToolsFromManifest,
14 fetchOpenApiDocument,
15} from "@executor-v3/codemode-openapi";18} from "@executor-v3/codemode-openapi";
16import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";19import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";
17import {20import {
@@ -20,192 +23,468 @@ import { · 2 unmodified lines
20} from "#persistence";23} from "#persistence";
21import type {24import type {
22 Source,25 Source,
26 StoredToolArtifactRecord,
23} from "#schema";27} from "#schema";
24import * as Context from "effect/Context";28import * as Context from "effect/Context";
25import * as Effect from "effect/Effect";29import * as Effect from "effect/Effect";
26import * as Layer from "effect/Layer";30import * as Layer from "effect/Layer";
31import * as Option from "effect/Option";
27 32
28import type {33import type {
29 ExecutionEnvironment,34 ExecutionEnvironment,
30 ResolveExecutionEnvironment,35 ResolveExecutionEnvironment,
31} from "./execution-state";36} from "./execution-state";
32import { createExecutorToolMap } from "./executor-tools";37import { createExecutorToolMap } from "./executor-tools";
33import { projectSourcesFromStorage } from "./source-definitions";38import { projectSourceFromStorage } from "./source-definitions";
34import {39import {
35 RuntimeSourceAuthServiceTag,40 RuntimeSourceAuthServiceTag,
36 createDbBackedSecretMaterialResolver,41 createDbBackedSecretMaterialResolver,
37 type ResolveSecretMaterial,42 type ResolveSecretMaterial,
38 type RuntimeSourceAuthService,43 type RuntimeSourceAuthService,
39} from "./source-auth-service";44} from "./source-auth-service";
45import {
46 createEnvSecretMaterialResolver,
47 namespaceFromSourceName,
48 resolveSourceAuthMaterial,
49 storedToolIdFromArtifact,
50} from "./tool-artifacts";
40 51
41export type ResolvedSourceAuthMaterial = {52const asToolPath = (value: string): ToolPath => value as ToolPath;
42 headers: Readonly<Record<string, string>>;
43};
44 53
45const namespaceFromSourceName = (name: string): string => {54const tokenize = (value: string): string[] =>
46 const normalized = name55 value
47 .trim()56 .trim()
48 .toLowerCase()57 .toLowerCase()
49 .replace(/[^a-z0-9]+/g, ".")58 .split(/\s+/)
50 .replace(/^\.+|\.+$/g, "");59 .filter(Boolean);
60
61const scoreArtifact = (
62 queryTokens: readonly string[],
63 searchText: string,
64): number =>
65 queryTokens.reduce(
66 (total, token) => total + (searchText.includes(token) ? 1 : 0),
67 0,
68 );
69
70const toDescriptor = (input: {
71 artifact: StoredToolArtifactRecord;
72 includeSchemas: boolean;
73 refHintKeys?: readonly string[];
74}): ToolDescriptor => ({
75 path: asToolPath(input.artifact.path),
76 sourceKey: input.artifact.sourceId,
77 description: input.artifact.description ?? input.artifact.title ?? undefined,
78 interaction: "auto",
79 inputHint: input.artifact.inputSchemaJson ? "object" : undefined,
80 outputHint: input.artifact.outputSchemaJson ? "output" : undefined,
81 inputSchemaJson: input.includeSchemas ? input.artifact.inputSchemaJson ?? undefined : undefined,
82 outputSchemaJson: input.includeSchemas ? input.artifact.outputSchemaJson ?? undefined : undefined,
83 refHintKeys: input.includeSchemas ? input.refHintKeys : undefined,
84});
85
86const loadSourceById = (input: {
87 rows: SqlControlPlaneRows;
88 workspaceId: Source["workspaceId"];
89 sourceId: Source["id"];
90}): Effect.Effect<Source, Error, never> =>
91 Effect.gen(function* () {
92 const sourceRecord = yield* input.rows.sources.getByWorkspaceAndId(
93 input.workspaceId,
94 input.sourceId,
95 ).pipe(
96 Effect.mapError((cause) =>
97 cause instanceof Error ? cause : new Error(String(cause)),
98 ),
99 );
51 100
52 return normalized.length > 0 ? normalized : "source";101 if (Option.isNone(sourceRecord)) {
53};102 return yield* Effect.fail(
103 new Error(`Source not found: workspaceId=${input.workspaceId} sourceId=${input.sourceId}`),
104 );
105 }
106
107 const credentialBinding = yield* input.rows.sourceCredentialBindings
108 .getByWorkspaceAndSourceId(input.workspaceId, input.sourceId)
109 .pipe(
110 Effect.mapError((cause) =>
111 cause instanceof Error ? cause : new Error(String(cause)),
112 ),
113 );
114
115 return yield* projectSourceFromStorage({
116 sourceRecord: sourceRecord.value,
117 credentialBinding: Option.isSome(credentialBinding) ? credentialBinding.value : null,
118 });
119 });
54 120
55export const createEnvSecretMaterialResolver = (): ResolveSecretMaterial =>121const createWorkspaceToolCatalog = (input: {
56 (ref) =>122 workspaceId: Source["workspaceId"];
123 rows: SqlControlPlaneRows;
124 executorCatalog: ToolCatalog;
125}): ToolCatalog => ({
126 listNamespaces: ({ limit }) =>
57 Effect.gen(function* () {127 Effect.gen(function* () {
58 if (ref.providerId !== "env") {128 const [persisted, executor] = yield* Effect.all([
59 return yield* Effect.fail(129 input.rows.toolArtifacts.listNamespacesByWorkspaceId(input.workspaceId, {
60 new Error(`Unsupported secret provider ${ref.providerId}`),130 limit,
61 );131 }).pipe(
62 }132 Effect.mapError((cause) =>
133 cause instanceof Error ? cause : new Error(String(cause)),
134 ),
135 ),
136 input.executorCatalog.listNamespaces({ limit }),
137 ]);
63 138
64 const value = process.env[ref.handle]?.trim();139 const merged = new Map<string, ToolNamespace>();
65 if (!value) {140 for (const namespace of persisted) {
66 return yield* Effect.fail(141 merged.set(namespace.namespace, namespace);
67 new Error(`Environment variable ${ref.handle} is not set`),142 }
68 );143 for (const namespace of executor) {
144 const existing = merged.get(namespace.namespace);
145 merged.set(namespace.namespace, {
146 namespace: namespace.namespace,
147 displayName: namespace.displayName ?? existing?.displayName,
148 toolCount:
149 namespace.toolCount !== undefined || existing?.toolCount === undefined
150 ? namespace.toolCount
151 : existing.toolCount,
152 });
69 }153 }
70 154
71 return value;155 return [...merged.values()]
72 });156 .sort((left, right) => left.namespace.localeCompare(right.namespace))
73 157 .slice(0, limit);
74export const resolveSourceAuthMaterial = (input: {158 }),
75 source: Source;
76 resolveSecretMaterial: ResolveSecretMaterial;
77}): Effect.Effect<ResolvedSourceAuthMaterial, Error, never> =>
78 Effect.gen(function* () {
79 if (input.source.auth.kind === "none") {
80 return { headers: {} } satisfies ResolvedSourceAuthMaterial;
81 }
82 159
83 const tokenRef =160 listTools: ({ namespace, query, limit, includeSchemas = false }) =>
84 input.source.auth.kind === "bearer"161 Effect.gen(function* () {
85 ? input.source.auth.token162 const [persisted, executor] = yield* Effect.all([
86 : input.source.auth.accessToken;163 namespace?.startsWith("executor")
164 ? Effect.succeed([] as readonly StoredToolArtifactRecord[])
165 : input.rows.toolArtifacts.listByWorkspaceId(input.workspaceId, {
166 namespace,
167 query,
168 limit,
169 }).pipe(
170 Effect.mapError((cause) =>
171 cause instanceof Error ? cause : new Error(String(cause)),
172 ),
173 ),
174 input.executorCatalog.listTools({
175 ...(namespace !== undefined ? { namespace } : {}),
176 ...(query !== undefined ? { query } : {}),
177 limit,
178 includeSchemas,
179 }),
180 ]);
181
182 const persistedDescriptors = includeSchemas
183 ? yield* Effect.forEach(
184 persisted,
185 (artifact) =>
186 input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
187 input.workspaceId,
188 artifact.path,
189 ).pipe(
190 Effect.map((rows) =>
191 toDescriptor({
192 artifact,
193 includeSchemas,
194 refHintKeys: rows.map((row) => row.refHintKey),
195 })
196 ),
197 Effect.mapError((cause) =>
198 cause instanceof Error ? cause : new Error(String(cause)),
199 ),
200 ),
201 { concurrency: "unbounded" },
202 )
203 : persisted.map((artifact) =>
204 toDescriptor({
205 artifact,
206 includeSchemas,
207 })
208 );
209
210 return [...persistedDescriptors, ...executor]
211 .sort((left, right) => left.path.localeCompare(right.path))
212 .slice(0, limit);
213 }),
214
215 getToolByPath: ({ path, includeSchemas }) =>
216 Effect.gen(function* () {
217 const executor = yield* input.executorCatalog.getToolByPath({
218 path,
219 includeSchemas,
220 });
221 if (executor) {
222 return executor;
223 }
87 224
88 const token = yield* input.resolveSecretMaterial(tokenRef);225 const artifact = yield* input.rows.toolArtifacts.getByWorkspaceAndPath(
226 input.workspaceId,
227 path,
228 ).pipe(
229 Effect.mapError((cause) =>
230 cause instanceof Error ? cause : new Error(String(cause)),
231 ),
232 );
89 233
90 return {234 if (Option.isNone(artifact)) {
91 headers: {235 return null;
92 [input.source.auth.headerName]: `${input.source.auth.prefix}${token}`,236 }
93 },
94 } satisfies ResolvedSourceAuthMaterial;
95 });
96 237
97const loadMcpSourceTools = (input: {238 const refHintKeys = includeSchemas
98 source: Source;239 ? yield* input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
99 auth: ResolvedSourceAuthMaterial;240 input.workspaceId,
100}): Effect.Effect<ToolMap, Error, never> =>241 path,
101 Effect.gen(function* () {242 ).pipe(
102 if (input.source.kind !== "mcp") {243 Effect.map((rows) => rows.map((row) => row.refHintKey)),
103 return yield* Effect.fail(new Error(`Expected MCP source, received ${input.source.kind}`));244 Effect.mapError((cause) =>
104 }245 cause instanceof Error ? cause : new Error(String(cause)),
246 ),
247 )
248 : undefined;
249
250 return toDescriptor({
251 artifact: artifact.value,
252 includeSchemas,
253 refHintKeys,
254 });
255 }),
105 256
106 const connector = yield* Effect.try({257 searchTools: ({ query, namespace, limit }) =>
107 try: () =>258 Effect.gen(function* () {
108 createSdkMcpConnector({259 const queryTokens = tokenize(query);
109 endpoint: input.source.endpoint,260 const [persisted, executor] = yield* Effect.all([
110 transport: input.source.transport ?? undefined,261 namespace?.startsWith("executor")
111 queryParams: input.source.queryParams ?? undefined,262 ? Effect.succeed([] as readonly StoredToolArtifactRecord[])
112 headers: {263 : input.rows.toolArtifacts.searchByWorkspaceId(input.workspaceId, {
113 ...(input.source.headers ?? {}),264 namespace,
114 ...input.auth.headers,265 query,
115 },266 limit: Math.max(limit * 8, 50),
267 }).pipe(
268 Effect.mapError((cause) =>
269 cause instanceof Error ? cause : new Error(String(cause)),
270 ),
271 ),
272 input.executorCatalog.searchTools({
273 query,
274 ...(namespace !== undefined ? { namespace } : {}),
275 limit,
116 }),276 }),
117 catch: (cause) =>277 ]);
118 cause instanceof Error278
119 ? new Error(279 const persistedHits: SearchHit[] = persisted
120 `Failed creating MCP connector for ${input.source.id}: ${cause.message}`,280 .map((artifact) => ({
121 )281 path: asToolPath(artifact.path),
122 : new Error(`Failed creating MCP connector for ${input.source.id}: ${String(cause)}`),282 score: scoreArtifact(queryTokens, artifact.searchText),
123 });283 }))
284 .filter((hit) => hit.score > 0);
285
286 return [...persistedHits, ...executor]
287 .sort((left, right) =>
288 right.score - left.score || left.path.localeCompare(right.path),
289 )
290 .slice(0, limit);
291 }),
292});
293
294const createWorkspaceToolInvoker = (input: {
295 workspaceId: Source["workspaceId"];
296 rows: SqlControlPlaneRows;
297 resolveSecretMaterial: ResolveSecretMaterial;
298 sourceAuthService: RuntimeSourceAuthService;
299 onElicitation?: Parameters<typeof makeToolInvokerFromTools>[0]["onElicitation"];
300}): {
301 catalog: ToolCatalog;
302 toolInvoker: ToolInvoker;
303} => {
304 const executorTools = createExecutorToolMap({
305 workspaceId: input.workspaceId,
306 sourceAuthService: input.sourceAuthService,
307 });
308 const executorCatalog = createToolCatalogFromTools({
309 tools: executorTools,
310 });
311 const catalog = createWorkspaceToolCatalog({
312 workspaceId: input.workspaceId,
313 rows: input.rows,
314 executorCatalog,
315 });
316 const systemTools = createSystemToolMap({ catalog });
317 const systemToolPaths = new Set(Object.keys(systemTools));
318 const executorToolPaths = new Set(Object.keys(executorTools));
319 const systemInvoker = makeToolInvokerFromTools({
320 tools: systemTools,
321 onElicitation: input.onElicitation,
322 });
323 const executorInvoker = makeToolInvokerFromTools({
324 tools: executorTools,
325 onElicitation: input.onElicitation,
326 });
124 327
125 const discovered = yield* discoverMcpToolsFromConnector({328 const invokePersistedTool = (invocation: {
126 connect: connector,329 path: string;
127 namespace: input.source.namespace ?? namespaceFromSourceName(input.source.name),330 args: unknown;
128 sourceKey: input.source.id,331 context?: Record<string, unknown>;
129 }).pipe(332 }) =>
130 Effect.mapError(333 Effect.gen(function* () {
131 (cause) =>334 const artifactOption = yield* input.rows.toolArtifacts
132 new Error(335 .getByWorkspaceAndPath(input.workspaceId, invocation.path)
133 `Failed discovering MCP tools for ${input.source.id}: ${cause.message}`,336 .pipe(
337 Effect.mapError((cause) =>
338 cause instanceof Error ? cause : new Error(String(cause)),
134 ),339 ),
135 ),340 );
136 );
137 341
138 return discovered.tools;342 if (Option.isNone(artifactOption)) {
139 });343 return yield* Effect.fail(new Error(`Unknown tool path: ${invocation.path}`));
344 }
140 345
141const loadOpenApiSourceTools = (input: {346 const artifact = artifactOption.value;
142 source: Source;347 const source = yield* loadSourceById({
143 auth: ResolvedSourceAuthMaterial;348 rows: input.rows,
144}): Effect.Effect<ToolMap, Error, never> =>349 workspaceId: input.workspaceId,
145 Effect.gen(function* () {350 sourceId: artifact.sourceId,
146 if (input.source.kind !== "openapi") {351 });
147 return yield* Effect.fail(
148 new Error(`Expected OpenAPI source, received ${input.source.kind}`),
149 );
150 }
151 352
152 if (!input.source.specUrl) {353 if (!source.enabled || source.status !== "connected") {
153 return yield* Effect.fail(354 return yield* Effect.fail(
154 new Error(`Missing OpenAPI specUrl for source ${input.source.id}`),355 new Error(`Source for tool path ${invocation.path} is not connected`),
155 );356 );
156 }357 }
157 358
158 const openApiDocument = yield* Effect.tryPromise({359 const auth = yield* resolveSourceAuthMaterial({
159 try: () => fetchOpenApiDocument(input.source.specUrl!),360 source,
160 catch: (cause) =>361 resolveSecretMaterial: input.resolveSecretMaterial,
161 cause instanceof Error362 });
162 ? new Error(
163 `Failed fetching OpenAPI spec for ${input.source.id}: ${cause.message}`,
164 )
165 : new Error(`Failed fetching OpenAPI spec for ${input.source.id}: ${String(cause)}`),
166 });
167 363
168 const extracted = yield* createOpenApiToolsFromSpec({364 if (artifact.providerKind === "mcp") {
169 sourceName: input.source.name,365 const tools = createMcpToolsFromManifest({
170 openApiSpec: openApiDocument,366 manifest: {
171 baseUrl: input.source.endpoint,367 version: 1,
172 namespace: input.source.namespace ?? namespaceFromSourceName(input.source.name),368 tools: [{
173 sourceKey: input.source.id,369 toolId: storedToolIdFromArtifact(artifact),
174 defaultHeaders: input.source.defaultHeaders ?? {},370 toolName: artifact.mcpToolName ?? artifact.title ?? artifact.path,
175 credentialHeaders: input.auth.headers,371 description: artifact.description ?? null,
176 }).pipe(372 ...(artifact.inputSchemaJson ? { inputSchemaJson: artifact.inputSchemaJson } : {}),
177 Effect.mapError(373 ...(artifact.outputSchemaJson ? { outputSchemaJson: artifact.outputSchemaJson } : {}),
178 (cause: unknown) =>374 }],
179 new Error(375 },
180 `Failed loading OpenAPI tools for ${input.source.id}: ${376 connect: createSdkMcpConnector({
181 cause instanceof Error ? cause.message : String(cause)377 endpoint: source.endpoint,
182 }`,378 transport: source.transport ?? undefined,
183 ),379 queryParams: source.queryParams ?? undefined,
184 ),380 headers: {
185 );381 ...(source.headers ?? {}),
382 ...auth.headers,
383 },
384 }),
385 namespace: source.namespace ?? namespaceFromSourceName(source.name),
386 sourceKey: source.id,
387 });
388
389 return yield* makeToolInvokerFromTools({
390 tools,
391 onElicitation: input.onElicitation,
392 }).invoke({
393 path: invocation.path,
394 args: invocation.args,
395 context: invocation.context,
396 });
397 }
186 398
187 return extracted.tools;399 if (artifact.providerKind === "openapi") {
188 });400 const [parameters, requestBodyContentTypes, refHintKeys] = yield* Effect.all([
401 input.rows.toolArtifacts.listParametersByWorkspaceAndPath(
402 input.workspaceId,
403 artifact.path,
404 ),
405 input.rows.toolArtifacts.listRequestBodyContentTypesByWorkspaceAndPath(
406 input.workspaceId,
407 artifact.path,
408 ),
409 input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
410 input.workspaceId,
411 artifact.path,
412 ),
413 ]).pipe(
414 Effect.mapError((cause) =>
415 cause instanceof Error ? cause : new Error(String(cause)),
416 ),
417 );
189 418
190const loadSourceTools = (input: {419 const tools = createOpenApiToolsFromManifest({
191 source: Source;420 manifest: {
192 auth: ResolvedSourceAuthMaterial;421 version: 1,
193}): Effect.Effect<ToolMap, Error, never> => {422 sourceHash: source.sourceHash ?? "stored",
194 if (input.source.kind === "mcp") {423 tools: [{
195 return loadMcpSourceTools({424 toolId: storedToolIdFromArtifact(artifact),
196 source: input.source,425 name: artifact.title ?? storedToolIdFromArtifact(artifact),
197 auth: input.auth,426 description: artifact.description ?? null,
198 });427 method: artifact.openApiMethod!,
199 }428 path: artifact.openApiPathTemplate!,
429 invocation: {
430 method: artifact.openApiMethod!,
431 pathTemplate: artifact.openApiPathTemplate!,
432 parameters: parameters.map((parameter) => ({
433 name: parameter.name,
434 location: parameter.location,
435 required: parameter.required,
436 })),
437 requestBody:
438 artifact.openApiRequestBodyRequired === null
439 ? null
440 : {
441 required: artifact.openApiRequestBodyRequired,
442 contentTypes: requestBodyContentTypes.map((row) => row.contentType),
443 },
444 },
445 operationHash: artifact.openApiOperationHash!,
446 typing: {
447 ...(artifact.inputSchemaJson ? { inputSchemaJson: artifact.inputSchemaJson } : {}),
448 ...(artifact.outputSchemaJson ? { outputSchemaJson: artifact.outputSchemaJson } : {}),
449 ...(refHintKeys.length > 0
450 ? { refHintKeys: refHintKeys.map((row) => row.refHintKey) }
451 : {}),
452 },
453 }],
454 },
455 baseUrl: source.endpoint,
456 namespace: source.namespace ?? namespaceFromSourceName(source.name),
457 sourceKey: source.id,
458 defaultHeaders: source.defaultHeaders ?? {},
459 credentialHeaders: auth.headers,
460 });
461
462 return yield* makeToolInvokerFromTools({
463 tools,
464 onElicitation: input.onElicitation,
465 }).invoke({
466 path: invocation.path,
467 args: invocation.args,
468 context: invocation.context,
469 });
470 }
200 471
201 if (input.source.kind === "openapi") {472 return yield* Effect.fail(
202 return loadOpenApiSourceTools({473 new Error(`Unsupported stored tool provider for ${invocation.path}`),
203 source: input.source,474 );
204 auth: input.auth,
205 });475 });
206 }
207 476
208 return Effect.succeed({});477 return {
478 catalog,
479 toolInvoker: {
480 invoke: ({ path, args, context }) =>
481 systemToolPaths.has(path)
482 ? systemInvoker.invoke({ path, args, context })
483 : executorToolPaths.has(path)
484 ? executorInvoker.invoke({ path, args, context })
485 : invokePersistedTool({ path, args, context }),
486 },
487 };
209};488};
210 489
211export const createWorkspaceExecutionEnvironmentResolver = (input: {490export const createWorkspaceExecutionEnvironmentResolver = (input: {
@@ -221,80 +500,18 @@ export const createWorkspaceExecutionEnvironmentResolver = (input: { · 9 unmodified lines
221 });500 });
222 501
223 return ({ workspaceId, onElicitation }) =>502 return ({ workspaceId, onElicitation }) =>
224 Effect.gen(function* () {503 Effect.sync(() => {
225 const sourceRecords = yield* input.rows.sources.listByWorkspaceId(workspaceId).pipe(504 const { catalog, toolInvoker } = createWorkspaceToolInvoker({
226 Effect.mapError((cause) =>
227 cause instanceof Error ? cause : new Error(String(cause)),
228 ),
229 );
230 const credentialBindings = yield* input.rows.sourceCredentialBindings
231 .listByWorkspaceId(workspaceId)
232 .pipe(
233 Effect.mapError((cause) =>
234 cause instanceof Error ? cause : new Error(String(cause)),
235 ),
236 );
237
238 const sources = yield* projectSourcesFromStorage({
239 sourceRecords,
240 credentialBindings,
241 });
242 const enabledSources = sources.filter(
243 (source) => source.enabled && source.status === "connected",
244 );
245
246 const discoveredToolMaps = yield* Effect.forEach(
247 enabledSources,
248 (source) =>
249 Effect.gen(function* () {
250 const auth = yield* resolveSourceAuthMaterial({
251 source,
252 resolveSecretMaterial,
253 });
254 return yield* loadSourceTools({
255 source,
256 auth,
257 });
258 }),
259 { concurrency: "unbounded" },
260 );
261
262 const executorTools = createExecutorToolMap({
263 workspaceId,505 workspaceId,
506 rows: input.rows,
507 resolveSecretMaterial,
264 sourceAuthService: input.sourceAuthService,508 sourceAuthService: input.sourceAuthService,
265 });509 onElicitation,
266
267 const sourceTools = yield* Effect.try({
268 try: () =>
269 mergeToolMaps([...discoveredToolMaps, executorTools], {
270 conflictMode: "throw",
271 }),
272 catch: (cause) =>
273 cause instanceof Error
274 ? new Error(`Failed merging discovered source tools: ${cause.message}`)
275 : new Error(`Failed merging discovered source tools: ${String(cause)}`),
276 });
277 const catalog = yield* Effect.try({
278 try: () => createToolCatalogFromTools({ tools: sourceTools }),
279 catch: (cause) =>
280 cause instanceof Error
281 ? new Error(`Failed creating tool catalog from source tools: ${cause.message}`)
282 : new Error(`Failed creating tool catalog from source tools: ${String(cause)}`),
283 });
284 const allTools = yield* Effect.try({
285 try: () => mergeToolMaps([sourceTools, createSystemToolMap({ catalog })]),
286 catch: (cause) =>
287 cause instanceof Error
288 ? new Error(`Failed creating source execution tool map: ${cause.message}`)
289 : new Error(`Failed creating source execution tool map: ${String(cause)}`),
290 });510 });
291 511
292 return {512 return {
293 executor: makeInProcessExecutor(),513 executor: makeInProcessExecutor(),
294 toolInvoker: makeToolInvokerFromTools({514 toolInvoker,
295 tools: allTools,
296 onElicitation,
297 }),
298 catalog,515 catalog,
299 } satisfies ExecutionEnvironment;516 } satisfies ExecutionEnvironment;
300 });517 });
packages/control-plane/src/schema/index.ts+1−0
@@ -5,6 +5,7 @@ export * from "./ids"; · 4 unmodified lines
5export * from "./models/auth";5export * from "./models/auth";
6export * from "./models/workspace";6export * from "./models/workspace";
7export * from "./models/source";7export * from "./models/source";
8export * from "./models/tool-artifact";
8export * from "./models/secret-material";9export * from "./models/secret-material";
9export * from "./models/source-auth-session";10export * from "./models/source-auth-session";
10export * from "./models/policy";11export * from "./models/policy";
packages/control-plane/src/schema/models/tool-artifact.ts+159−0
@@ -0,0 +1,159 @@
1import { createSelectSchema } from "drizzle-orm/effect-schema";
2import { Schema } from "effect";
3
4import {
5 toolArtifactParametersTable,
6 toolArtifactRefHintKeysTable,
7 toolArtifactRequestBodyContentTypesTable,
8 toolArtifactsTable,
9} from "../../persistence/schema";
10import { TimestampMsSchema } from "../common";
11import { SourceIdSchema, WorkspaceIdSchema } from "../ids";
12
13export const ToolArtifactProviderKindSchema = Schema.Literal(
14 "mcp",
15 "openapi",
16);
17
18export const ToolArtifactParameterLocationSchema = Schema.Literal(
19 "path",
20 "query",
21 "header",
22 "cookie",
23);
24
25const toolArtifactRowSchemaOverrides = {
26 workspaceId: WorkspaceIdSchema,
27 sourceId: SourceIdSchema,
28 providerKind: ToolArtifactProviderKindSchema,
29 openApiMethod: Schema.NullOr(
30 Schema.Literal("get", "put", "post", "delete", "patch", "head", "options", "trace"),
31 ),
32 createdAt: TimestampMsSchema,
33 updatedAt: TimestampMsSchema,
34} as const;
35
36const ToolArtifactStorageRowSchema = createSelectSchema(
37 toolArtifactsTable,
38 toolArtifactRowSchemaOverrides,
39);
40
41export const StoredToolArtifactRecordSchema = Schema.Struct({
42 workspaceId: WorkspaceIdSchema,
43 path: Schema.String,
44 toolId: Schema.String,
45 sourceId: SourceIdSchema,
46 title: Schema.NullOr(Schema.String),
47 description: Schema.NullOr(Schema.String),
48 searchNamespace: Schema.String,
49 searchText: Schema.String,
50 inputSchemaJson: Schema.NullOr(Schema.String),
51 outputSchemaJson: Schema.NullOr(Schema.String),
52 providerKind: ToolArtifactProviderKindSchema,
53 mcpToolName: Schema.NullOr(Schema.String),
54 openApiMethod: Schema.NullOr(
55 Schema.Literal("get", "put", "post", "delete", "patch", "head", "options", "trace"),
56 ),
57 openApiPathTemplate: Schema.NullOr(Schema.String),
58 openApiOperationHash: Schema.NullOr(Schema.String),
59 openApiRequestBodyRequired: Schema.NullOr(Schema.Boolean),
60 createdAt: TimestampMsSchema,
61 updatedAt: TimestampMsSchema,
62}).annotations({
63 identifier: "StoredToolArtifactRecord",
64});
65
66export const ToolArtifactStorageSchema = Schema.transform(
67 ToolArtifactStorageRowSchema,
68 StoredToolArtifactRecordSchema,
69 {
70 strict: false,
71 decode: (row) => ({
72 workspaceId: row.workspaceId,
73 path: row.path,
74 toolId: row.toolId,
75 sourceId: row.sourceId,
76 title: row.title,
77 description: row.description,
78 searchNamespace: row.searchNamespace,
79 searchText: row.searchText,
80 inputSchemaJson: row.inputSchemaJson,
81 outputSchemaJson: row.outputSchemaJson,
82 providerKind: row.providerKind,
83 mcpToolName: row.mcpToolName,
84 openApiMethod: row.openApiMethod,
85 openApiPathTemplate: row.openApiPathTemplate,
86 openApiOperationHash: row.openApiOperationHash,
87 openApiRequestBodyRequired: row.openApiRequestBodyRequired,
88 createdAt: row.createdAt,
89 updatedAt: row.updatedAt,
90 }),
91 encode: (record) => ({
92 workspaceId: record.workspaceId,
93 path: record.path,
94 toolId: record.toolId,
95 sourceId: record.sourceId,
96 title: record.title,
97 description: record.description,
98 searchNamespace: record.searchNamespace,
99 searchText: record.searchText,
100 inputSchemaJson: record.inputSchemaJson,
101 outputSchemaJson: record.outputSchemaJson,
102 providerKind: record.providerKind,
103 mcpToolName: record.mcpToolName,
104 openApiMethod: record.openApiMethod,
105 openApiPathTemplate: record.openApiPathTemplate,
106 openApiOperationHash: record.openApiOperationHash,
107 openApiRequestBodyRequired: record.openApiRequestBodyRequired,
108 createdAt: record.createdAt,
109 updatedAt: record.updatedAt,
110 }),
111 },
112);
113
114const toolArtifactParameterRowSchemaOverrides = {
115 workspaceId: WorkspaceIdSchema,
116 location: ToolArtifactParameterLocationSchema,
117 position: Schema.Number,
118} as const;
119
120export const StoredToolArtifactParameterRecordSchema = createSelectSchema(
121 toolArtifactParametersTable,
122 toolArtifactParameterRowSchemaOverrides,
123).annotations({
124 identifier: "StoredToolArtifactParameterRecord",
125});
126
127const toolArtifactRequestBodyContentTypeRowSchemaOverrides = {
128 workspaceId: WorkspaceIdSchema,
129 position: Schema.Number,
130} as const;
131
132export const StoredToolArtifactRequestBodyContentTypeRecordSchema = createSelectSchema(
133 toolArtifactRequestBodyContentTypesTable,
134 toolArtifactRequestBodyContentTypeRowSchemaOverrides,
135).annotations({
136 identifier: "StoredToolArtifactRequestBodyContentTypeRecord",
137});
138
139const toolArtifactRefHintKeyRowSchemaOverrides = {
140 workspaceId: WorkspaceIdSchema,
141 position: Schema.Number,
142} as const;
143
144export const StoredToolArtifactRefHintKeyRecordSchema = createSelectSchema(
145 toolArtifactRefHintKeysTable,
146 toolArtifactRefHintKeyRowSchemaOverrides,
147).annotations({
148 identifier: "StoredToolArtifactRefHintKeyRecord",
149});
150
151export type ToolArtifactProviderKind = typeof ToolArtifactProviderKindSchema.Type;
152export type ToolArtifactParameterLocation = typeof ToolArtifactParameterLocationSchema.Type;
153export type StoredToolArtifactRecord = typeof StoredToolArtifactRecordSchema.Type;
154export type StoredToolArtifactParameterRecord =
155 typeof StoredToolArtifactParameterRecordSchema.Type;
156export type StoredToolArtifactRequestBodyContentTypeRecord =
157 typeof StoredToolArtifactRequestBodyContentTypeRecordSchema.Type;
158export type StoredToolArtifactRefHintKeyRecord =
159 typeof StoredToolArtifactRefHintKeyRecordSchema.Type;
packages/kitchen-sink/package.json+1−0
@@ -13,6 +13,7 @@ · 12 unmodified lines
13 "@executor-v3/ai-sdk-adapter": "workspace:*",13 "@executor-v3/ai-sdk-adapter": "workspace:*",
14 "@executor-v3/codemode-core": "workspace:*",14 "@executor-v3/codemode-core": "workspace:*",
15 "@executor-v3/codemode-openapi": "workspace:*",15 "@executor-v3/codemode-openapi": "workspace:*",
16 "@executor-v3/control-plane": "workspace:*",
16 "@executor-v3/runtime-local-inproc": "workspace:*",17 "@executor-v3/runtime-local-inproc": "workspace:*",
17 "effect": "catalog:"18 "effect": "catalog:"
18 },19 },
packages/kitchen-sink/src/source-runtime-sketch-types.ts+0−128
@@ -1,128 +0,0 @@
1import type { ToolInvocationContext, ToolPath } from "@executor-v3/codemode-core";
2
3export type SourceKey = string & { readonly __sourceKey: unique symbol };
4
5export type SourceAuthScheme =
6 | { kind: "none" }
7 | { kind: "apiKey"; in: "header" | "query"; name: string }
8 | { kind: "bearer" }
9 | { kind: "basic" }
10 | { kind: "oauth2" }
11 | { kind: "dynamic" };
12
13export type SourceDefinition =
14 | {
15 sourceKey: SourceKey;
16 displayName: string;
17 kind: "openapi";
18 enabled: boolean;
19 auth: SourceAuthScheme;
20 connection: {
21 specUrl?: string;
22 baseUrl: string;
23 };
24 }
25 | {
26 sourceKey: SourceKey;
27 displayName: string;
28 kind: "mcp";
29 enabled: boolean;
30 auth: SourceAuthScheme;
31 connection: {
32 endpoint: string;
33 transport?: "auto" | "streamable-http" | "sse";
34 };
35 }
36 | {
37 sourceKey: SourceKey;
38 displayName: string;
39 kind: "snippet";
40 enabled: boolean;
41 auth: SourceAuthScheme;
42 connection: {
43 snippetId: string;
44 entrypoint: string;
45 };
46 };
47
48export type ToolArtifact = {
49 path: ToolPath;
50 sourceKey: SourceKey;
51 title?: string;
52 description?: string;
53 inputSchemaJson?: string;
54 outputSchemaJson?: string;
55 search: {
56 namespace: string;
57 keywords: readonly string[];
58 };
59 invocation:
60 | {
61 provider: "openapi";
62 operationId: string;
63 method: "get" | "post" | "put" | "patch" | "delete";
64 pathTemplate: string;
65 }
66 | {
67 provider: "mcp";
68 toolName: string;
69 }
70 | {
71 provider: "snippet";
72 exportName: string;
73 };
74};
75
76export type SecretRef = {
77 providerId: string;
78 handle: string;
79};
80
81export type CredentialBinding = {
82 sourceKey: SourceKey;
83 authScheme: SourceAuthScheme;
84 materials: Record<string, SecretRef>;
85};
86
87export type ResolvedAuthMaterial =
88 | { kind: "none" }
89 | { kind: "headers"; headers: Record<string, string> }
90 | { kind: "query"; queryParams: Record<string, string> }
91 | { kind: "composite"; values: Record<string, string> };
92
93export type SourceCallContext = {
94 auth: ResolvedAuthMaterial;
95};
96
97export interface SecretMaterialProvider {
98 providerId: string;
99 get(input: {
100 handle: string;
101 }): Promise<string>;
102}
103
104export interface SecretMaterialRegistry {
105 get(input: {
106 ref: SecretRef;
107 }): Promise<string>;
108}
109
110export interface SourceRuntimeResolver {
111 resolveForCall(input: {
112 source: SourceDefinition;
113 artifact: ToolArtifact;
114 context?: ToolInvocationContext;
115 }): Promise<SourceCallContext>;
116}
117
118export interface ProviderInvoker {
119 invoke(input: {
120 source: SourceDefinition;
121 artifact: ToolArtifact;
122 args: unknown;
123 runtime: SourceCallContext;
124 context?: ToolInvocationContext;
125 }): Promise<unknown>;
126}
127
128export const asSourceKey = (value: string): SourceKey => value as SourceKey;
packages/kitchen-sink/src/source-runtime.test.ts+263−282
@@ -12,6 +12,13 @@ import * as Effect from "effect/Effect"; · 11 unmodified lines
12import * as Schema from "effect/Schema";12import * as Schema from "effect/Schema";
13import { Schema as EffectSchema } from "effect";13import { Schema as EffectSchema } from "effect";
14 14
15import {
16 type SecretRef,
17 type Source,
18 type SourceId,
19 type StoredToolArtifactRecord,
20 type WorkspaceId,
21} from "@executor-v3/control-plane";
15import {22import {
16 createToolCatalogDiscovery,23 createToolCatalogDiscovery,
17 createToolCatalogFromTools,24 createToolCatalogFromTools,
@@ -24,100 +31,137 @@ import { · 6 unmodified lines
24 type ToolInvocationContext,31 type ToolInvocationContext,
25 type ToolInvoker,32 type ToolInvoker,
26} from "@executor-v3/codemode-core";33} from "@executor-v3/codemode-core";
27import {
28 type CredentialBinding,
29 type ProviderInvoker,
30 type SecretMaterialRegistry,
31 type SecretMaterialProvider,
32 type SourceCallContext,
33 type SourceDefinition,
34 type SourceKey,
35 type SourceRuntimeResolver,
36 type ToolArtifact,
37 asSourceKey,
38} from "./source-runtime-sketch-types";
39import { createOpenApiToolsFromSpec } from "@executor-v3/codemode-openapi";34import { createOpenApiToolsFromSpec } from "@executor-v3/codemode-openapi";
40import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";35import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";
41 36
42const asToolPath = (value: string): ToolPath => value as ToolPath;37const asToolPath = (value: string): ToolPath => value as ToolPath;
38const asSourceId = (value: string): SourceId => value as SourceId;
39const asWorkspaceId = (value: string): WorkspaceId => value as WorkspaceId;
40
41const tokenize = (value: string): string[] =>
42 value
43 .trim()
44 .toLowerCase()
45 .split(/\s+/)
46 .filter(Boolean);
47
48const normalizeSearchText = (...parts: ReadonlyArray<string | null | undefined>): string =>
49 parts
50 .flatMap((part) => {
51 const normalized = part?.trim();
52 return normalized ? [normalized] : [];
53 })
54 .join(" ")
55 .toLowerCase();
56
57const catalogNamespaceFromPath = (path: string): string => {
58 const [first, second] = path.split(".");
59 return second ? `${first}.${second}` : first;
60};
61
62const toDescriptor = (
63 artifact: StoredToolArtifactRecord,
64 includeSchemas: boolean,
65) => ({
66 path: asToolPath(artifact.path),
67 sourceKey: artifact.sourceId,
68 description: artifact.description ?? artifact.title ?? undefined,
69 interaction: "auto" as const,
70 inputHint: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,
71 outputHint: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,
72 inputSchemaJson: includeSchemas ? artifact.inputSchemaJson ?? undefined : undefined,
73 outputSchemaJson: includeSchemas ? artifact.outputSchemaJson ?? undefined : undefined,
74});
75
76type SourceCallContext = {
77 auth:
78 | { kind: "none" }
79 | {
80 kind: "headers";
81 headers: Record<string, string>;
82 };
83};
84
85interface SecretMaterialProvider {
86 providerId: string;
87 get(input: {
88 handle: string;
89 }): Promise<string>;
90}
91
92interface SecretMaterialRegistry {
93 get(input: {
94 ref: SecretRef;
95 }): Promise<string>;
96}
97
98interface ProviderInvoker {
99 invoke(input: {
100 source: Source;
101 artifact: StoredToolArtifactRecord;
102 args: unknown;
103 runtime: SourceCallContext;
104 context?: ToolInvocationContext;
105 }): Promise<unknown>;
106}
43 107
44type WorkspaceScopedSourceStore = {108type WorkspaceScopedSourceStore = {
45 registerSource(input: {109 registerSource(input: {
46 workspaceId: string;110 workspaceId: WorkspaceId;
47 source: SourceDefinition;111 source: Source;
48 }): Promise<void>;112 }): Promise<void>;
49 listSources(input: {113 getById(input: {
50 workspaceId: string;114 sourceId: SourceId;
51 limit?: number;115 }): Promise<Source | null>;
52 }): Promise<
53 readonly {
54 sourceKey: SourceKey;
55 displayName: string;
56 }[]
57 >;
58 getByKey(input: {
59 sourceKey: SourceKey;
60 }): Promise<SourceDefinition | null>;
61};116};
62 117
63type WorkspaceScopedToolStore = {118type WorkspaceScopedToolStore = {
64 indexArtifacts(input: {119 indexArtifacts(input: {
65 workspaceId: string;120 workspaceId: WorkspaceId;
66 sourceKey: SourceKey;121 artifacts: readonly StoredToolArtifactRecord[];
67 artifacts: readonly ToolArtifact[];
68 }): Promise<void>;122 }): Promise<void>;
123 listNamespaces(input: {
124 workspaceId: WorkspaceId;
125 limit?: number;
126 }): Promise<
127 readonly {
128 namespace: string;
129 toolCount: number;
130 }[]
131 >;
69 list(input: {132 list(input: {
70 workspaceId: string;133 workspaceId: WorkspaceId;
71 sourceKey?: SourceKey;
72 query?: string;134 query?: string;
135 namespace?: string;
73 limit?: number;136 limit?: number;
74 }): Promise<readonly ToolArtifact[]>;137 }): Promise<readonly StoredToolArtifactRecord[]>;
75 getByPath(input: {138 getByPath(input: {
76 workspaceId: string;139 workspaceId: WorkspaceId;
77 path: ToolPath;140 path: ToolPath;
78 }): Promise<ToolArtifact | null>;141 }): Promise<StoredToolArtifactRecord | null>;
79};
80
81type WorkspaceScopedBindingStore = {
82 put(input: {
83 workspaceId: string;
84 binding: CredentialBinding;
85 }): Promise<void>;
86 getBySourceKey(input: {
87 workspaceId: string;
88 sourceKey: SourceKey;
89 }): Promise<CredentialBinding | null>;
90};142};
91 143
92const createInMemorySourceStore = (): WorkspaceScopedSourceStore => {144const createInMemorySourceStore = (): WorkspaceScopedSourceStore => {
93 const byWorkspace = new Map<string, Map<string, SourceDefinition>>();145 const byWorkspace = new Map<string, Map<string, Source>>();
94 146
95 const getWorkspaceMap = (workspaceId: string) => {147 const getWorkspaceMap = (workspaceId: WorkspaceId) => {
96 const existing = byWorkspace.get(workspaceId);148 const existing = byWorkspace.get(workspaceId);
97 if (existing) {149 if (existing) {
98 return existing;150 return existing;
99 }151 }
100 152
101 const created = new Map<string, SourceDefinition>();153 const created = new Map<string, Source>();
102 byWorkspace.set(workspaceId, created);154 byWorkspace.set(workspaceId, created);
103 return created;155 return created;
104 };156 };
105 157
106 return {158 return {
107 async registerSource({ workspaceId, source }) {159 async registerSource({ workspaceId, source }) {
108 getWorkspaceMap(workspaceId).set(source.sourceKey, source);160 getWorkspaceMap(workspaceId).set(source.id, source);
109 },161 },
110 async listSources({ workspaceId, limit = 200 }) {162 async getById({ sourceId }) {
111 return [...getWorkspaceMap(workspaceId).values()]
112 .slice(0, limit)
113 .map((source) => ({
114 sourceKey: source.sourceKey,
115 displayName: source.displayName,
116 }));
117 },
118 async getByKey({ sourceKey }) {
119 for (const workspace of byWorkspace.values()) {163 for (const workspace of byWorkspace.values()) {
120 const source = workspace.get(sourceKey);164 const source = workspace.get(sourceId);
121 if (source) {165 if (source) {
122 return source;166 return source;
123 }167 }
@@ -128,15 +172,15 @@ const createInMemorySourceStore = (): WorkspaceScopedSourceStore => { · 4 unmodified lines
128};172};
129 173
130const createInMemoryToolStore = (): WorkspaceScopedToolStore => {174const createInMemoryToolStore = (): WorkspaceScopedToolStore => {
131 const byWorkspace = new Map<string, Map<string, ToolArtifact>>();175 const byWorkspace = new Map<string, Map<string, StoredToolArtifactRecord>>();
132 176
133 const getWorkspaceMap = (workspaceId: string) => {177 const getWorkspaceMap = (workspaceId: WorkspaceId) => {
134 const existing = byWorkspace.get(workspaceId);178 const existing = byWorkspace.get(workspaceId);
135 if (existing) {179 if (existing) {
136 return existing;180 return existing;
137 }181 }
138 182
139 const created = new Map<string, ToolArtifact>();183 const created = new Map<string, StoredToolArtifactRecord>();
140 byWorkspace.set(workspaceId, created);184 byWorkspace.set(workspaceId, created);
141 return created;185 return created;
142 };186 };
@@ -148,21 +192,28 @@ const createInMemoryToolStore = (): WorkspaceScopedToolStore => { · 5 unmodified lines
148 workspace.set(artifact.path, artifact);192 workspace.set(artifact.path, artifact);
149 }193 }
150 },194 },
151 async list({ workspaceId, sourceKey, query, limit = 200 }) {195 async listNamespaces({ workspaceId, limit = 200 }) {
196 const counts = new Map<string, number>();
197 for (const artifact of getWorkspaceMap(workspaceId).values()) {
198 counts.set(
199 artifact.searchNamespace,
200 (counts.get(artifact.searchNamespace) ?? 0) + 1,
201 );
202 }
203
204 return [...counts.entries()]
205 .map(([namespace, toolCount]) => ({
206 namespace,
207 toolCount,
208 }))
209 .sort((left, right) => left.namespace.localeCompare(right.namespace))
210 .slice(0, limit);
211 },
212 async list({ workspaceId, namespace, query, limit = 200 }) {
152 return [...getWorkspaceMap(workspaceId).values()]213 return [...getWorkspaceMap(workspaceId).values()]
153 .filter((artifact) => !sourceKey || artifact.sourceKey === sourceKey)214 .filter((artifact) => !namespace || artifact.searchNamespace === namespace)
154 .filter((artifact) =>215 .filter((artifact) =>
155 !query216 !query || tokenize(query).every((token) => artifact.searchText.includes(token))
156 || [
157 artifact.path,
158 artifact.title ?? "",
159 artifact.description ?? "",
160 artifact.search.namespace,
161 ...artifact.search.keywords,
162 ]
163 .join(" ")
164 .toLowerCase()
165 .includes(query.toLowerCase())
166 )217 )
167 .slice(0, limit);218 .slice(0, limit);
168 },219 },
@@ -172,32 +223,6 @@ const createInMemoryToolStore = (): WorkspaceScopedToolStore => { · 3 unmodified lines
172 };223 };
173};224};
174 225
175const createInMemoryBindingStore = (): WorkspaceScopedBindingStore => {
176 const byWorkspace = new Map<string, Map<string, CredentialBinding>>();
177
178 const getWorkspaceMap = (workspaceId: string) => {
179 const existing = byWorkspace.get(workspaceId);
180 if (existing) {
181 return existing;
182 }
183
184 const created = new Map<string, CredentialBinding>();
185 byWorkspace.set(workspaceId, created);
186 return created;
187 };
188
189 return {
190 async put(input) {
191 const workspaceId = "workspaceId" in input ? input.workspaceId : "default";
192 getWorkspaceMap(workspaceId).set(input.binding.sourceKey, input.binding);
193 },
194 async getBySourceKey(input) {
195 const workspaceId = "workspaceId" in input ? input.workspaceId : "default";
196 return getWorkspaceMap(workspaceId).get(input.sourceKey) ?? null;
197 },
198 };
199};
200
201const createStaticSecretProvider = (226const createStaticSecretProvider = (
202 providerId: string,227 providerId: string,
203 values: Record<string, string>,228 values: Record<string, string>,
@@ -232,87 +257,46 @@ const createSecretRegistry = ( · 28 unmodified lines
232 };257 };
233};258};
234 259
235const createSourceRuntimeResolver = (input: {260const resolveSourceCallContext = (input: {
236 bindingStore: WorkspaceScopedBindingStore;
237 secretRegistry: SecretMaterialRegistry;261 secretRegistry: SecretMaterialRegistry;
238}): SourceRuntimeResolver => ({262}) =>
239 async resolveForCall({ source, context }) {263 async (source: Source): Promise<SourceCallContext> => {
240 if (source.auth.kind === "none") {264 if (source.auth.kind === "none") {
241 return { auth: { kind: "none" } };265 return { auth: { kind: "none" } };
242 }266 }
243 267
244 const workspaceId =268 const tokenRef = source.auth.kind === "bearer"
245 typeof context?.workspaceId === "string" ? context.workspaceId : "default";269 ? source.auth.token
246 270 : source.auth.accessToken;
247 const binding = await input.bindingStore.getBySourceKey({271 const token = await input.secretRegistry.get({ ref: tokenRef });
248 workspaceId,
249 sourceKey: source.sourceKey,
250 });
251 if (!binding) {
252 throw new Error(`Missing credential binding for source ${source.sourceKey}`);
253 }
254
255 if (source.auth.kind === "bearer" || source.auth.kind === "oauth2") {
256 const ref = binding.materials.token ?? binding.materials.accessToken;
257 if (!ref) {
258 throw new Error(`Missing token material for source ${source.sourceKey}`);
259 }
260
261 const token = await input.secretRegistry.get({ ref });
262 return {
263 auth: {
264 kind: "headers",
265 headers: {
266 Authorization: `Bearer ${token}`,
267 },
268 },
269 } satisfies SourceCallContext;
270 }
271
272 if (source.auth.kind === "apiKey") {
273 const ref = binding.materials.apiKey;
274 if (!ref) {
275 throw new Error(`Missing apiKey material for source ${source.sourceKey}`);
276 }
277
278 const apiKey = await input.secretRegistry.get({ ref });
279 return source.auth.in === "header"
280 ? {
281 auth: {
282 kind: "headers",
283 headers: {
284 [source.auth.name]: apiKey,
285 },
286 },
287 }
288 : {
289 auth: {
290 kind: "query",
291 queryParams: {
292 [source.auth.name]: apiKey,
293 },
294 },
295 };
296 }
297 272
298 return {273 return {
299 auth: {274 auth: {
300 kind: "composite",275 kind: "headers",
301 values: {276 headers: {
302 sourceKind: source.kind,277 [source.auth.headerName]: `${source.auth.prefix}${token}`,
303 },278 },
304 },279 },
305 };280 };
306 },281 };
307});
308 282
309const createProviderInvoker = (): ProviderInvoker => ({283const createProviderInvoker = (): ProviderInvoker => ({
310 async invoke({ source, artifact, args, runtime, context }) {284 async invoke({ source, artifact, args, runtime, context }) {
285 const invocation = artifact.providerKind === "mcp"
286 ? {
287 toolName: artifact.mcpToolName ?? artifact.title ?? artifact.toolId,
288 }
289 : {
290 method: artifact.openApiMethod,
291 pathTemplate: artifact.openApiPathTemplate,
292 operationHash: artifact.openApiOperationHash,
293 };
294
311 return {295 return {
312 sourceKey: source.sourceKey,296 sourceId: source.id,
313 path: artifact.path,297 path: artifact.path,
314 provider: artifact.invocation.provider,298 provider: artifact.providerKind,
315 invocation: artifact.invocation,299 invocation,
316 args,300 args,
317 auth: runtime.auth,301 auth: runtime.auth,
318 workspaceId: context?.workspaceId ?? null,302 workspaceId: context?.workspaceId ?? null,
@@ -322,20 +306,18 @@ const createProviderInvoker = (): ProviderInvoker => ({ · 3 unmodified lines
322});306});
323 307
324const createWorkspaceToolCatalog = (input: {308const createWorkspaceToolCatalog = (input: {
325 workspaceId: string;309 workspaceId: WorkspaceId;
326 sourceStore: WorkspaceScopedSourceStore;
327 toolStore: WorkspaceScopedToolStore;310 toolStore: WorkspaceScopedToolStore;
328}): ToolCatalog => ({311}): ToolCatalog => ({
329 listNamespaces: ({ limit }) =>312 listNamespaces: ({ limit }) =>
330 Effect.promise(() =>313 Effect.promise(() =>
331 input.sourceStore.listSources({314 input.toolStore.listNamespaces({
332 workspaceId: input.workspaceId,315 workspaceId: input.workspaceId,
333 limit,316 limit,
334 }).then((sources) =>317 }).then((namespaces) =>
335 sources.map((source) => ({318 namespaces.map((namespace) => ({
336 namespace: source.sourceKey as string,319 namespace: namespace.namespace,
337 displayName: source.displayName,320 toolCount: namespace.toolCount,
338 toolCount: undefined,
339 }))321 }))
340 )322 )
341 ),323 ),
@@ -343,69 +325,37 @@ const createWorkspaceToolCatalog = (input: { · 1 unmodified line
343 Effect.promise(() =>325 Effect.promise(() =>
344 input.toolStore.list({326 input.toolStore.list({
345 workspaceId: input.workspaceId,327 workspaceId: input.workspaceId,
346 ...(namespace !== undefined ? { sourceKey: namespace as SourceKey } : {}),328 ...(namespace !== undefined ? { namespace } : {}),
347 ...(query !== undefined ? { query } : {}),329 ...(query !== undefined ? { query } : {}),
348 limit,330 limit,
349 }).then((artifacts) =>331 }).then((artifacts) => artifacts.map((artifact) => toDescriptor(artifact, includeSchemas)))
350 artifacts.map((artifact) => ({
351 path: artifact.path as any,
352 sourceKey: artifact.sourceKey,
353 description: artifact.description ?? artifact.title,
354 interaction: "auto" as const,
355 inputHint: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,
356 outputHint: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,
357 inputSchemaJson: includeSchemas ? artifact.inputSchemaJson : undefined,
358 outputSchemaJson: includeSchemas ? artifact.outputSchemaJson : undefined,
359 }))
360 )
361 ),332 ),
362 getToolByPath: ({ path, includeSchemas }) =>333 getToolByPath: ({ path, includeSchemas }) =>
363 Effect.promise(() =>334 Effect.promise(() =>
364 input.toolStore.getByPath({335 input.toolStore.getByPath({
365 workspaceId: input.workspaceId,336 workspaceId: input.workspaceId,
366 path: path as ToolPath,337 path,
367 }).then((artifact) =>338 }).then((artifact) => (artifact ? toDescriptor(artifact, includeSchemas) : null))
368 artifact
369 ? {
370 path: artifact.path as any,
371 sourceKey: artifact.sourceKey,
372 description: artifact.description ?? artifact.title,
373 interaction: "auto" as const,
374 inputHint: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,
375 outputHint: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,
376 inputSchemaJson: includeSchemas ? artifact.inputSchemaJson : undefined,
377 outputSchemaJson: includeSchemas ? artifact.outputSchemaJson : undefined,
378 }
379 : null
380 )
381 ),339 ),
382 searchTools: ({ query, namespace, limit }) =>340 searchTools: ({ query, namespace, limit }) =>
383 Effect.promise(() =>341 Effect.promise(() =>
384 input.toolStore.list({342 input.toolStore.list({
385 workspaceId: input.workspaceId,343 workspaceId: input.workspaceId,
386 ...(namespace !== undefined ? { sourceKey: namespace as SourceKey } : {}),344 ...(namespace !== undefined ? { namespace } : {}),
387 ...(query !== undefined ? { query } : {}),345 query,
388 limit: 500,346 limit: 500,
389 }).then((artifacts) => {347 }).then((artifacts) => {
390 const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean);348 const queryTokens = tokenize(query);
391 349
392 return artifacts350 return artifacts
393 .map((artifact) => {351 .map((artifact) => {
394 const haystack = [
395 artifact.path,
396 artifact.title ?? "",
397 artifact.description ?? "",
398 artifact.search.namespace,
399 ...artifact.search.keywords,
400 ].join(" ").toLowerCase();
401
402 const score = queryTokens.reduce(352 const score = queryTokens.reduce(
403 (total, token) => total + (haystack.includes(token) ? 1 : 0),353 (total, token) => total + (artifact.searchText.includes(token) ? 1 : 0),
404 0,354 0,
405 );355 );
406 356
407 return {357 return {
408 path: artifact.path as any,358 path: asToolPath(artifact.path),
409 score,359 score,
410 };360 };
411 })361 })
@@ -417,16 +367,15 @@ const createWorkspaceToolCatalog = (input: { · 5 unmodified lines
417});367});
418 368
419const createWorkspaceToolInvoker = (input: {369const createWorkspaceToolInvoker = (input: {
420 workspaceId: string;370 workspaceId: WorkspaceId;
421 sourceStore: WorkspaceScopedSourceStore;371 sourceStore: WorkspaceScopedSourceStore;
422 toolStore: WorkspaceScopedToolStore;372 toolStore: WorkspaceScopedToolStore;
423 runtimeResolver: SourceRuntimeResolver;373 resolveSourceCallContext: (source: Source) => Promise<SourceCallContext>;
424 providerInvoker: ProviderInvoker;374 providerInvoker: ProviderInvoker;
425}): ToolInvoker => ({375}): ToolInvoker => ({
426 invoke: (() => {376 invoke: (() => {
427 const catalog = createWorkspaceToolCatalog({377 const catalog = createWorkspaceToolCatalog({
428 workspaceId: input.workspaceId,378 workspaceId: input.workspaceId,
429 sourceStore: input.sourceStore,
430 toolStore: input.toolStore,379 toolStore: input.toolStore,
431 });380 });
432 const systemTools = createSystemToolMap({381 const systemTools = createSystemToolMap({
@@ -457,18 +406,14 @@ const createWorkspaceToolInvoker = (input: { · 24 unmodified lines
457 throw new Error(`Unknown tool path: ${path}`);406 throw new Error(`Unknown tool path: ${path}`);
458 }407 }
459 408
460 const source = await input.sourceStore.getByKey({409 const source = await input.sourceStore.getById({
461 sourceKey: artifact.sourceKey,410 sourceId: artifact.sourceId,
462 });411 });
463 if (!source) {412 if (!source) {
464 throw new Error(`Unknown source for tool path: ${path}`);413 throw new Error(`Unknown source for tool path: ${path}`);
465 }414 }
466 415
467 const runtime = await input.runtimeResolver.resolveForCall({416 const runtime = await input.resolveSourceCallContext(source);
468 source,
469 artifact,
470 context: mergedContext,
471 });
472 417
473 return input.providerInvoker.invoke({418 return input.providerInvoker.invoke({
474 source,419 source,
@@ -484,10 +429,9 @@ const createWorkspaceToolInvoker = (input: { · 9 unmodified lines
484});429});
485 430
486const toolInvokerFromWorkspace = (input: {431const toolInvokerFromWorkspace = (input: {
487 workspaceId: string;432 workspaceId: WorkspaceId;
488 sourceStore: WorkspaceScopedSourceStore;433 sourceStore: WorkspaceScopedSourceStore;
489 toolStore: WorkspaceScopedToolStore;434 toolStore: WorkspaceScopedToolStore;
490 bindingStore: WorkspaceScopedBindingStore;
491 secretRegistry: SecretMaterialRegistry;435 secretRegistry: SecretMaterialRegistry;
492 providerInvoker?: ProviderInvoker;436 providerInvoker?: ProviderInvoker;
493}): ToolInvoker =>437}): ToolInvoker =>
@@ -495,46 +439,100 @@ const toolInvokerFromWorkspace = (input: { · 1 unmodified line
495 workspaceId: input.workspaceId,439 workspaceId: input.workspaceId,
496 sourceStore: input.sourceStore,440 sourceStore: input.sourceStore,
497 toolStore: input.toolStore,441 toolStore: input.toolStore,
498 runtimeResolver: createSourceRuntimeResolver({442 resolveSourceCallContext: resolveSourceCallContext({
499 bindingStore: input.bindingStore,
500 secretRegistry: input.secretRegistry,443 secretRegistry: input.secretRegistry,
501 }),444 }),
502 providerInvoker: input.providerInvoker ?? createProviderInvoker(),445 providerInvoker: input.providerInvoker ?? createProviderInvoker(),
503 });446 });
504 447
505const bearerBinding = (input: {448const bearerSourceAuth = (input: {
506 sourceKey: SourceKey;
507 providerId: string;449 providerId: string;
508 handle: string;450 handle: string;
509}): CredentialBinding => ({451}): Source["auth"] => ({
510 sourceKey: input.sourceKey,452 kind: "bearer",
511 authScheme: { kind: "bearer" },453 headerName: "Authorization",
512 materials: {454 prefix: "Bearer ",
513 token: {455 token: {
514 providerId: input.providerId,456 providerId: input.providerId,
515 handle: input.handle,457 handle: input.handle,
516 },
517 },458 },
518});459});
519 460
520const openApiSource = (input: {461const openApiSource = (input: {
521 sourceKey: SourceKey;462 workspaceId: WorkspaceId;
463 sourceId: SourceId;
522 displayName: string;464 displayName: string;
523 baseUrl: string;465 baseUrl: string;
524 specUrl?: string;466 specUrl?: string;
525 auth: SourceDefinition["auth"];467 namespace?: string;
526}): SourceDefinition => ({468 auth: Source["auth"];
527 sourceKey: input.sourceKey,469}): Source => ({
528 displayName: input.displayName,470 id: input.sourceId,
471 workspaceId: input.workspaceId,
472 name: input.displayName,
529 kind: "openapi",473 kind: "openapi",
474 endpoint: input.baseUrl,
475 status: "connected",
530 enabled: true,476 enabled: true,
477 namespace: input.namespace ?? null,
478 transport: null,
479 queryParams: null,
480 headers: null,
481 specUrl: input.specUrl ?? null,
482 defaultHeaders: null,
531 auth: input.auth,483 auth: input.auth,
532 connection: {484 sourceHash: null,
533 specUrl: input.specUrl,485 lastError: null,
534 baseUrl: input.baseUrl,486 createdAt: 0,
535 },487 updatedAt: 0,
536});488});
537 489
490const openApiArtifact = (input: {
491 workspaceId: WorkspaceId;
492 sourceId: SourceId;
493 path: ToolPath;
494 toolId: string;
495 title: string;
496 description?: string;
497 method: NonNullable<StoredToolArtifactRecord["openApiMethod"]>;
498 pathTemplate: string;
499 operationHash?: string;
500 inputSchemaJson?: string | null;
501 outputSchemaJson?: string | null;
502}): StoredToolArtifactRecord => {
503 const path = input.path as string;
504 const description =
505 input.description ?? `${input.method.toUpperCase()} ${input.pathTemplate}`;
506
507 return {
508 workspaceId: input.workspaceId,
509 path,
510 toolId: input.toolId,
511 sourceId: input.sourceId,
512 title: input.title,
513 description,
514 searchNamespace: catalogNamespaceFromPath(path),
515 searchText: normalizeSearchText(
516 path,
517 catalogNamespaceFromPath(path),
518 input.title,
519 description,
520 input.method.toUpperCase(),
521 input.pathTemplate,
522 ),
523 inputSchemaJson: input.inputSchemaJson ?? null,
524 outputSchemaJson: input.outputSchemaJson ?? null,
525 providerKind: "openapi",
526 mcpToolName: null,
527 openApiMethod: input.method,
528 openApiPathTemplate: input.pathTemplate,
529 openApiOperationHash: input.operationHash ?? input.toolId,
530 openApiRequestBodyRequired: null,
531 createdAt: 0,
532 updatedAt: 0,
533 };
534};
535
538const numberPairInputSchema = Schema.standardSchemaV1(536const numberPairInputSchema = Schema.standardSchemaV1(
539 Schema.Struct({537 Schema.Struct({
540 a: Schema.Number,538 a: Schema.Number,
@@ -545,10 +543,9 @@ const numberPairInputSchema = Schema.standardSchemaV1( · 4 unmodified lines
545const createDiscoveryBackedToolMap = (input: {543const createDiscoveryBackedToolMap = (input: {
546 tools: ToolMap;544 tools: ToolMap;
547 namespace: string;545 namespace: string;
548 displayName?: string;
549 sourceKey?: string;546 sourceKey?: string;
550}) => {547}) => {
551 const sourceKey = asSourceKey(input.sourceKey ?? "in_memory.tools");548 const sourceKey = input.sourceKey ?? "in_memory.tools";
552 const catalog = createToolCatalogFromTools({549 const catalog = createToolCatalogFromTools({
553 tools: input.tools,550 tools: input.tools,
554 defaultNamespace: input.namespace,551 defaultNamespace: input.namespace,
@@ -657,10 +654,9 @@ const makeOpenApiTestServer = Effect.acquireRelease( · 102 unmodified lines
657describe("source runtime", () => {654describe("source runtime", () => {
658 it.effect("searches serialized workspace tools and calls one", () =>655 it.effect("searches serialized workspace tools and calls one", () =>
659 Effect.gen(function* () {656 Effect.gen(function* () {
660 const workspaceId = "workspace_123";657 const workspaceId = asWorkspaceId("workspace_123");
661 const sourceStore = createInMemorySourceStore();658 const sourceStore = createInMemorySourceStore();
662 const toolStore = createInMemoryToolStore();659 const toolStore = createInMemoryToolStore();
663 const bindingStore = createInMemoryBindingStore();
664 const secretRegistry = createSecretRegistry([660 const secretRegistry = createSecretRegistry([
665 createStaticSecretProvider("postgres", {661 createStaticSecretProvider("postgres", {
666 "github-db-token": "ghp_from_db",662 "github-db-token": "ghp_from_db",
@@ -668,11 +664,16 @@ describe("source runtime", () => { · 1 unmodified line
668 ]);664 ]);
669 665
670 const githubSource = openApiSource({666 const githubSource = openApiSource({
671 sourceKey: asSourceKey("github"),667 workspaceId,
668 sourceId: asSourceId("github"),
672 displayName: "GitHub API",669 displayName: "GitHub API",
673 baseUrl: "https://api.github.com",670 baseUrl: "https://api.github.com",
674 specUrl: "https://api.github.com/openapi.json",671 specUrl: "https://api.github.com/openapi.json",
675 auth: { kind: "bearer" },672 namespace: "github",
673 auth: bearerSourceAuth({
674 providerId: "postgres",
675 handle: "github-db-token",
676 }),
676 });677 });
677 678
678 yield* Effect.promise(() =>679 yield* Effect.promise(() =>
@@ -681,37 +682,20 @@ describe("source runtime", () => { · 2 unmodified lines
681 source: githubSource,682 source: githubSource,
682 })683 })
683 );684 );
684 yield* Effect.promise(() =>
685 bindingStore.put({
686 workspaceId,
687 binding: bearerBinding({
688 sourceKey: githubSource.sourceKey,
689 providerId: "postgres",
690 handle: "github-db-token",
691 }),
692 })
693 );
694 yield* Effect.promise(() =>685 yield* Effect.promise(() =>
695 toolStore.indexArtifacts({686 toolStore.indexArtifacts({
696 workspaceId,687 workspaceId,
697 sourceKey: githubSource.sourceKey,
698 artifacts: [688 artifacts: [
699 {689 openApiArtifact({
690 workspaceId,
691 sourceId: githubSource.id,
700 path: asToolPath("github.issues.list"),692 path: asToolPath("github.issues.list"),
701 sourceKey: githubSource.sourceKey,693 toolId: "issues.list",
702 title: "List issues",694 title: "List issues",
703 description: "Serialized artifact loaded from a database row",695 description: "Serialized artifact loaded from a database row",
704 invocation: {696 method: "get",
705 provider: "openapi",697 pathTemplate: "/repos/{owner}/{repo}/issues",
706 operationId: "issues.list",698 }),
707 method: "get",
708 pathTemplate: "/repos/{owner}/{repo}/issues",
709 },
710 search: {
711 namespace: "github.issues",
712 keywords: ["github", "issues", "list", "serialized", "db"],
713 },
714 },
715 ],699 ],
716 })700 })
717 );701 );
@@ -720,12 +704,10 @@ describe("source runtime", () => { · 2 unmodified lines
720 workspaceId,704 workspaceId,
721 sourceStore,705 sourceStore,
722 toolStore,706 toolStore,
723 bindingStore,
724 secretRegistry,707 secretRegistry,
725 });708 });
726 const workspaceCatalog = createWorkspaceToolCatalog({709 const workspaceCatalog = createWorkspaceToolCatalog({
727 workspaceId,710 workspaceId,
728 sourceStore,
729 toolStore,711 toolStore,
730 });712 });
731 const executeDescription = yield* createToolCatalogDiscovery({713 const executeDescription = yield* createToolCatalogDiscovery({
@@ -770,7 +752,7 @@ describe("source runtime", () => { · 38 unmodified lines
770 [752 [
771 "Execute TypeScript in sandbox; call tools via discovery workflow.",753 "Execute TypeScript in sandbox; call tools via discovery workflow.",
772 "Available namespaces:",754 "Available namespaces:",
773 "- GitHub API",755 "- github.issues",
774 "Workflow:",756 "Workflow:",
775 '1) const matches = await tools.discover({ query: "<intent>", limit: 12 });',757 '1) const matches = await tools.discover({ query: "<intent>", limit: 12 });',
776 "2) const details = await tools.describe.tool({ path, includeSchemas: true });",758 "2) const details = await tools.describe.tool({ path, includeSchemas: true });",
@@ -798,7 +780,6 @@ describe("source runtime", () => { · 21 unmodified lines
798 const discoveryBacked = createDiscoveryBackedToolMap({780 const discoveryBacked = createDiscoveryBackedToolMap({
799 tools: extracted.tools,781 tools: extracted.tools,
800 namespace: "github",782 namespace: "github",
801 displayName: "GitHub API",
802 sourceKey: "github.openapi",783 sourceKey: "github.openapi",
803 });784 });
804 785