History

wip ui

Rhys Sullivan committed 8 months ago

Commit 2d7f7801 parent 4b6e5d781 files · +6620 −566Browse files

Unified · Split

81 files
  1. apps/executor/package.json +1 −0
  2. apps/executor/src/cli/main.ts +2 −2
  3. apps/executor/src/server/index.ts +0 −138
  4. apps/executor/src/server/server.test.ts +1 −1
  5. apps/web/package.json +36 −0
  6. apps/web/src/components/code-block.tsx +96 −0
  7. apps/web/src/components/document-panel.tsx +44 −0
  8. apps/web/src/components/icons.tsx +109 −0
  9. apps/web/src/components/loadable.tsx +50 −0
  10. apps/web/src/components/shell.tsx +232 −0
  11. apps/web/src/components/ui/badge.tsx +43 −0
  12. apps/web/src/components/ui/button.tsx +34 −0
  13. apps/web/src/dev.ts +29 −0
  14. apps/web/src/frontend.tsx +10 −0
  15. apps/web/src/globals.css +107 −0
  16. apps/web/src/index.html +29 −0
  17. apps/web/src/lib/utils.ts +6 −0
  18. apps/web/src/main.tsx +100 −0
  19. apps/web/src/server.ts +13 −0
  20. apps/web/src/views/home.tsx +181 −0
  21. apps/web/src/views/source-detail.tsx +604 −0
  22. apps/web/tsconfig.json +17 −0
  23. apps/web/vite.config.ts +29 −0
  24. bun.lock +453 −2
  25. package.json +2 −0
  26. packages/ai-sdk-adapter/src/example-dynamic.ts +4 −4
  27. packages/codemode-core/src/discovery.ts +4 −6
  28. packages/codemode-core/src/index.test.ts +4 −5
  29. packages/codemode-core/src/index.ts +1 −0
  30. packages/codemode-core/src/schema-types.ts +125 −0
  31. packages/codemode-core/src/system-tools.ts +8 −6
  32. packages/codemode-core/src/tool-map.ts +18 −47
  33. packages/codemode-core/src/types.ts +12 −6
  34. packages/codemode-openapi/openapi-extractor-rs/.gitignore +1 −0
  35. packages/codemode-openapi/openapi-extractor-rs/Cargo.lock +445 −0
  36. packages/codemode-openapi/openapi-extractor-rs/Cargo.toml +17 −0
  37. packages/codemode-openapi/openapi-extractor-rs/build-wasm.sh +20 −0
  38. packages/codemode-openapi/openapi-extractor-rs/src/lib.rs +1018 −0
  39. packages/codemode-openapi/openapi-extractor-rs/src/main.rs +58 −0
  40. packages/codemode-openapi/package.json +1 −0
  41. packages/codemode-openapi/src/index.ts +20 −0
  42. packages/codemode-openapi/src/openapi-definitions.ts +254 −0
  43. packages/codemode-openapi/src/openapi-extraction.real-specs.test.ts +118 −0
  44. packages/codemode-openapi/src/openapi-extractor-wasm/openapi_extractor_bg.wasm +0 −0
  45. packages/codemode-openapi/src/openapi-schema-refs.ts +135 −0
  46. packages/codemode-openapi/src/openapi-tool-presentation.ts +106 −0
  47. packages/codemode-openapi/src/openapi-tools.test.ts +59 −0
  48. packages/codemode-openapi/src/openapi-tools.ts +107 −29
  49. packages/codemode-openapi/src/openapi-types.ts +43 −2
  50. packages/control-plane/drizzle/20260307134000_openapi_tool_identity/migration.sql +37 −0
  51. packages/control-plane/drizzle/20260307152000_source_document_text/migration.sql +1 −0
  52. packages/control-plane/package.json +3 −1
  53. packages/control-plane/src/api/sources/api.ts +33 −0
  54. packages/control-plane/src/api/sources/http.ts +37 −0
  55. packages/control-plane/src/client.ts +2 −2
  56. packages/control-plane/src/persistence/control-plane-rows.test.ts +1 −0
  57. packages/control-plane/src/persistence/repos/sources-repo.ts +1 −0
  58. packages/control-plane/src/persistence/schema.ts +9 −0
  59. packages/control-plane/src/runtime/control-plane-runtime.test.ts +2 −0
  60. packages/control-plane/src/runtime/executor-tools.ts +2 −2
  61. packages/control-plane/src/runtime/index.ts +2 −1
  62. packages/control-plane/src/runtime/source-auth-service.ts +14 −7
  63. packages/control-plane/src/runtime/source-definitions.ts +1 −0
  64. packages/control-plane/src/runtime/source-inspection.ts +520 −0
  65. packages/control-plane/src/runtime/sources-operations.ts +4 −2
  66. packages/control-plane/src/runtime/tool-artifacts.ts +28 −80
  67. packages/control-plane/src/runtime/workspace-execution-environment.test.ts +101 −94
  68. packages/control-plane/src/runtime/workspace-execution-environment.ts +249 −125
  69. packages/control-plane/src/schema/index.ts +1 −0
  70. packages/control-plane/src/schema/models/source-inspection.ts +85 −0
  71. packages/control-plane/src/schema/models/source.ts +3 −0
  72. packages/control-plane/src/schema/models/tool-artifact.ts +9 −0
  73. packages/kitchen-sink/src/source-runtime.test.ts +8 −4
  74. packages/react/package.json +24 −0
  75. packages/react/src/index.ts +325 −0
  76. packages/react/tsconfig.json +17 −0
  77. packages/server/package.json +20 −0
  78. packages/server/src/config.ts +7 −1
  79. packages/server/src/index.ts +237 −0
  80. packages/server/tsconfig.json +15 −0
  81. turbo.json +17 −0
apps/executor/package.json+1−0
@@ -17,6 +17,7 @@ · 16 unmodified lines
17 "@effect/platform-node": "catalog:",17 "@effect/platform-node": "catalog:",
18 "@executor-v3/codemode-core": "workspace:*",18 "@executor-v3/codemode-core": "workspace:*",
19 "@executor-v3/control-plane": "workspace:*",19 "@executor-v3/control-plane": "workspace:*",
20 "@executor-v3/server": "workspace:*",
20 "@executor-v3/runtime-local-inproc": "workspace:*",21 "@executor-v3/runtime-local-inproc": "workspace:*",
21 "effect": "catalog:"22 "effect": "catalog:"
22 },23 },
apps/executor/src/cli/main.ts+2−2
@@ -32,12 +32,12 @@ import { · 31 unmodified lines
32 DEFAULT_SERVER_PORT,32 DEFAULT_SERVER_PORT,
33 SERVER_POLL_INTERVAL_MS,33 SERVER_POLL_INTERVAL_MS,
34 SERVER_START_TIMEOUT_MS,34 SERVER_START_TIMEOUT_MS,
35} from "../server/config";35 runLocalExecutorServer,
36} from "@executor-v3/server";
36import {37import {
37 seedDemoMcpSourceInWorkspace,38 seedDemoMcpSourceInWorkspace,
38 seedGithubOpenApiSourceInWorkspace,39 seedGithubOpenApiSourceInWorkspace,
39} from "./dev";40} from "./dev";
40import { runLocalExecutorServer } from "../server";
41 41
42const toError = (cause: unknown): Error =>42const toError = (cause: unknown): Error =>
43 cause instanceof Error ? cause : new Error(String(cause));43 cause instanceof Error ? cause : new Error(String(cause));
apps/executor/src/server/index.ts+0−138
@@ -1,138 +0,0 @@
1import { mkdir } from "node:fs/promises";
2import { createServer } from "node:http";
3import { dirname } from "node:path";
4
5import { HttpApiBuilder, HttpServer } from "@effect/platform";
6import { NodeHttpServer } from "@effect/platform-node";
7import {
8 createControlPlaneApiLayer,
9 createSqlControlPlaneRuntime,
10 type ResolveExecutionEnvironment,
11 type ResolveSecretMaterial,
12 type SqlControlPlaneRuntime,
13} from "@executor-v3/control-plane";
14import * as Context from "effect/Context";
15import * as Effect from "effect/Effect";
16import * as Layer from "effect/Layer";
17import type * as Scope from "effect/Scope";
18
19import {
20 DEFAULT_LOCAL_DATA_DIR,
21 DEFAULT_SERVER_HOST,
22 DEFAULT_SERVER_PORT,
23} from "./config";
24
25type LocalExecutorServer = {
26 readonly runtime: SqlControlPlaneRuntime;
27 readonly port: number;
28 readonly host: string;
29 readonly baseUrl: string;
30};
31
32type StartLocalExecutorServerOptions = {
33 readonly port?: number;
34 readonly host?: string;
35 readonly localDataDir?: string;
36 readonly executionResolver?: ResolveExecutionEnvironment;
37 readonly resolveSecretMaterial?: ResolveSecretMaterial;
38};
39
40const disposeRuntime = (runtime: SqlControlPlaneRuntime) =>
41 Effect.promise(() => runtime.close()).pipe(Effect.orDie);
42
43const createControlPlaneServerLayer = (input: {
44 runtime: SqlControlPlaneRuntime;
45 host: string;
46 port: number;
47}) => {
48 const apiLayer = createControlPlaneApiLayer(input.runtime.runtimeLayer);
49
50 return HttpApiBuilder.serve().pipe(
51 Layer.provide(apiLayer),
52 Layer.provideMerge(
53 NodeHttpServer.layer(createServer, {
54 host: input.host,
55 port: input.port,
56 }),
57 ),
58 );
59};
60
61export const createLocalExecutorServer = (
62 options: StartLocalExecutorServerOptions = {},
63): Effect.Effect<LocalExecutorServer, Error, Scope.Scope> =>
64 Effect.gen(function* () {
65 const host = options.host ?? DEFAULT_SERVER_HOST;
66 const port = options.port ?? DEFAULT_SERVER_PORT;
67 const localDataDir = options.localDataDir ?? DEFAULT_LOCAL_DATA_DIR;
68
69 if (localDataDir !== ":memory:") {
70 yield* Effect.tryPromise({
71 try: () => mkdir(dirname(localDataDir), { recursive: true }),
72 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
73 });
74 }
75
76 let baseUrlRef: string | undefined;
77
78 const runtime = yield* Effect.acquireRelease(
79 createSqlControlPlaneRuntime({
80 localDataDir,
81 executionResolver: options.executionResolver,
82 resolveSecretMaterial: options.resolveSecretMaterial,
83 getLocalServerBaseUrl: () => baseUrlRef,
84 }).pipe(
85 Effect.mapError((cause) =>
86 cause instanceof Error ? cause : new Error(String(cause)),
87 ),
88 ),
89 disposeRuntime,
90 );
91
92 const serverContext = yield* Layer.build(
93 createControlPlaneServerLayer({
94 runtime,
95 host,
96 port,
97 }),
98 ).pipe(
99 Effect.mapError((cause) =>
100 cause instanceof Error ? cause : new Error(String(cause)),
101 ),
102 );
103
104 const server = Context.get(serverContext, HttpServer.HttpServer);
105 if (server.address._tag !== "TcpAddress") {
106 return yield* Effect.fail(new Error("Local executor server did not bind to a TCP address"));
107 }
108
109 baseUrlRef = `http://${server.address.hostname}:${server.address.port}`;
110
111 return {
112 runtime,
113 host: server.address.hostname,
114 port: server.address.port,
115 baseUrl: baseUrlRef,
116 } satisfies LocalExecutorServer;
117 });
118
119export const runLocalExecutorServer = (
120 options: StartLocalExecutorServerOptions = {},
121): Effect.Effect<void, Error, never> =>
122 Effect.scoped(
123 Effect.gen(function* () {
124 const server = yield* createLocalExecutorServer(options);
125 console.error(`executor server listening on ${server.baseUrl}`);
126
127 yield* Effect.async<void, never>((resume) => {
128 const shutdown = () => resume(Effect.void);
129 process.once("SIGINT", shutdown);
130 process.once("SIGTERM", shutdown);
131
132 return Effect.sync(() => {
133 process.off("SIGINT", shutdown);
134 process.off("SIGTERM", shutdown);
135 });
136 });
137 }),
138 );
apps/executor/src/server/server.test.ts+1−1
@@ -31,7 +31,7 @@ import { · 30 unmodified lines
31 seedDemoMcpSourceInWorkspace,31 seedDemoMcpSourceInWorkspace,
32 seedGithubOpenApiSourceInWorkspace,32 seedGithubOpenApiSourceInWorkspace,
33} from "../cli/dev";33} from "../cli/dev";
34import { createLocalExecutorServer } from ".";34import { createLocalExecutorServer } from "@executor-v3/server";
35 35
36const executionResolver: ResolveExecutionEnvironment = () =>36const executionResolver: ResolveExecutionEnvironment = () =>
37 Effect.succeed({37 Effect.succeed({
apps/web/package.json+36−0
@@ -0,0 +1,36 @@
1{
2 "name": "@executor-v3/web",
3 "private": true,
4 "type": "module",
5 "scripts": {
6 "dev": "bunx --bun vite dev",
7 "build": "bunx vite build --config vite.config.ts",
8 "typecheck": "bunx tsc --noEmit -p tsconfig.json",
9 "start": "NODE_ENV=production bun src/server.ts"
10 },
11 "dependencies": {
12 "@executor-v3/react": "workspace:*",
13 "@executor-v3/server": "workspace:*",
14 "@tanstack/react-router": "^1.132.7",
15 "class-variance-authority": "^0.7.1",
16 "clsx": "^2.1.1",
17 "effect": "catalog:",
18 "react": "^19.2.0",
19 "react-dom": "^19.2.0",
20 "react-grab": "^0.1.26",
21 "scheduler": "^0.27.0",
22 "shiki": "^4.0.1",
23 "tailwind-merge": "^3.5.0"
24 },
25 "devDependencies": {
26 "@hono/vite-dev-server": "^0.25.0",
27 "@tailwindcss/vite": "^4.2.1",
28 "@types/react": "^19.2.10",
29 "@types/react-dom": "^19.2.3",
30 "@vitejs/plugin-react": "^5.1.4",
31 "bun-types": "catalog:",
32 "tailwindcss": "^4.2.1",
33 "tw-animate-css": "^1.4.0",
34 "vite": "^7.3.1"
35 }
36}
apps/web/src/components/code-block.tsx+96−0
@@ -0,0 +1,96 @@
1import { useEffect, useState, useRef, useCallback } from "react";
2import { codeToHtml, type BundledLanguage } from "shiki";
3import { cn } from "../lib/utils";
4import { IconCopy, IconCheck } from "./icons";
5
6// Lazily loaded, cached across renders
7const highlightCache = new Map<string, string>();
8
9function cacheKey(code: string, lang: string) {
10 return `${lang}::${code.length}::${code.slice(0, 64)}`;
11}
12
13function detectLanguage(code: string, hint?: string): BundledLanguage {
14 if (hint) return hint as BundledLanguage;
15 const trimmed = code.trimStart();
16 if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
17 if (trimmed.startsWith("<")) return "xml";
18 if (trimmed.startsWith("---")) return "yaml";
19 return "json";
20}
21
22export function CodeBlock(props: {
23 code: string;
24 lang?: string;
25 className?: string;
26}) {
27 const { code, lang: langHint, className } = props;
28 const [html, setHtml] = useState<string | null>(null);
29 const [copied, setCopied] = useState(false);
30 const mountedRef = useRef(true);
31
32 const language = detectLanguage(code, langHint);
33 const key = cacheKey(code, language);
34
35 useEffect(() => {
36 mountedRef.current = true;
37 return () => {
38 mountedRef.current = false;
39 };
40 }, []);
41
42 useEffect(() => {
43 const cached = highlightCache.get(key);
44 if (cached) {
45 setHtml(cached);
46 return;
47 }
48
49 let cancelled = false;
50 codeToHtml(code, {
51 lang: language,
52 theme: "vitesse-dark",
53 }).then((result) => {
54 if (cancelled) return;
55 highlightCache.set(key, result);
56 if (mountedRef.current) setHtml(result);
57 });
58
59 return () => {
60 cancelled = true;
61 };
62 }, [code, key, language]);
63
64 const handleCopy = useCallback(() => {
65 void navigator.clipboard.writeText(code).then(() => {
66 setCopied(true);
67 setTimeout(() => setCopied(false), 1500);
68 });
69 }, [code]);
70
71 return (
72 <div className={cn("group relative overflow-auto", className)}>
73 {/* Copy button */}
74 <button
75 type="button"
76 onClick={handleCopy}
77 className="absolute right-2 top-2 z-10 rounded-md border border-border bg-card/90 p-1.5 text-muted-foreground/40 opacity-0 backdrop-blur-sm transition-all hover:text-foreground group-hover:opacity-100"
78 title="Copy to clipboard"
79 >
80 {copied ? <IconCheck className="size-3" /> : <IconCopy className="size-3" />}
81 </button>
82
83 {html ? (
84 <div
85 className="shiki-container text-[12px] leading-relaxed [&_pre]:!bg-transparent [&_pre]:p-3 [&_code]:font-mono"
86 dangerouslySetInnerHTML={{ __html: html }}
87 />
88 ) : (
89 // Fallback while shiki loads
90 <pre className="p-3 font-mono text-[12px] leading-relaxed text-foreground/60">
91 {code}
92 </pre>
93 )}
94 </div>
95 );
96}
apps/web/src/components/document-panel.tsx+44−0
@@ -0,0 +1,44 @@
1import { useMemo } from "react";
2import { cn } from "../lib/utils";
3import { CodeBlock } from "./code-block";
4
5function prettyJson(raw: string): string {
6 try {
7 return JSON.stringify(JSON.parse(raw), null, 2);
8 } catch {
9 return raw;
10 }
11}
12
13export function DocumentPanel(props: {
14 title: string;
15 body: string | null;
16 lang?: string;
17 empty: string;
18 compact?: boolean;
19}) {
20 const formatted = useMemo(
21 () => (props.body ? prettyJson(props.body) : null),
22 [props.body],
23 );
24
25 return (
26 <section
27 className={cn(
28 "overflow-hidden rounded-lg border border-border bg-card/60",
29 props.compact && "min-h-48",
30 )}
31 >
32 <div className="border-b border-border px-3 py-2 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70">
33 {props.title}
34 </div>
35 {formatted ? (
36 <CodeBlock code={formatted} lang={props.lang} className="max-h-[32rem]" />
37 ) : (
38 <div className="flex items-center justify-center p-6 text-[13px] text-muted-foreground/40">
39 {props.empty}
40 </div>
41 )}
42 </section>
43 );
44}
apps/web/src/components/icons.tsx+109−0
@@ -0,0 +1,109 @@
1import type { SVGProps } from "react";
2import { cn } from "../lib/utils";
3
4type IconProps = SVGProps<SVGSVGElement> & { className?: string };
5
6export function IconSources({ className, ...props }: IconProps) {
7 return (
8 <svg viewBox="0 0 16 16" fill="none" className={cn("size-4", className)} {...props}>
9 <rect x="2" y="2" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
10 <rect x="9" y="2" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
11 <rect x="2" y="9" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
12 <rect x="9" y="9" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.3" />
13 </svg>
14 );
15}
16
17export function IconTool({ className, ...props }: IconProps) {
18 return (
19 <svg viewBox="0 0 16 16" fill="none" className={cn("size-4", className)} {...props}>
20 <path d="M4 8h8M8 4v8" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
21 </svg>
22 );
23}
24
25export function IconSearch({ className, ...props }: IconProps) {
26 return (
27 <svg viewBox="0 0 16 16" fill="none" className={cn("size-4", className)} {...props}>
28 <circle cx="7" cy="7" r="4.5" stroke="currentColor" strokeWidth="1.3" />
29 <path d="M10.5 10.5L14 14" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
30 </svg>
31 );
32}
33
34export function IconChevron({ className, ...props }: IconProps) {
35 return (
36 <svg viewBox="0 0 16 16" fill="none" className={cn("size-3", className)} {...props}>
37 <path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
38 </svg>
39 );
40}
41
42export function IconFolder({ className, ...props }: IconProps) {
43 return (
44 <svg viewBox="0 0 16 16" fill="none" className={cn("size-3", className)} {...props}>
45 <path d="M2 4h5l2 2h5v7H2V4z" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round" />
46 </svg>
47 );
48}
49
50export function IconDocument({ className, ...props }: IconProps) {
51 return (
52 <svg viewBox="0 0 16 16" fill="none" className={cn("size-4", className)} {...props}>
53 <path d="M4 2h5l3 3v9H4V2z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" />
54 <path d="M6 8h4M6 10.5h3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
55 </svg>
56 );
57}
58
59export function IconDiscover({ className, ...props }: IconProps) {
60 return (
61 <svg viewBox="0 0 16 16" fill="none" className={cn("size-4", className)} {...props}>
62 <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.3" />
63 <path d="M6.5 5.5l4.5 2-2 4.5-4.5-2z" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round" />
64 </svg>
65 );
66}
67
68export function IconCopy({ className, ...props }: IconProps) {
69 return (
70 <svg viewBox="0 0 16 16" fill="none" className={cn("size-3.5", className)} {...props}>
71 <rect x="5" y="5" width="8" height="8" rx="1" stroke="currentColor" strokeWidth="1.2" />
72 <path d="M11 3H4a1 1 0 00-1 1v7" stroke="currentColor" strokeWidth="1.2" />
73 </svg>
74 );
75}
76
77export function IconCheck({ className, ...props }: IconProps) {
78 return (
79 <svg viewBox="0 0 16 16" fill="none" className={cn("size-3.5", className)} {...props}>
80 <path d="M4 8.5l3 3 5-6" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
81 </svg>
82 );
83}
84
85export function IconClose({ className, ...props }: IconProps) {
86 return (
87 <svg viewBox="0 0 16 16" fill="none" className={cn("size-3", className)} {...props}>
88 <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
89 </svg>
90 );
91}
92
93export function IconEmpty({ className, ...props }: IconProps) {
94 return (
95 <svg viewBox="0 0 48 48" fill="none" className={cn("size-12", className)} {...props}>
96 <rect x="6" y="6" width="36" height="36" rx="4" stroke="currentColor" strokeWidth="1.5" />
97 <path d="M16 18h16M16 24h12M16 30h8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
98 </svg>
99 );
100}
101
102export function IconSpinner({ className, ...props }: IconProps) {
103 return (
104 <svg viewBox="0 0 16 16" fill="none" className={cn("size-4 animate-spin", className)} {...props}>
105 <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.5" opacity="0.2" />
106 <path d="M14 8a6 6 0 00-6-6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
107 </svg>
108 );
109}
apps/web/src/components/loadable.tsx+50−0
@@ -0,0 +1,50 @@
1import type { ReactNode } from "react";
2import type { Loadable } from "@executor-v3/react";
3import { IconSpinner, IconEmpty } from "./icons";
4
5export function LoadableBlock<T>(props: {
6 loadable: Loadable<T>;
7 loading?: string;
8 children: (data: T) => ReactNode;
9}) {
10 if (props.loadable.status === "loading") {
11 return (
12 <div className="flex h-full min-h-48 items-center justify-center">
13 <div className="flex items-center gap-3 text-sm text-muted-foreground">
14 <IconSpinner />
15 <span>{props.loading ?? "Loading..."}</span>
16 </div>
17 </div>
18 );
19 }
20
21 if (props.loadable.status === "error") {
22 return (
23 <div className="flex h-full min-h-48 items-center justify-center">
24 <div className="rounded-lg border border-destructive/25 bg-destructive/5 px-4 py-3 text-sm text-destructive">
25 {props.loadable.error.message}
26 </div>
27 </div>
28 );
29 }
30
31 return <>{props.children(props.loadable.data)}</>;
32}
33
34export function EmptyState(props: {
35 title: string;
36 description?: string;
37 className?: string;
38}) {
39 return (
40 <div className={`flex h-full items-center justify-center ${props.className ?? ""}`}>
41 <div className="text-center">
42 <IconEmpty className="mx-auto mb-3 text-muted-foreground/20" />
43 <p className="text-[13px] text-muted-foreground/60">{props.title}</p>
44 {props.description && (
45 <p className="mt-1 text-[11px] text-muted-foreground/40">{props.description}</p>
46 )}
47 </div>
48 </div>
49 );
50}
apps/web/src/components/shell.tsx+232−0
@@ -0,0 +1,232 @@
1import { useState, useMemo } from "react";
2import { Link, Outlet, useMatchRoute } from "@tanstack/react-router";
3import { useSources, type Source } from "@executor-v3/react";
4import { cn } from "../lib/utils";
5import { IconSources, IconChevron } from "./icons";
6import { LoadableBlock } from "./loadable";
7
8// ── Kind labels + ordering ───────────────────────────────────────────────
9
10const kindMeta: Record<string, { label: string; order: number }> = {
11 mcp: { label: "MCP", order: 0 },
12 openapi: { label: "OpenAPI", order: 1 },
13 graphql: { label: "GraphQL", order: 2 },
14 internal: { label: "Internal", order: 3 },
15};
16
17function groupByKind(sources: ReadonlyArray<Source>) {
18 const groups = new Map<string, Source[]>();
19 for (const s of sources) {
20 const list = groups.get(s.kind) ?? [];
21 list.push(s);
22 groups.set(s.kind, list);
23 }
24 return [...groups.entries()].sort(
25 (a, b) => (kindMeta[a[0]]?.order ?? 99) - (kindMeta[b[0]]?.order ?? 99),
26 );
27}
28
29// ── Status dot color ─────────────────────────────────────────────────────
30
31const statusColor: Record<string, string> = {
32 connected: "bg-primary",
33 probing: "bg-amber-400",
34 draft: "bg-muted-foreground/30",
35 auth_required: "bg-amber-500",
36 error: "bg-destructive",
37};
38
39// ── AppShell ─────────────────────────────────────────────────────────────
40
41export function AppShell() {
42 const sources = useSources();
43 const matchRoute = useMatchRoute();
44 const isHome = matchRoute({ to: "/" });
45
46 return (
47 <div className="flex h-screen overflow-hidden">
48 {/* Sidebar */}
49 <aside className="flex w-52 shrink-0 flex-col border-r border-sidebar-border bg-sidebar lg:w-56">
50 {/* Brand */}
51 <div className="flex h-12 shrink-0 items-center border-b border-sidebar-border px-4">
52 <Link to="/" className="flex items-center gap-1.5">
53 <span className="font-display text-base tracking-tight text-foreground">
54 executor
55 </span>
56 <span className="rounded bg-primary/15 px-1.5 py-0.5 text-[10px] font-medium text-primary">
57 v3
58 </span>
59 </Link>
60 </div>
61
62 {/* Main nav */}
63 <nav className="flex flex-1 flex-col p-2 overflow-y-auto">
64 <NavItem to="/" label="Dashboard" active={!!isHome} />
65
66 {/* Sources tree */}
67 <div className="mt-5 mb-1 px-2.5 text-[10px] font-medium uppercase tracking-widest text-muted-foreground/50">
68 Sources
69 </div>
70 <LoadableBlock loadable={sources} loading="Loading...">
71 {(items) =>
72 items.length === 0 ? (
73 <div className="px-2.5 py-2 text-[11px] leading-relaxed text-muted-foreground/40">
74 No sources yet
75 </div>
76 ) : (
77 <SourceTree sources={items} matchRoute={matchRoute} />
78 )
79 }
80 </LoadableBlock>
81 </nav>
82
83 {/* Footer */}
84 <div className="shrink-0 border-t border-sidebar-border px-4 py-2.5">
85 <div className="text-[10px] text-muted-foreground/30">
86 Local instance
87 </div>
88 </div>
89 </aside>
90
91 {/* Main content */}
92 <main className="flex flex-1 flex-col min-w-0 overflow-hidden">
93 <Outlet />
94 </main>
95 </div>
96 );
97}
98
99// ── SourceTree ───────────────────────────────────────────────────────────
100
101function SourceTree(props: {
102 sources: ReadonlyArray<Source>;
103 matchRoute: ReturnType<typeof useMatchRoute>;
104}) {
105 const groups = useMemo(() => groupByKind(props.sources), [props.sources]);
106
107 // If there's only one kind, skip the group headers
108 if (groups.length === 1) {
109 return (
110 <div className="flex flex-col gap-px">
111 {groups[0]![1].map((source) => (
112 <SourceItem key={source.id} source={source} matchRoute={props.matchRoute} depth={0} />
113 ))}
114 </div>
115 );
116 }
117
118 return (
119 <div className="flex flex-col gap-0.5">
120 {groups.map(([kind, sources]) => (
121 <KindGroup key={kind} kind={kind} sources={sources} matchRoute={props.matchRoute} />
122 ))}
123 </div>
124 );
125}
126
127// ── KindGroup (collapsible) ──────────────────────────────────────────────
128
129function KindGroup(props: {
130 kind: string;
131 sources: Source[];
132 matchRoute: ReturnType<typeof useMatchRoute>;
133}) {
134 // Auto-expand if any source in this group is active
135 const hasActive = props.sources.some((s) =>
136 props.matchRoute({ to: "/sources/$sourceId", params: { sourceId: s.id }, fuzzy: true }),
137 );
138 const [open, setOpen] = useState(true);
139
140 return (
141 <div>
142 {/* Group header */}
143 <button
144 type="button"
145 onClick={() => setOpen((o) => !o)}
146 className="group flex w-full items-center gap-1.5 rounded-md px-2 py-1 text-[11px] font-medium text-muted-foreground/60 transition-colors hover:text-muted-foreground"
147 >
148 <IconChevron
149 className={cn(
150 "size-2.5 shrink-0 text-muted-foreground/30 transition-transform duration-150",
151 open && "rotate-90",
152 )}
153 />
154 <span className="uppercase tracking-wider">{kindMeta[props.kind]?.label ?? props.kind}</span>
155 <span className="ml-auto tabular-nums text-[10px] text-muted-foreground/30">
156 {props.sources.length}
157 </span>
158 </button>
159
160 {/* Children */}
161 {open && (
162 <div className="flex flex-col gap-px mt-px">
163 {props.sources.map((source) => (
164 <SourceItem key={source.id} source={source} matchRoute={props.matchRoute} depth={1} />
165 ))}
166 </div>
167 )}
168 </div>
169 );
170}
171
172// ── SourceItem ───────────────────────────────────────────────────────────
173
174function SourceItem(props: {
175 source: Source;
176 matchRoute: ReturnType<typeof useMatchRoute>;
177 depth: number;
178}) {
179 const { source, matchRoute, depth } = props;
180 const active = matchRoute({
181 to: "/sources/$sourceId",
182 params: { sourceId: source.id },
183 fuzzy: true,
184 });
185
186 return (
187 <Link
188 to="/sources/$sourceId"
189 params={{ sourceId: source.id }}
190 search={{ tab: "model" }}
191 className={cn(
192 "group flex items-center gap-2 rounded-md py-1.5 text-[12px] transition-colors",
193 depth === 0 ? "px-2.5" : "pl-6 pr-2.5",
194 active
195 ? "bg-sidebar-active text-foreground font-medium"
196 : "text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground",
197 )}
198 >
199 {/* Tree connector line for nested items */}
200 {depth > 0 && (
201 <span className="absolute left-[18px] h-full w-px bg-sidebar-border" aria-hidden />
202 )}
203
204 <IconSources className="size-3 shrink-0 text-muted-foreground/40" />
205 <span className="flex-1 truncate">{source.name}</span>
206
207 {/* Status dot + tooltip */}
208 <span
209 className={cn("size-1.5 shrink-0 rounded-full", statusColor[source.status] ?? "bg-muted-foreground/30")}
210 title={source.status}
211 />
212 </Link>
213 );
214}
215
216// ── NavItem ──────────────────────────────────────────────────────────────
217
218function NavItem(props: { to: string; label: string; active: boolean }) {
219 return (
220 <Link
221 to={props.to}
222 className={cn(
223 "flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors",
224 props.active
225 ? "bg-sidebar-active text-foreground font-medium"
226 : "text-sidebar-foreground hover:bg-sidebar-active/60 hover:text-foreground",
227 )}
228 >
229 {props.label}
230 </Link>
231 );
232}
apps/web/src/components/ui/badge.tsx+43−0
@@ -0,0 +1,43 @@
1import type { HTMLAttributes } from "react";
2import { cva, type VariantProps } from "class-variance-authority";
3import { cn } from "../../lib/utils";
4
5const badgeVariants = cva(
6 "inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium tracking-wide uppercase",
7 {
8 variants: {
9 variant: {
10 default: "border-transparent bg-primary/15 text-primary",
11 secondary: "border-transparent bg-secondary text-secondary-foreground",
12 outline: "border-border text-muted-foreground",
13 muted: "border-transparent bg-muted text-muted-foreground",
14 get: "border-[var(--method-get)]/20 bg-[var(--method-get)]/10 text-[var(--method-get)]",
15 post: "border-[var(--method-post)]/20 bg-[var(--method-post)]/10 text-[var(--method-post)]",
16 put: "border-[var(--method-put)]/20 bg-[var(--method-put)]/10 text-[var(--method-put)]",
17 delete: "border-[var(--method-delete)]/20 bg-[var(--method-delete)]/10 text-[var(--method-delete)]",
18 destructive: "border-destructive/20 bg-destructive/10 text-destructive",
19 },
20 },
21 defaultVariants: {
22 variant: "default",
23 },
24 },
25);
26
27export type BadgeProps = HTMLAttributes<HTMLSpanElement> & VariantProps<typeof badgeVariants>;
28
29export function Badge({ className, variant, ...props }: BadgeProps) {
30 return <span className={cn(badgeVariants({ variant }), className)} {...props} />;
31}
32
33export function MethodBadge({ method }: { method: string }) {
34 const variant = ({
35 GET: "get",
36 POST: "post",
37 PUT: "put",
38 PATCH: "put",
39 DELETE: "delete",
40 } as const)[method.toUpperCase()] ?? "outline";
41
42 return <Badge variant={variant}>{method}</Badge>;
43}
apps/web/src/components/ui/button.tsx+34−0
@@ -0,0 +1,34 @@
1import type { ButtonHTMLAttributes } from "react";
2import { cva, type VariantProps } from "class-variance-authority";
3import { cn } from "../../lib/utils";
4
5const buttonVariants = cva(
6 "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-1 focus-visible:ring-offset-background cursor-pointer",
7 {
8 variants: {
9 variant: {
10 default: "bg-primary text-primary-foreground hover:bg-primary/90",
11 secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
12 outline: "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
13 ghost: "hover:bg-accent hover:text-accent-foreground",
14 destructive: "bg-destructive text-white hover:bg-destructive/90",
15 },
16 size: {
17 default: "h-9 px-4 py-2",
18 sm: "h-7 rounded-md px-2.5 text-xs",
19 lg: "h-10 rounded-md px-6",
20 icon: "h-8 w-8",
21 },
22 },
23 defaultVariants: {
24 variant: "default",
25 size: "default",
26 },
27 },
28);
29
30type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>;
31
32export function Button({ className, variant, size, ...props }: ButtonProps) {
33 return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />;
34}
apps/web/src/dev.ts+29−0
@@ -0,0 +1,29 @@
1/**
2 * Dev entry point for @hono/vite-dev-server.
3 *
4 * Exports `{ fetch }` so Vite can forward API requests to the executor
5 * control-plane handler. Everything else (frontend assets, HMR) is
6 * handled by Vite itself.
7 */
8import * as Effect from "effect/Effect";
9import * as Scope from "effect/Scope";
10import { createLocalExecutorRequestHandler } from "@executor-v3/server";
11
12// Create a long-lived scope that stays open for the lifetime of the process.
13const handlerPromise = Effect.runPromise(
14 Effect.gen(function* () {
15 const scope = yield* Scope.make();
16 const handler = yield* createLocalExecutorRequestHandler().pipe(
17 Effect.provideService(Scope.Scope, scope),
18 );
19 handler.setBaseUrl("http://127.0.0.1:8788");
20 return handler;
21 }),
22);
23
24export default {
25 async fetch(request: Request) {
26 const handler = await handlerPromise;
27 return handler.handleApiRequest(request);
28 },
29};
apps/web/src/frontend.tsx+10−0
@@ -0,0 +1,10 @@
1import { createRoot } from "react-dom/client";
2import { App } from "./main";
3
4const rootElement = document.getElementById("root");
5
6if (!rootElement) {
7 throw new Error("Missing #root element");
8}
9
10createRoot(rootElement).render(<App />);
apps/web/src/globals.css+107−0
@@ -0,0 +1,107 @@
1@import "tailwindcss";
2@import "tw-animate-css";
3
4@custom-variant dark (&:is(.dark *));
5
6@theme inline {
7 --font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif;
8 --font-mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, monospace;
9 --font-display: "Instrument Serif", Georgia, serif;
10
11 --color-background: var(--background);
12 --color-foreground: var(--foreground);
13 --color-card: var(--card);
14 --color-card-foreground: var(--card-foreground);
15 --color-popover: var(--popover);
16 --color-popover-foreground: var(--popover-foreground);
17 --color-primary: var(--primary);
18 --color-primary-foreground: var(--primary-foreground);
19 --color-secondary: var(--secondary);
20 --color-secondary-foreground: var(--secondary-foreground);
21 --color-muted: var(--muted);
22 --color-muted-foreground: var(--muted-foreground);
23 --color-accent: var(--accent);
24 --color-accent-foreground: var(--accent-foreground);
25 --color-destructive: var(--destructive);
26 --color-border: var(--border);
27 --color-input: var(--input);
28 --color-ring: var(--ring);
29
30 --color-sidebar: var(--sidebar);
31 --color-sidebar-foreground: var(--sidebar-foreground);
32 --color-sidebar-border: var(--sidebar-border);
33 --color-sidebar-active: var(--sidebar-active);
34}
35
36/*
37 * Dark-first. An opinionated dark palette:
38 * charcoal base, warm stone accents, teal primary, amber warning.
39 * No light mode — this is a dev tool.
40 */
41:root {
42 --background: oklch(0.115 0.005 260);
43 --foreground: oklch(0.90 0.008 250);
44 --card: oklch(0.145 0.005 260);
45 --card-foreground: oklch(0.90 0.008 250);
46 --popover: oklch(0.145 0.005 260);
47 --popover-foreground: oklch(0.90 0.008 250);
48 --primary: oklch(0.72 0.15 175);
49 --primary-foreground: oklch(0.12 0.01 175);
50 --secondary: oklch(0.195 0.007 260);
51 --secondary-foreground: oklch(0.78 0.008 250);
52 --muted: oklch(0.175 0.005 260);
53 --muted-foreground: oklch(0.56 0.01 250);
54 --accent: oklch(0.195 0.008 260);
55 --accent-foreground: oklch(0.90 0.008 250);
56 --destructive: oklch(0.62 0.22 25);
57 --border: oklch(0.235 0.007 260);
58 --input: oklch(0.195 0.007 260);
59 --ring: oklch(0.72 0.15 175);
60
61 --sidebar: oklch(0.105 0.005 260);
62 --sidebar-foreground: oklch(0.72 0.008 250);
63 --sidebar-border: oklch(0.20 0.006 260);
64 --sidebar-active: oklch(0.18 0.008 260);
65
66 /* Method colors */
67 --method-get: oklch(0.75 0.16 155);
68 --method-post: oklch(0.72 0.14 230);
69 --method-put: oklch(0.78 0.14 85);
70 --method-delete: oklch(0.70 0.18 25);
71}
72
73@layer base {
74 * {
75 @apply border-border;
76 }
77
78 html,
79 body,
80 #root {
81 min-height: 100%;
82 height: 100%;
83 }
84
85 body {
86 @apply bg-background font-sans text-foreground antialiased;
87 }
88
89 /* Scrollbar styling */
90 ::-webkit-scrollbar {
91 width: 6px;
92 height: 6px;
93 }
94
95 ::-webkit-scrollbar-track {
96 background: transparent;
97 }
98
99 ::-webkit-scrollbar-thumb {
100 background: oklch(0.30 0.005 260);
101 border-radius: 3px;
102 }
103
104 ::-webkit-scrollbar-thumb:hover {
105 background: oklch(0.38 0.005 260);
106 }
107}
apps/web/src/index.html+29−0
@@ -0,0 +1,29 @@
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Executor</title>
7 <meta
8 name="description"
9 content="Executor v3 — manage sources, inspect tools, and monitor executions."
10 />
11 <link rel="preconnect" href="https://fonts.googleapis.com" />
12 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
13 <script type="module">
14 // first npm i react-grab
15 // then in head:
16 if (import.meta.env.DEV) {
17 import("react-grab");
18 }
19 </script>
20 <link
21 href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=Instrument+Serif&family=JetBrains+Mono:wght@400;500&display=swap"
22 rel="stylesheet"
23 />
24 </head>
25 <body>
26 <div id="root"></div>
27 <script type="module" src="./frontend.tsx"></script>
28 </body>
29</html>
apps/web/src/lib/utils.ts+6−0
@@ -0,0 +1,6 @@
1import { clsx, type ClassValue } from "clsx";
2import { twMerge } from "tailwind-merge";
3
4export function cn(...inputs: ReadonlyArray<ClassValue>): string {
5 return twMerge(clsx(inputs));
6}
apps/web/src/main.tsx+100−0
@@ -0,0 +1,100 @@
1import * as React from "react";
2import {
3 RouterProvider,
4 createRootRoute,
5 createRoute,
6 createRouter,
7 useNavigate,
8} from "@tanstack/react-router";
9import { ExecutorReactProvider } from "@executor-v3/react";
10
11import "./globals.css";
12
13import { AppShell } from "./components/shell";
14import { HomePage } from "./views/home";
15import { SourceDetailPage } from "./views/source-detail";
16
17// ---------------------------------------------------------------------------
18// Route search schema
19// ---------------------------------------------------------------------------
20
21type SourceRouteSearch = {
22 tab: "model" | "discover" | "manifest" | "definitions" | "raw";
23 tool?: string;
24 query?: string;
25};
26
27const sourceTabs = ["model", "discover", "manifest", "definitions", "raw"] as const;
28
29// ---------------------------------------------------------------------------
30// Routes
31// ---------------------------------------------------------------------------
32
33const rootRoute = createRootRoute({
34 component: AppShell,
35});
36
37const homeRoute = createRoute({
38 getParentRoute: () => rootRoute,
39 path: "/",
40 component: HomePage,
41});
42
43const sourceRoute = createRoute({
44 getParentRoute: () => rootRoute,
45 path: "/sources/$sourceId",
46 validateSearch: (search: Record<string, unknown>): SourceRouteSearch => ({
47 tab:
48 typeof search.tab === "string" && sourceTabs.includes(search.tab as SourceRouteSearch["tab"])
49 ? (search.tab as SourceRouteSearch["tab"])
50 : "model",
51 tool: typeof search.tool === "string" && search.tool.length > 0 ? search.tool : undefined,
52 query: typeof search.query === "string" ? search.query : undefined,
53 }),
54 component: SourceDetailPageWrapper,
55});
56
57function SourceDetailPageWrapper() {
58 const { sourceId } = sourceRoute.useParams();
59 const search = sourceRoute.useSearch();
60 const navigate = useNavigate({ from: sourceRoute.fullPath });
61
62 return (
63 <SourceDetailPage
64 sourceId={sourceId}
65 search={search}
66 navigate={navigate as any}
67 />
68 );
69}
70
71// ---------------------------------------------------------------------------
72// Router
73// ---------------------------------------------------------------------------
74
75const routeTree = rootRoute.addChildren([homeRoute, sourceRoute]);
76
77const router = createRouter({
78 routeTree,
79 defaultPreload: "intent",
80});
81
82declare module "@tanstack/react-router" {
83 interface Register {
84 router: typeof router;
85 }
86}
87
88// ---------------------------------------------------------------------------
89// App
90// ---------------------------------------------------------------------------
91
92export function App() {
93 return (
94 <React.StrictMode>
95 <ExecutorReactProvider>
96 <RouterProvider router={router} />
97 </ExecutorReactProvider>
98 </React.StrictMode>
99 );
100}
apps/web/src/server.ts+13−0
@@ -0,0 +1,13 @@
1/**
2 * Production server. Serves the API + Vite-built static assets.
3 *
4 * In development, use `bun run dev` which starts Vite with
5 * @hono/vite-dev-server embedding the API via dev.ts.
6 */
7import { runLocalExecutorServer } from "@executor-v3/server";
8
9await runLocalExecutorServer({
10 ui: {
11 assetsDir: new URL("../dist", import.meta.url).pathname,
12 },
13});
apps/web/src/views/home.tsx+181−0
@@ -0,0 +1,181 @@
1import { Link } from "@tanstack/react-router";
2import { useSources, type Source } from "@executor-v3/react";
3import { LoadableBlock } from "../components/loadable";
4import { Badge } from "../components/ui/badge";
5import { IconSources, IconDiscover } from "../components/icons";
6import { CodeBlock } from "../components/code-block";
7
8export function HomePage() {
9 const sources = useSources();
10
11 return (
12 <div className="flex-1 overflow-y-auto">
13 <div className="mx-auto max-w-5xl px-6 py-8 lg:px-10 lg:py-12">
14 {/* Hero */}
15 <div className="mb-10">
16 <h1 className="font-display text-3xl tracking-tight text-foreground lg:text-4xl">
17 Dashboard
18 </h1>
19 <p className="mt-2 max-w-lg text-[15px] leading-relaxed text-muted-foreground">
20 Manage sources, inspect tool surfaces, and monitor executions.
21 </p>
22 </div>
23
24 {/* Quick stats row */}
25 <LoadableBlock loadable={sources} loading="Loading...">
26 {(items) => (
27 <div className="grid gap-3 sm:grid-cols-3 mb-8">
28 <QuickStat label="Connected sources" value={String(items.length)} />
29 <QuickStat
30 label="Total tools"
31 value={items.length > 0 ? `${items.length} sources` : "0"}
32 sub="Inspect a source to see tools"
33 />
34 <QuickStat label="Executions" value="--" sub="Coming soon" />
35 </div>
36 )}
37 </LoadableBlock>
38
39 {/* Two-column dashboard grid */}
40 <div className="grid gap-6 xl:grid-cols-2">
41 {/* Sources card */}
42 <section className="rounded-xl border border-border bg-card">
43 <div className="flex items-center justify-between border-b border-border px-5 py-3.5">
44 <div className="flex items-center gap-2.5">
45 <div className="flex size-7 items-center justify-center rounded-lg bg-primary/10 text-primary">
46 <IconSources className="size-3.5" />
47 </div>
48 <h2 className="text-sm font-semibold text-foreground">Sources</h2>
49 </div>
50 <span className="text-[11px] text-muted-foreground/50">
51 Connected tool providers
52 </span>
53 </div>
54 <div className="p-3">
55 <LoadableBlock loadable={sources} loading="Loading sources...">
56 {(items) =>
57 items.length === 0 ? (
58 <div className="flex min-h-40 items-center justify-center rounded-lg border border-dashed border-border">
59 <div className="text-center px-4">
60 <p className="text-[13px] font-medium text-foreground/70">No sources connected</p>
61 <p className="mt-1 text-[12px] text-muted-foreground/50">
62 Run{" "}
63 <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-[11px] text-primary">
64 executor dev seed-github
65 </code>{" "}
66 to get started
67 </p>
68 </div>
69 </div>
70 ) : (
71 <div className="space-y-1">
72 {items.map((source) => (
73 <SourceRow key={source.id} source={source} />
74 ))}
75 </div>
76 )
77 }
78 </LoadableBlock>
79 </div>
80 </section>
81
82 {/* Getting started / MCP setup card */}
83 <section className="rounded-xl border border-border bg-card">
84 <div className="flex items-center justify-between border-b border-border px-5 py-3.5">
85 <div className="flex items-center gap-2.5">
86 <div className="flex size-7 items-center justify-center rounded-lg bg-accent text-accent-foreground">
87 <IconDiscover className="size-3.5" />
88 </div>
89 <h2 className="text-sm font-semibold text-foreground">Quick start</h2>
90 </div>
91 </div>
92 <div className="p-5 space-y-4">
93 <div>
94 <h3 className="text-[13px] font-medium text-foreground">Connect to an agent</h3>
95 <p className="mt-1 text-[12px] leading-relaxed text-muted-foreground">
96 Point your MCP-compatible client at this executor instance to give agents access to all connected tool sources.
97 </p>
98 </div>
99 <div className="rounded-lg border border-border bg-muted/30 overflow-hidden">
100 <CodeBlock
101 lang="json"
102 code={JSON.stringify({
103 mcpServers: {
104 executor: {
105 url: `${typeof window !== "undefined" ? window.location.origin : "http://127.0.0.1:8788"}/v1/mcp`,
106 },
107 },
108 }, null, 2)}
109 />
110 </div>
111 <div className="space-y-2">
112 <StepItem number={1} text="Add a source via CLI or API" />
113 <StepItem number={2} text="Point your agent at the MCP endpoint above" />
114 <StepItem number={3} text="The agent discovers and calls tools through executor" />
115 </div>
116 </div>
117 </section>
118 </div>
119 </div>
120 </div>
121 );
122}
123
124function SourceRow({ source }: { source: Source }) {
125 return (
126 <Link
127 to="/sources/$sourceId"
128 params={{ sourceId: source.id }}
129 search={{ tab: "model" }}
130 className="group flex items-center gap-3 rounded-lg px-3 py-2.5 transition-colors hover:bg-accent/50"
131 >
132 <div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
133 <IconSources className="size-3.5" />
134 </div>
135 <div className="flex-1 min-w-0">
136 <div className="flex items-center gap-2">
137 <span className="truncate text-[13px] font-medium text-foreground group-hover:text-primary transition-colors">
138 {source.name}
139 </span>
140 <Badge variant="outline" className="text-[9px]">{source.kind}</Badge>
141 </div>
142 <p className="truncate text-[11px] text-muted-foreground/50 font-mono mt-0.5">
143 {source.endpoint}
144 </p>
145 </div>
146 <Badge variant={source.status === "connected" ? "default" : "muted"} className="shrink-0">
147 {source.status}
148 </Badge>
149 <span className="text-[11px] text-muted-foreground/30 opacity-0 group-hover:opacity-100 transition-opacity">
150 &rarr;
151 </span>
152 </Link>
153 );
154}
155
156function QuickStat(props: { label: string; value: string; sub?: string }) {
157 return (
158 <div className="rounded-xl border border-border bg-card px-4 py-3.5">
159 <div className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/50">
160 {props.label}
161 </div>
162 <div className="mt-1 text-xl font-semibold tracking-tight text-foreground">
163 {props.value}
164 </div>
165 {props.sub && (
166 <div className="mt-0.5 text-[11px] text-muted-foreground/40">{props.sub}</div>
167 )}
168 </div>
169 );
170}
171
172function StepItem(props: { number: number; text: string }) {
173 return (
174 <div className="flex items-start gap-3">
175 <span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary">
176 {props.number}
177 </span>
178 <span className="text-[12px] leading-relaxed text-muted-foreground">{props.text}</span>
179 </div>
180 );
181}
apps/web/src/views/source-detail.tsx+604−0
@@ -0,0 +1,604 @@
1import { useState, useEffect, useCallback, useRef } from "react";
2import { useNavigate } from "@tanstack/react-router";
3import {
4 useSourceInspection,
5 useSourceToolDetail,
6 useSourceDiscovery,
7 type Loadable,
8 type SourceInspection,
9 type SourceInspectionToolDetail,
10 type SourceInspectionDiscoverResult,
11} from "@executor-v3/react";
12import { cn } from "../lib/utils";
13import { Badge, MethodBadge } from "../components/ui/badge";
14import { Button } from "../components/ui/button";
15import { LoadableBlock, EmptyState } from "../components/loadable";
16import { DocumentPanel } from "../components/document-panel";
17import {
18 IconSearch,
19 IconChevron,
20 IconTool,
21 IconCopy,
22 IconCheck,
23 IconClose,
24 IconEmpty,
25} from "../components/icons";
26
27// ---------------------------------------------------------------------------
28// Types
29// ---------------------------------------------------------------------------
30
31type SourceRouteSearch = {
32 tab: "model" | "discover" | "manifest" | "definitions" | "raw";
33 tool?: string;
34 query?: string;
35};
36
37const sourceTabs: Array<{ id: SourceRouteSearch["tab"]; label: string }> = [
38 { id: "model", label: "Model" },
39 { id: "discover", label: "Discover" },
40 { id: "manifest", label: "Manifest" },
41 { id: "definitions", label: "Definitions" },
42 { id: "raw", label: "Raw" },
43];
44
45// ---------------------------------------------------------------------------
46// SourceDetailPage (main export)
47// ---------------------------------------------------------------------------
48
49export function SourceDetailPage(props: {
50 sourceId: string;
51 search: SourceRouteSearch;
52 navigate: (opts: { search: (prev: SourceRouteSearch) => SourceRouteSearch; replace?: boolean }) => void;
53}) {
54 const { sourceId, search, navigate } = props;
55 const inspection = useSourceInspection(sourceId);
56
57 const selectedToolPath =
58 search.tool
59 ?? (inspection.status === "ready" ? inspection.data.tools[0]?.path : undefined);
60
61 const toolDetail = useSourceToolDetail(
62 sourceId,
63 search.tab === "model" ? selectedToolPath ?? null : null,
64 );
65
66 const discovery = useSourceDiscovery({
67 sourceId,
68 query: search.query ?? "",
69 limit: 12,
70 });
71
72 // Auto-select first tool
73 useEffect(() => {
74 if (search.tab !== "model" || search.tool || inspection.status !== "ready") return;
75 const firstTool = inspection.data.tools[0]?.path;
76 if (!firstTool) return;
77 void navigate({ search: (prev) => ({ ...prev, tool: firstTool }), replace: true });
78 }, [inspection, navigate, search.tab, search.tool]);
79
80 return (
81 <LoadableBlock loadable={inspection} loading="Resolving inspection bundle...">
82 {(bundle) => {
83 const selectedTool =
84 bundle.tools.find((t) => t.path === selectedToolPath) ?? bundle.tools[0] ?? null;
85
86 return (
87 <div className="flex h-full flex-col overflow-hidden">
88 {/* Header bar */}
89 <div className="flex shrink-0 items-center justify-between border-b border-border bg-background/95 backdrop-blur-sm px-4 h-12">
90 <div className="flex items-center gap-3 min-w-0">
91 <h2 className="truncate text-sm font-semibold text-foreground">
92 {bundle.source.name}
93 </h2>
94 <Badge variant="outline">{bundle.pipelineKind}</Badge>
95 <span className="hidden text-[11px] text-muted-foreground/50 font-mono sm:block">
96 {bundle.namespace}
97 </span>
98 </div>
99 <div className="flex items-center gap-1">
100 {sourceTabs.map((tab) => (
101 <button
102 key={tab.id}
103 type="button"
104 onClick={() => void navigate({ search: (prev) => ({ ...prev, tab: tab.id }) })}
105 className={cn(
106 "rounded-md px-2.5 py-1 text-[12px] font-medium transition-colors",
107 tab.id === search.tab
108 ? "bg-accent text-foreground"
109 : "text-muted-foreground hover:text-foreground hover:bg-accent/50",
110 )}
111 >
112 {tab.label}
113 </button>
114 ))}
115 </div>
116 </div>
117
118 {/* Tab content */}
119 <div className="flex flex-1 min-h-0 overflow-hidden">
120 {search.tab === "model" && (
121 <ModelView
122 bundle={bundle}
123 detail={toolDetail}
124 selectedToolPath={selectedTool?.path ?? null}
125 onSelectTool={(toolPath) =>
126 void navigate({ search: (prev) => ({ ...prev, tool: toolPath, tab: "model" }) })
127 }
128 />
129 )}
130 {search.tab === "discover" && (
131 <DiscoveryView
132 bundle={bundle}
133 discovery={discovery}
134 initialQuery={search.query ?? ""}
135 onSubmitQuery={(query) =>
136 void navigate({ search: (prev) => ({ ...prev, query, tab: "discover" }) })
137 }
138 onOpenTool={(toolPath) =>
139 void navigate({ search: (prev) => ({ ...prev, tab: "model", tool: toolPath }) })
140 }
141 />
142 )}
143 {search.tab === "manifest" && (
144 <div className="flex-1 overflow-y-auto p-4">
145 <DocumentPanel title="Extracted manifest" body={bundle.manifestJson} empty="No manifest snapshot available." />
146 </div>
147 )}
148 {search.tab === "definitions" && (
149 <div className="flex-1 overflow-y-auto p-4">
150 <DocumentPanel title="Compiled tool definitions" body={bundle.definitionsJson} empty="No compiled definitions snapshot available." />
151 </div>
152 )}
153 {search.tab === "raw" && (
154 <div className="flex-1 overflow-y-auto p-4">
155 <DocumentPanel title="Raw source document" body={bundle.rawDocumentText} empty="No raw source document stored." />
156 </div>
157 )}
158 </div>
159 </div>
160 );
161 }}
162 </LoadableBlock>
163 );
164}
165
166// ---------------------------------------------------------------------------
167// ModelView — full-screen two-panel: tool list + tool detail
168// ---------------------------------------------------------------------------
169
170function ModelView(props: {
171 bundle: SourceInspection;
172 detail: Loadable<SourceInspectionToolDetail | null>;
173 selectedToolPath: string | null;
174 onSelectTool: (toolPath: string) => void;
175}) {
176 const [search, setSearch] = useState("");
177 const searchRef = useRef<HTMLInputElement>(null);
178 const terms = search.trim().toLowerCase().split(/\s+/).filter(Boolean);
179
180 const filteredTools = props.bundle.tools.filter((tool) => {
181 if (terms.length === 0) return true;
182 const corpus = [tool.path, tool.description ?? "", tool.title ?? "", tool.method ?? ""]
183 .join(" ")
184 .toLowerCase();
185 return terms.every((t) => corpus.includes(t));
186 });
187
188 // Keyboard shortcut: / to focus search
189 useEffect(() => {
190 const handler = (e: KeyboardEvent) => {
191 if (e.key === "/" && document.activeElement?.tagName !== "INPUT") {
192 e.preventDefault();
193 searchRef.current?.focus();
194 }
195 if (e.key === "Escape") {
196 searchRef.current?.blur();
197 if (search.length > 0) setSearch("");
198 }
199 };
200 document.addEventListener("keydown", handler);
201 return () => document.removeEventListener("keydown", handler);
202 }, [search]);
203
204 return (
205 <>
206 {/* Left panel: tool tree */}
207 <div className="flex w-72 shrink-0 flex-col border-r border-border bg-card/30 lg:w-80 xl:w-[22rem]">
208 {/* Search */}
209 <div className="shrink-0 border-b border-border px-3 py-2">
210 <div className="relative">
211 <IconSearch className="absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground/40" />
212 <input
213 ref={searchRef}
214 value={search}
215 onChange={(e) => setSearch(e.target.value)}
216 placeholder={`Search ${props.bundle.toolCount} tools...`}
217 className="h-8 w-full rounded-md border border-input bg-background pl-8 pr-8 text-[13px] outline-none transition-colors placeholder:text-muted-foreground/40 focus:border-ring focus:ring-1 focus:ring-ring/30"
218 />
219 {search.length > 0 ? (
220 <button
221 type="button"
222 onClick={() => setSearch("")}
223 className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground/40 hover:text-foreground"
224 >
225 <IconClose />
226 </button>
227 ) : (
228 <kbd className="absolute right-2.5 top-1/2 -translate-y-1/2 rounded border border-border bg-muted px-1 py-px text-[10px] text-muted-foreground/50">
229 /
230 </kbd>
231 )}
232 </div>
233 </div>
234
235 {/* Tool header */}
236 <div className="flex shrink-0 items-center justify-between border-b border-border px-3 py-2">
237 <span className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/60">
238 Tools
239 </span>
240 <span className="text-[11px] tabular-nums text-muted-foreground/40">
241 {filteredTools.length !== props.bundle.toolCount
242 ? `${filteredTools.length} / ${props.bundle.toolCount}`
243 : props.bundle.toolCount}
244 </span>
245 </div>
246
247 {/* Tool list */}
248 <div className="flex-1 overflow-y-auto">
249 {filteredTools.length === 0 ? (
250 <div className="p-4 text-center text-[13px] text-muted-foreground/50">
251 {terms.length > 0 ? "No tools match your search" : "No tools available"}
252 </div>
253 ) : (
254 <div className="p-1.5 space-y-px">
255 {filteredTools.map((tool) => (
256 <ToolListItem
257 key={tool.path}
258 tool={tool}
259 active={tool.path === props.selectedToolPath}
260 onSelect={() => props.onSelectTool(tool.path)}
261 search={search}
262 />
263 ))}
264 </div>
265 )}
266 </div>
267 </div>
268
269 {/* Right panel: tool detail */}
270 <div className="flex flex-1 flex-col min-w-0 overflow-hidden">
271 <LoadableBlock loadable={props.detail} loading="Loading tool detail...">
272 {(detail) =>
273 detail ? (
274 <ToolDetailPanel detail={detail} />
275 ) : (
276 <EmptyState
277 title={props.bundle.toolCount > 0 ? "Select a tool to view its details" : "No tools available"}
278 description={props.bundle.toolCount > 0 ? "Browse the tree on the left or press / to search" : undefined}
279 />
280 )
281 }
282 </LoadableBlock>
283 </div>
284 </>
285 );
286}
287
288// ---------------------------------------------------------------------------
289// ToolListItem
290// ---------------------------------------------------------------------------
291
292function ToolListItem(props: {
293 tool: SourceInspection["tools"][number];
294 active: boolean;
295 onSelect: () => void;
296 search: string;
297}) {
298 const ref = useRef<HTMLButtonElement>(null);
299
300 useEffect(() => {
301 if (props.active && ref.current) {
302 ref.current.scrollIntoView({ block: "nearest" });
303 }
304 }, [props.active]);
305
306 return (
307 <button
308 ref={ref}
309 type="button"
310 onClick={props.onSelect}
311 className={cn(
312 "group flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left transition-colors",
313 props.active
314 ? "bg-primary/10 text-foreground border-l-2 border-l-primary -ml-px"
315 : "hover:bg-accent/50 text-foreground/70 hover:text-foreground",
316 )}
317 >
318 <IconTool className="size-3 shrink-0 text-muted-foreground/40" />
319 <span className="flex-1 truncate font-mono text-[12px]">
320 {highlightMatch(props.tool.path, props.search)}
321 </span>
322 {props.tool.method && <MethodBadge method={props.tool.method} />}
323 </button>
324 );
325}
326
327// ---------------------------------------------------------------------------
328// ToolDetailPanel
329// ---------------------------------------------------------------------------
330
331function ToolDetailPanel(props: { detail: SourceInspectionToolDetail }) {
332 const { detail } = props;
333 const [copiedField, setCopiedField] = useState<string | null>(null);
334
335 const copy = useCallback((text: string, field: string) => {
336 void navigator.clipboard.writeText(text).then(() => {
337 setCopiedField(field);
338 setTimeout(() => setCopiedField(null), 1500);
339 });
340 }, []);
341
342 return (
343 <div className="flex h-full flex-col overflow-hidden">
344 {/* Sticky header */}
345 <div className="shrink-0 border-b border-border bg-background/95 backdrop-blur-sm">
346 <div className="flex items-start gap-3 px-5 py-3.5">
347 <div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
348 <IconTool className="size-4" />
349 </div>
350 <div className="min-w-0 flex-1">
351 <div className="flex items-center gap-2">
352 <h3 className="truncate text-sm font-semibold text-foreground">
353 {detail.summary.path}
354 </h3>
355 <CopyButton text={detail.summary.path} field="path" copiedField={copiedField} onCopy={copy} />
356 </div>
357 <div className="mt-1 flex flex-wrap items-center gap-1.5">
358 <Badge variant="outline">{detail.summary.providerKind}</Badge>
359 {detail.summary.operationId && (
360 <span className="font-mono text-[11px] text-muted-foreground">
361 {detail.summary.operationId}
362 </span>
363 )}
364 </div>
365 </div>
366 </div>
367 </div>
368
369 {/* Scrollable body */}
370 <div className="flex-1 overflow-y-auto">
371 <div className="px-5 py-4 space-y-5">
372 {/* Summary cards */}
373 <div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
374 <MetricCard label="Input type" value={detail.summary.inputType ?? "unknown"} />
375 <MetricCard label="Output type" value={detail.summary.outputType ?? "unknown"} />
376 <MetricCard label="Tool ID" value={detail.summary.toolId} mono />
377 <MetricCard label="Path template" value={detail.summary.pathTemplate ?? "n/a"} mono />
378 </div>
379
380 {/* Schema panels */}
381 <div className="grid grid-cols-1 gap-2 xl:grid-cols-2">
382 <DocumentPanel title="Input schema" body={detail.inputSchemaJson} empty="No input schema." compact />
383 <DocumentPanel title="Output schema" body={detail.outputSchemaJson} empty="No output schema." compact />
384 <DocumentPanel title="Example input" body={detail.exampleInputJson} empty="No example input." compact />
385 <DocumentPanel title="Example output" body={detail.exampleOutputJson} empty="No example output." compact />
386 <DocumentPanel title="Provider detail" body={detail.providerDataJson} empty="No provider detail." compact />
387 <DocumentPanel title="Definition" body={detail.definitionJson} empty="No compiled definition." compact />
388 </div>
389 </div>
390 </div>
391 </div>
392 );
393}
394
395// ---------------------------------------------------------------------------
396// DiscoveryView
397// ---------------------------------------------------------------------------
398
399function DiscoveryView(props: {
400 bundle: SourceInspection;
401 discovery: Loadable<SourceInspectionDiscoverResult>;
402 initialQuery: string;
403 onSubmitQuery: (query: string) => void;
404 onOpenTool: (toolPath: string) => void;
405}) {
406 const [draftQuery, setDraftQuery] = useState(props.initialQuery);
407
408 useEffect(() => {
409 setDraftQuery(props.initialQuery);
410 }, [props.initialQuery]);
411
412 return (
413 <div className="flex flex-1 flex-col overflow-hidden">
414 {/* Search bar */}
415 <div className="shrink-0 border-b border-border px-4 py-3">
416 <form
417 className="flex items-center gap-2"
418 onSubmit={(e) => {
419 e.preventDefault();
420 props.onSubmitQuery(draftQuery.trim());
421 }}
422 >
423 <input
424 value={draftQuery}
425 onChange={(e) => setDraftQuery(e.target.value)}
426 placeholder="Search repos, issues, webhooks..."
427 className="h-9 flex-1 rounded-md border border-input bg-background px-3 text-sm outline-none transition-colors placeholder:text-muted-foreground/40 focus:border-ring focus:ring-1 focus:ring-ring/30"
428 />
429 <Button type="submit" size="sm">Run</Button>
430 </form>
431 </div>
432
433 {/* Results */}
434 <div className="flex flex-1 overflow-hidden">
435 {/* Sidebar stats */}
436 <aside className="hidden w-56 shrink-0 border-r border-border p-3 space-y-2 lg:block">
437 <MetricCard label="Source" value={props.bundle.source.name} />
438 <MetricCard label="Namespace" value={props.bundle.namespace} />
439 <MetricCard label="Tool count" value={String(props.bundle.toolCount)} />
440 </aside>
441
442 {/* Results list */}
443 <div className="flex-1 overflow-y-auto p-3">
444 <LoadableBlock loadable={props.discovery} loading="Scoring candidate tools...">
445 {(result) =>
446 result.query.length === 0 ? (
447 <EmptyState
448 title="Enter a query"
449 description="Search terms live in the URL so you can deep-link ranking investigations."
450 />
451 ) : result.results.length === 0 ? (
452 <EmptyState
453 title="No ranked matches"
454 description="Try a provider noun, operation verb, tag, or path fragment."
455 />
456 ) : (
457 <div className="space-y-2">
458 {result.results.map((item, index) => (
459 <article
460 key={item.path}
461 className="rounded-lg border border-border bg-card/60 p-3 space-y-2"
462 >
463 <div className="flex items-start justify-between gap-2">
464 <div className="flex items-center gap-2 min-w-0">
465 <span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-mono tabular-nums text-muted-foreground">
466 #{index + 1}
467 </span>
468 <h4 className="truncate font-mono text-[13px] font-medium text-foreground">
469 {item.path}
470 </h4>
471 </div>
472 <span className="shrink-0 font-mono text-sm font-semibold text-primary">
473 {item.score.toFixed(2)}
474 </span>
475 </div>
476 <p className="text-[12px] text-muted-foreground leading-relaxed">
477 {item.description ?? "No description"}
478 </p>
479 <div className="flex items-center gap-2 text-[11px] text-muted-foreground/60">
480 <span>{item.inputType ?? "unknown input"}</span>
481 <span>&rarr;</span>
482 <span>{item.outputType ?? "unknown output"}</span>
483 </div>
484 <div className="flex flex-wrap gap-1">
485 {item.reasons.map((reason) => (
486 <span
487 key={reason}
488 className="rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-medium text-primary"
489 >
490 {reason}
491 </span>
492 ))}
493 </div>
494 <Button
495 variant="ghost"
496 size="sm"
497 className="text-[11px]"
498 onClick={() => props.onOpenTool(item.path)}
499 >
500 Open tool detail &rarr;
501 </Button>
502 </article>
503 ))}
504 </div>
505 )
506 }
507 </LoadableBlock>
508 </div>
509 </div>
510 </div>
511 );
512}
513
514// ---------------------------------------------------------------------------
515// Shared sub-components
516// ---------------------------------------------------------------------------
517
518function MetricCard(props: { label: string; value: string; mono?: boolean }) {
519 return (
520 <div className="rounded-lg border border-border bg-card/60 px-3 py-2">
521 <div className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground/60">
522 {props.label}
523 </div>
524 <div className={cn(
525 "mt-0.5 truncate text-[13px] text-foreground/85",
526 props.mono && "font-mono text-[12px]",
527 )}>
528 {props.value}
529 </div>
530 </div>
531 );
532}
533
534function CopyButton(props: {
535 text: string;
536 field: string;
537 copiedField: string | null;
538 onCopy: (text: string, field: string) => void;
539}) {
540 return (
541 <button
542 type="button"
543 onClick={() => props.onCopy(props.text, props.field)}
544 className="shrink-0 rounded p-1 text-muted-foreground/30 transition-colors hover:text-muted-foreground"
545 title={`Copy ${props.field}`}
546 >
547 {props.copiedField === props.field ? <IconCheck /> : <IconCopy />}
548 </button>
549 );
550}
551
552function highlightMatch(text: string, search: string) {
553 if (!search.trim()) return text;
554 const terms = search.trim().toLowerCase().split(/\s+/);
555 const lowerText = text.toLowerCase();
556 const ranges: Array<[number, number]> = [];
557
558 for (const term of terms) {
559 let idx = 0;
560 while (idx < lowerText.length) {
561 const found = lowerText.indexOf(term, idx);
562 if (found === -1) break;
563 ranges.push([found, found + term.length]);
564 idx = found + 1;
565 }
566 }
567
568 if (ranges.length === 0) return text;
569
570 ranges.sort((a, b) => a[0] - b[0]);
571 const merged: Array<[number, number]> = [ranges[0]!];
572 for (let i = 1; i < ranges.length; i++) {
573 const last = merged[merged.length - 1]!;
574 const current = ranges[i]!;
575 if (current[0] <= last[1]) {
576 last[1] = Math.max(last[1], current[1]);
577 } else {
578 merged.push(current);
579 }
580 }
581
582 const parts: Array<{ text: string; hl: boolean }> = [];
583 let cursor = 0;
584 for (const [start, end] of merged) {
585 if (cursor < start) parts.push({ text: text.slice(cursor, start), hl: false });
586 parts.push({ text: text.slice(start, end), hl: true });
587 cursor = end;
588 }
589 if (cursor < text.length) parts.push({ text: text.slice(cursor), hl: false });
590
591 return (
592 <>
593 {parts.map((part, i) =>
594 part.hl ? (
595 <mark key={i} className="rounded-sm bg-primary/20 text-foreground px-px">
596 {part.text}
597 </mark>
598 ) : (
599 <span key={i}>{part.text}</span>
600 ),
601 )}
602 </>
603 );
604}
apps/web/tsconfig.json+17−0
@@ -0,0 +1,17 @@
1{
2 "compilerOptions": {
3 "target": "ES2022",
4 "module": "ESNext",
5 "moduleResolution": "Bundler",
6 "strict": true,
7 "skipLibCheck": true,
8 "noEmit": true,
9 "lib": ["ES2022", "DOM", "DOM.Iterable"],
10 "jsx": "react-jsx",
11 "types": ["bun-types"]
12 },
13 "include": [
14 "src/**/*.ts",
15 "src/**/*.tsx"
16 ]
17}
apps/web/vite.config.ts+29−0
@@ -0,0 +1,29 @@
1import { defineConfig } from "vite";
2import react from "@vitejs/plugin-react";
3import tailwindcss from "@tailwindcss/vite";
4import devServer from "@hono/vite-dev-server";
5
6export default defineConfig({
7 root: "src",
8 plugins: [
9 tailwindcss(),
10 react(),
11 devServer({
12 entry: "src/dev.ts",
13 exclude: [
14 // Only let /v1 requests reach the API handler
15 /^\/(?!v1(\/|$))/,
16 /^\/(src|node_modules|@vite|@id|@react-refresh)/,
17 /\.(css|ts|tsx|js|jsx|svg|png|jpg|gif|ico|woff2?|json|map)(\?.*)?$/,
18 ],
19 injectClientScript: false,
20 }),
21 ],
22 build: {
23 outDir: "../dist",
24 emptyOutDir: true,
25 },
26 server: {
27 port: 8788,
28 },
29});
bun.lock+453−2
@@ -6,6 +6,7 @@ · 5 unmodified lines
6 "name": "executor-v3",6 "name": "executor-v3",
7 "devDependencies": {7 "devDependencies": {
8 "knip": "^5.85.0",8 "knip": "^5.85.0",
9 "turbo": "^2.5.6",
9 "typescript": "^5.9.3",10 "typescript": "^5.9.3",
10 },11 },
11 },12 },
@@ -21,6 +22,7 @@ · 9 unmodified lines
21 "@executor-v3/codemode-core": "workspace:*",22 "@executor-v3/codemode-core": "workspace:*",
22 "@executor-v3/control-plane": "workspace:*",23 "@executor-v3/control-plane": "workspace:*",
23 "@executor-v3/runtime-local-inproc": "workspace:*",24 "@executor-v3/runtime-local-inproc": "workspace:*",
25 "@executor-v3/server": "workspace:*",
24 "effect": "catalog:",26 "effect": "catalog:",
25 },27 },
26 "devDependencies": {28 "devDependencies": {
@@ -44,6 +46,34 @@ · 17 unmodified lines
44 "bun-types": "catalog:",46 "bun-types": "catalog:",
45 },47 },
46 },48 },
49 "apps/web": {
50 "name": "@executor-v3/web",
51 "dependencies": {
52 "@executor-v3/react": "workspace:*",
53 "@executor-v3/server": "workspace:*",
54 "@tanstack/react-router": "^1.132.7",
55 "class-variance-authority": "^0.7.1",
56 "clsx": "^2.1.1",
57 "effect": "catalog:",
58 "react": "^19.2.0",
59 "react-dom": "^19.2.0",
60 "react-grab": "^0.1.26",
61 "scheduler": "^0.27.0",
62 "shiki": "^4.0.1",
63 "tailwind-merge": "^3.5.0",
64 },
65 "devDependencies": {
66 "@hono/vite-dev-server": "^0.25.0",
67 "@tailwindcss/vite": "^4.2.1",
68 "@types/react": "^19.2.10",
69 "@types/react-dom": "^19.2.3",
70 "@vitejs/plugin-react": "^5.1.4",
71 "bun-types": "catalog:",
72 "tailwindcss": "^4.2.1",
73 "tw-animate-css": "^1.4.0",
74 "vite": "^7.3.1",
75 },
76 },
47 "packages/ai-sdk-adapter": {77 "packages/ai-sdk-adapter": {
48 "name": "@executor-v3/ai-sdk-adapter",78 "name": "@executor-v3/ai-sdk-adapter",
49 "dependencies": {79 "dependencies": {
@@ -91,12 +121,12 @@ · 41 unmodified lines
91 "packages/codemode-openapi": {121 "packages/codemode-openapi": {
92 "name": "@executor-v3/codemode-openapi",122 "name": "@executor-v3/codemode-openapi",
93 "dependencies": {123 "dependencies": {
124 "@effect/platform": "catalog:",
94 "@executor-v3/codemode-core": "workspace:*",125 "@executor-v3/codemode-core": "workspace:*",
95 "effect": "catalog:",126 "effect": "catalog:",
96 "yaml": "^2.8.1",127 "yaml": "^2.8.1",
97 },128 },
98 "devDependencies": {129 "devDependencies": {
99 "@effect/platform": "catalog:",
100 "@effect/platform-node": "^0.104.1",130 "@effect/platform-node": "^0.104.1",
101 "@effect/vitest": "catalog:",131 "@effect/vitest": "catalog:",
102 "@types/node": "catalog:",132 "@types/node": "catalog:",
@@ -135,6 +165,7 @@ · 32 unmodified lines
135 "@executor-v3/ai-sdk-adapter": "workspace:*",165 "@executor-v3/ai-sdk-adapter": "workspace:*",
136 "@executor-v3/codemode-core": "workspace:*",166 "@executor-v3/codemode-core": "workspace:*",
137 "@executor-v3/codemode-openapi": "workspace:*",167 "@executor-v3/codemode-openapi": "workspace:*",
168 "@executor-v3/control-plane": "workspace:*",
138 "@executor-v3/runtime-local-inproc": "workspace:*",169 "@executor-v3/runtime-local-inproc": "workspace:*",
139 "effect": "catalog:",170 "effect": "catalog:",
140 },171 },
@@ -145,6 +176,22 @@ · 4 unmodified lines
145 "vitest": "catalog:",176 "vitest": "catalog:",
146 },177 },
147 },178 },
179 "packages/react": {
180 "name": "@executor-v3/react",
181 "dependencies": {
182 "@effect-atom/atom": "0.5.3",
183 "@effect-atom/atom-react": "0.5.0",
184 "@executor-v3/control-plane": "workspace:*",
185 "effect": "catalog:",
186 },
187 "devDependencies": {
188 "@types/react": "^19.2.10",
189 "bun-types": "catalog:",
190 },
191 "peerDependencies": {
192 "react": ">=18 <20",
193 },
194 },
148 "packages/runtime-local-inproc": {195 "packages/runtime-local-inproc": {
149 "name": "@executor-v3/runtime-local-inproc",196 "name": "@executor-v3/runtime-local-inproc",
150 "dependencies": {197 "dependencies": {
@@ -156,6 +203,18 @@ · 5 unmodified lines
156 "bun-types": "catalog:",203 "bun-types": "catalog:",
157 },204 },
158 },205 },
206 "packages/server": {
207 "name": "@executor-v3/server",
208 "dependencies": {
209 "@effect/platform": "catalog:",
210 "@executor-v3/control-plane": "workspace:*",
211 "effect": "catalog:",
212 },
213 "devDependencies": {
214 "@types/node": "catalog:",
215 "bun-types": "catalog:",
216 },
217 },
159 },218 },
160 "catalog": {219 "catalog": {
161 "@effect/cli": "^0.73.2",220 "@effect/cli": "^0.73.2",
@@ -175,6 +234,8 @@ · 13 unmodified lines
175 234
176 "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],235 "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="],
177 236
237 "@antfu/ni": ["@antfu/ni@0.23.2", "", { "bin": { "na": "bin/na.mjs", "ni": "bin/ni.mjs", "nr": "bin/nr.mjs", "nu": "bin/nu.mjs", "nci": "bin/nci.mjs", "nlx": "bin/nlx.mjs", "nun": "bin/nun.mjs" } }, "sha512-FSEVWXvwroExDXUu8qV6Wqp2X3D1nJ0Li4LFymCyvCVrm7I3lNfG0zZWSWvGU1RE7891eTnFTyh31L3igOwNKQ=="],
238
178 "@azure-rest/core-client": ["@azure-rest/core-client@2.5.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A=="],239 "@azure-rest/core-client": ["@azure-rest/core-client@2.5.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.10.0", "@azure/core-rest-pipeline": "^1.22.0", "@azure/core-tracing": "^1.3.0", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A=="],
179 240
180 "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],241 "@azure/abort-controller": ["@azure/abort-controller@2.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA=="],
@@ -209,8 +270,50 @@ · 28 unmodified lines
209 270
210 "@azure/msal-node": ["@azure/msal-node@3.8.8", "", { "dependencies": { "@azure/msal-common": "15.15.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-+f1VrJH1iI517t4zgmuhqORja0bL6LDQXfBqkjuMmfTYXTQQnh1EvwwxO3UbKLT05N0obF72SRHFrC1RBDv5Gg=="],271 "@azure/msal-node": ["@azure/msal-node@3.8.8", "", { "dependencies": { "@azure/msal-common": "15.15.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-+f1VrJH1iI517t4zgmuhqORja0bL6LDQXfBqkjuMmfTYXTQQnh1EvwwxO3UbKLT05N0obF72SRHFrC1RBDv5Gg=="],
211 272
273 "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
274
275 "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
276
277 "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
278
279 "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
280
281 "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
282
283 "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
284
285 "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
286
287 "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
288
289 "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
290
291 "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
292
293 "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
294
295 "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
296
297 "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
298
299 "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
300
301 "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
302
303 "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
304
305 "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
306
307 "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
308
309 "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
310
212 "@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],311 "@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
213 312
313 "@effect-atom/atom": ["@effect-atom/atom@0.5.3", "", { "peerDependencies": { "@effect/experimental": "^0.58.0", "@effect/platform": "^0.94.2", "@effect/rpc": "^0.73.0", "effect": "^3.19.15" } }, "sha512-TRZv/i+YT3TtnN0oFORJqXdxSs1fc7lrJlH+1xZvDFyjC9hgoVnrcKbeZsDFmr6r0wYRqVo7U3IftxiQNjpNZA=="],
314
315 "@effect-atom/atom-react": ["@effect-atom/atom-react@0.5.0", "", { "dependencies": { "@effect-atom/atom": "^0.5.0" }, "peerDependencies": { "effect": "^3.19", "react": ">=18 <20", "scheduler": "*" } }, "sha512-aFfjWi4rEJCqfM12Oi36/EKaDm/W6n4/N6yM5vL0t/QozKhJhK05rQL/GY4XMxlH2eqkQ4ih8jBQa3Yyp0Fiqw=="],
316
214 "@effect/cli": ["@effect/cli@0.73.2", "", { "dependencies": { "ini": "^4.1.3", "toml": "^3.0.0", "yaml": "^2.5.0" }, "peerDependencies": { "@effect/platform": "^0.94.3", "@effect/printer": "^0.47.0", "@effect/printer-ansi": "^0.47.0", "effect": "^3.19.16" } }, "sha512-K8IJo81+qa1LU8dhxcDU4QO/bIjL/dPd3zUOSCpLiuUNz8Y3/T+WNs3GqIXEhMfCFMSlRZERN0YgmtRlEZUREA=="],317 "@effect/cli": ["@effect/cli@0.73.2", "", { "dependencies": { "ini": "^4.1.3", "toml": "^3.0.0", "yaml": "^2.5.0" }, "peerDependencies": { "@effect/platform": "^0.94.3", "@effect/printer": "^0.47.0", "@effect/printer-ansi": "^0.47.0", "effect": "^3.19.16" } }, "sha512-K8IJo81+qa1LU8dhxcDU4QO/bIjL/dPd3zUOSCpLiuUNz8Y3/T+WNs3GqIXEhMfCFMSlRZERN0YgmtRlEZUREA=="],
215 318
216 "@effect/cluster": ["@effect/cluster@0.56.4", "", { "dependencies": { "kubernetes-types": "^1.30.0" }, "peerDependencies": { "@effect/platform": "^0.94.5", "@effect/rpc": "^0.73.1", "@effect/sql": "^0.49.0", "@effect/workflow": "^0.16.0", "effect": "^3.19.17" } }, "sha512-7Je5/JlbZOlsSxsbKjr97dJed2cNGWsb+TLNgMcr5mRDbcWlFOTUGvsrisEJV6waosYLIg+2omPdvnvRoYKdhA=="],319 "@effect/cluster": ["@effect/cluster@0.56.4", "", { "dependencies": { "kubernetes-types": "^1.30.0" }, "peerDependencies": { "@effect/platform": "^0.94.5", "@effect/rpc": "^0.73.1", "@effect/sql": "^0.49.0", "@effect/workflow": "^0.16.0", "effect": "^3.19.17" } }, "sha512-7Je5/JlbZOlsSxsbKjr97dJed2cNGWsb+TLNgMcr5mRDbcWlFOTUGvsrisEJV6waosYLIg+2omPdvnvRoYKdhA=="],
@@ -311,16 +414,34 @@ · 94 unmodified lines
311 414
312 "@executor-v3/mcp-elicitation-demo": ["@executor-v3/mcp-elicitation-demo@workspace:apps/mcp-elicitation-demo"],415 "@executor-v3/mcp-elicitation-demo": ["@executor-v3/mcp-elicitation-demo@workspace:apps/mcp-elicitation-demo"],
313 416
417 "@executor-v3/react": ["@executor-v3/react@workspace:packages/react"],
418
314 "@executor-v3/runtime-local-inproc": ["@executor-v3/runtime-local-inproc@workspace:packages/runtime-local-inproc"],419 "@executor-v3/runtime-local-inproc": ["@executor-v3/runtime-local-inproc@workspace:packages/runtime-local-inproc"],
315 420
421 "@executor-v3/server": ["@executor-v3/server@workspace:packages/server"],
422
423 "@executor-v3/web": ["@executor-v3/web@workspace:apps/web"],
424
316 "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],425 "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
317 426
427 "@hono/vite-dev-server": ["@hono/vite-dev-server@0.25.0", "", { "dependencies": { "@hono/node-server": "^1.14.2", "minimatch": "^9.0.3" }, "peerDependencies": { "hono": "*", "miniflare": "*", "wrangler": "*" }, "optionalPeers": ["miniflare", "wrangler"] }, "sha512-4j5rs7yUTOObU2/yCEHeWbf3C8m95SD9lhdUUE6CJ462Emp9vS94yiza2dsA8+ly5pdaIQW0cd4nhNVcwYL5tA=="],
428
429 "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
430
431 "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
432
433 "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
434
318 "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],435 "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
319 436
437 "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
438
320 "@js-joda/core": ["@js-joda/core@5.7.0", "", {}, "sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg=="],439 "@js-joda/core": ["@js-joda/core@5.7.0", "", {}, "sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg=="],
321 440
322 "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],441 "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
323 442
443 "@medv/finder": ["@medv/finder@4.0.2", "", {}, "sha512-RraNY9SCcx4KZV0Dh6BEW6XEW2swkqYca74pkFFRw6hHItSHiy+O/xMnpbofjYbzXj0tSpBGthUF1hHTsr3vIQ=="],
444
324 "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],445 "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="],
325 446
326 "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],447 "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
@@ -413,6 +534,10 @@ · 86 unmodified lines
413 534
414 "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="],535 "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="],
415 536
537 "@react-grab/cli": ["@react-grab/cli@0.1.26", "", { "dependencies": { "@antfu/ni": "^0.23.0", "commander": "^14.0.0", "ignore": "^7.0.5", "jsonc-parser": "^3.3.1", "ora": "^8.2.0", "picocolors": "^1.1.1", "prompts": "^2.4.2", "smol-toml": "^1.6.0" }, "bin": { "react-grab": "dist/cli.js" } }, "sha512-nNOBdrJ2K7jji6L/X9ARCpU1o/CQ33KYZUtW7vAtrfDM2hyvAIA224ps+Syl97FxhS7pO8b59Bnhc7YLG6h3nA=="],
538
539 "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
540
416 "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],541 "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
417 542
418 "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],543 "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
@@ -463,28 +588,108 @@ · 44 unmodified lines
463 588
464 "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],589 "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
465 590
591 "@shikijs/core": ["@shikijs/core@4.0.1", "", { "dependencies": { "@shikijs/primitive": "4.0.1", "@shikijs/types": "4.0.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg=="],
592
593 "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.1", "", { "dependencies": { "@shikijs/types": "4.0.1", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg=="],
594
595 "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.1", "", { "dependencies": { "@shikijs/types": "4.0.1", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg=="],
596
597 "@shikijs/langs": ["@shikijs/langs@4.0.1", "", { "dependencies": { "@shikijs/types": "4.0.1" } }, "sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w=="],
598
599 "@shikijs/primitive": ["@shikijs/primitive@4.0.1", "", { "dependencies": { "@shikijs/types": "4.0.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA=="],
600
601 "@shikijs/themes": ["@shikijs/themes@4.0.1", "", { "dependencies": { "@shikijs/types": "4.0.1" } }, "sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA=="],
602
603 "@shikijs/types": ["@shikijs/types@4.0.1", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw=="],
604
605 "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
606
466 "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],607 "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
467 608
609 "@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
610
611 "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="],
612
613 "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="],
614
615 "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="],
616
617 "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="],
618
619 "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="],
620
621 "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="],
622
623 "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="],
624
625 "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="],
626
627 "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="],
628
629 "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="],
630
631 "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="],
632
633 "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="],
634
635 "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="],
636
637 "@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
638
639 "@tanstack/history": ["@tanstack/history@1.161.4", "", {}, "sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww=="],
640
641 "@tanstack/react-router": ["@tanstack/react-router@1.166.2", "", { "dependencies": { "@tanstack/history": "1.161.4", "@tanstack/react-store": "^0.9.1", "@tanstack/router-core": "1.166.2", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-pKhUtrvVLlhjWhsHkJSuIzh1J4LcP+8ErbIqRLORX9Js8dUFMKoT0+8oFpi+P8QRpuhm/7rzjYiWfcyTsqQZtA=="],
642
643 "@tanstack/react-store": ["@tanstack/react-store@0.9.2", "", { "dependencies": { "@tanstack/store": "0.9.2", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Vt5usJE5sHG/cMechQfmwvwne6ktGCELe89Lmvoxe3LKRoFrhPa8OCKWs0NliG8HTJElEIj7PLtaBQIcux5pAQ=="],
644
645 "@tanstack/router-core": ["@tanstack/router-core@1.166.2", "", { "dependencies": { "@tanstack/history": "1.161.4", "@tanstack/store": "^0.9.1", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-zn3NhENOAX9ToQiX077UV2OH3aJKOvV2ZMNZZxZ3gDG3i3WqL8NfWfEgetEAfMN37/Mnt90PpotYgf7IyuoKqQ=="],
646
647 "@tanstack/store": ["@tanstack/store@0.9.2", "", {}, "sha512-K013lUJEFJK2ofFQ/hZKJUmCnpcV00ebLyOyFOWQvyQHUOZp/iYO84BM6aOGiV81JzwbX0APTVmW8YI7yiG5oA=="],
648
468 "@tediousjs/connection-string": ["@tediousjs/connection-string@0.5.0", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="],649 "@tediousjs/connection-string": ["@tediousjs/connection-string@0.5.0", "", {}, "sha512-7qSgZbincDDDFyRweCIEvZULFAw5iz/DeunhvuxpL31nfntX3P4Yd4HkHBRg9H8CdqY1e5WFN1PZIz/REL9MVQ=="],
469 650
470 "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],651 "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
471 652
653 "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
654
655 "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
656
657 "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
658
659 "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
660
472 "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],661 "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
473 662
474 "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],663 "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
475 664
476 "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],665 "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
477 666
667 "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
668
669 "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
670
478 "@types/mssql": ["@types/mssql@9.1.9", "", { "dependencies": { "@types/node": "*", "tarn": "^3.0.1", "tedious": "*" } }, "sha512-P0nCgw6vzY23UxZMnbI4N7fnLGANt4LI4yvxze1paPj+LuN28cFv5EI+QidP8udnId/BKhkcRhm/BleNsjK65A=="],671 "@types/mssql": ["@types/mssql@9.1.9", "", { "dependencies": { "@types/node": "*", "tarn": "^3.0.1", "tedious": "*" } }, "sha512-P0nCgw6vzY23UxZMnbI4N7fnLGANt4LI4yvxze1paPj+LuN28cFv5EI+QidP8udnId/BKhkcRhm/BleNsjK65A=="],
479 672
480 "@types/node": ["@types/node@24.11.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw=="],673 "@types/node": ["@types/node@24.11.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw=="],
481 674
675 "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
676
677 "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
678
679 "@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="],
680
482 "@types/readable-stream": ["@types/readable-stream@4.0.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig=="],681 "@types/readable-stream": ["@types/readable-stream@4.0.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig=="],
483 682
683 "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
684
484 "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.3", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA=="],685 "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.3", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA=="],
485 686
687 "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
688
486 "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],689 "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
487 690
691 "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="],
692
488 "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],693 "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
489 694
490 "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],695 "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
@@ -511,18 +716,30 @@ · 20 unmodified lines
511 716
512 "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],717 "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
513 718
719 "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
720
514 "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],721 "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
515 722
516 "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],723 "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
517 724
725 "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
726
518 "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],727 "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
519 728
729 "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
730
731 "bippy": ["bippy@0.5.32", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": ">=17.0.1" } }, "sha512-yt1mC8eReTxjfg41YBZdN4PvsDwHFWxltoiQX0Q+Htlbf41aSniopb7ECZits01HwNAvXEh69RGk/ImlswDTEw=="],
732
520 "bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="],733 "bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="],
521 734
522 "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],735 "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
523 736
737 "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
738
524 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],739 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
525 740
741 "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
742
526 "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],743 "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
527 744
528 "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],745 "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
@@ -539,24 +756,50 @@ · 10 unmodified lines
539 756
540 "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],757 "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
541 758
759 "caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="],
760
761 "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
762
542 "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],763 "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
543 764
765 "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
766
767 "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
768
769 "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
770
544 "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],771 "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
545 772
773 "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
774
775 "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
776
777 "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
778
779 "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
780
781 "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
782
546 "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],783 "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
547 784
548 "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],785 "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
549 786
550 "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],787 "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
551 788
789 "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
790
552 "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],791 "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
553 792
793 "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
794
554 "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],795 "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
555 796
556 "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],797 "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
557 798
558 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],799 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
559 800
801 "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
802
560 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],803 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
561 804
562 "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],805 "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
@@ -569,8 +812,12 @@ · 6 unmodified lines
569 812
570 "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],813 "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
571 814
815 "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
816
572 "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],817 "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
573 818
819 "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
820
574 "drizzle-kit": ["drizzle-kit@1.0.0-beta.15-859cf75", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-Y36s1XQGVb1PgU3aRNgufp1K3D2VkIifu8kv4Ubsmxi+Dq+N7KMklnpp7Knu/XC4FZi2MHPPG3v3o097r0/TcQ=="],821 "drizzle-kit": ["drizzle-kit@1.0.0-beta.15-859cf75", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-Y36s1XQGVb1PgU3aRNgufp1K3D2VkIifu8kv4Ubsmxi+Dq+N7KMklnpp7Knu/XC4FZi2MHPPG3v3o097r0/TcQ=="],
575 822
576 "drizzle-orm": ["drizzle-orm@1.0.0-beta.15-859cf75", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-dGVb2Q70H2AV6513hkOXR3Ud0FeGXLdugVq3YehoqkGIVTJrkuo0gRnCcW/dfI00O07t3T4HSh4clF/D/o/IsQ=="],823 "drizzle-orm": ["drizzle-orm@1.0.0-beta.15-859cf75", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-dGVb2Q70H2AV6513hkOXR3Ud0FeGXLdugVq3YehoqkGIVTJrkuo0gRnCcW/dfI00O07t3T4HSh4clF/D/o/IsQ=="],
@@ -583,8 +830,14 @@ · 6 unmodified lines
583 830
584 "effect": ["effect@3.19.19", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg=="],831 "effect": ["effect@3.19.19", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-Yc8U/SVXo2dHnaP7zNBlAo83h/nzSJpi7vph6Hzyl4ulgMBIgPmz3UzOjb9sBgpFE00gC0iETR244sfXDNLHRg=="],
585 832
833 "electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="],
834
835 "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
836
586 "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],837 "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
587 838
839 "enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
840
588 "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],841 "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
589 842
590 "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],843 "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
@@ -595,6 +848,8 @@ · 4 unmodified lines
595 848
596 "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],849 "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
597 850
851 "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
852
598 "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],853 "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
599 854
600 "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],855 "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
@@ -647,6 +902,10 @@ · 46 unmodified lines
647 902
648 "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],903 "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
649 904
905 "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
906
907 "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
908
650 "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],909 "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
651 910
652 "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],911 "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
@@ -655,12 +914,20 @@ · 2 unmodified lines
655 914
656 "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],915 "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
657 916
917 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
918
658 "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],919 "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
659 920
660 "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],921 "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
661 922
923 "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
924
925 "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
926
662 "hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="],927 "hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="],
663 928
929 "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
930
664 "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],931 "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
665 932
666 "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],933 "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
@@ -671,6 +938,8 @@ · 4 unmodified lines
671 938
672 "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],939 "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
673 940
941 "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
942
674 "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],943 "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
675 944
676 "ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="],945 "ini": ["ini@4.1.3", "", {}, "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg=="],
@@ -687,12 +956,18 @@ · 10 unmodified lines
687 956
688 "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],957 "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
689 958
959 "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
960
690 "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],961 "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
691 962
692 "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],963 "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
693 964
965 "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
966
694 "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],967 "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
695 968
969 "isbot": ["isbot@5.1.35", "", {}, "sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg=="],
970
696 "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],971 "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
697 972
698 "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],973 "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -707,22 +982,54 @@ · 8 unmodified lines
707 982
708 "jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],983 "jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],
709 984
985 "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
986
710 "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],987 "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
711 988
712 "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],989 "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
713 990
714 "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],991 "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
715 992
993 "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
994
995 "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
996
716 "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],997 "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
717 998
718 "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],999 "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
719 1000
720 "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],1001 "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
721 1002
1003 "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
1004
722 "knip": ["knip@5.85.0", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.1", "minimist": "^1.2.8", "oxc-resolver": "^11.15.0", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg=="],1005 "knip": ["knip@5.85.0", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.1", "minimist": "^1.2.8", "oxc-resolver": "^11.15.0", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg=="],
723 1006
724 "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],1007 "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
725 1008
1009 "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
1010
1011 "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
1012
1013 "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
1014
1015 "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
1016
1017 "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
1018
1019 "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
1020
1021 "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
1022
1023 "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
1024
1025 "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
1026
1027 "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
1028
1029 "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
1030
1031 "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
1032
726 "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],1033 "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
727 1034
728 "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],1035 "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
@@ -737,18 +1044,34 @@ · 8 unmodified lines
737 1044
738 "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],1045 "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
739 1046
1047 "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
1048
740 "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],1049 "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
741 1050
1051 "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
1052
742 "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],1053 "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
743 1054
744 "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],1055 "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
745 1056
1057 "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
1058
746 "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],1059 "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
747 1060
748 "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],1061 "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
749 1062
750 "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],1063 "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
751 1064
1065 "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
1066
1067 "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
1068
1069 "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
1070
1071 "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
1072
1073 "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
1074
752 "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],1075 "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
753 1076
754 "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],1077 "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
@@ -757,6 +1080,10 @@ · 2 unmodified lines
757 1080
758 "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],1081 "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
759 1082
1083 "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
1084
1085 "minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
1086
760 "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],1087 "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
761 1088
762 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],1089 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -779,6 +1106,8 @@ · 16 unmodified lines
779 1106
780 "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],1107 "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
781 1108
1109 "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
1110
782 "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],1111 "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
783 1112
784 "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],1113 "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -787,8 +1116,16 @@ · 2 unmodified lines
787 1116
788 "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],1117 "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
789 1118
1119 "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
1120
1121 "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="],
1122
1123 "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="],
1124
790 "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],1125 "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
791 1126
1127 "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
1128
792 "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="],1129 "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="],
793 1130
794 "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],1131 "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
@@ -813,6 +1150,10 @@ · 18 unmodified lines
813 1150
814 "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],1151 "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
815 1152
1153 "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
1154
1155 "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
1156
816 "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],1157 "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
817 1158
818 "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],1159 "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
@@ -825,10 +1166,26 @@ · 6 unmodified lines
825 1166
826 "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],1167 "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
827 1168
1169 "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
1170
1171 "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
1172
1173 "react-grab": ["react-grab@0.1.26", "", { "dependencies": { "@medv/finder": "^4.0.2", "@react-grab/cli": "0.1.26", "bippy": "^0.5.32", "solid-js": "^1.9.10" }, "peerDependencies": { "react": ">=17.0.0" }, "optionalPeers": ["react"], "bin": { "react-grab": "bin/cli.js" } }, "sha512-ubpS3jvxuO47J3/GGYYXVQ0L/EfIojpLg8a+2Ii+8doGJmnZw0nw2Ez1cGpNaZeSHbmY4+8qADWoIFOMWD008A=="],
1174
1175 "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
1176
828 "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],1177 "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
829 1178
1179 "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
1180
1181 "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
1182
1183 "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
1184
830 "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],1185 "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
831 1186
1187 "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
1188
832 "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],1189 "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
833 1190
834 "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],1191 "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
@@ -845,10 +1202,16 @@ · 10 unmodified lines
845 1202
846 "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],1203 "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
847 1204
848 "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],1205 "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
1206
1207 "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
849 1208
850 "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],1209 "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
851 1210
1211 "seroval": ["seroval@1.5.0", "", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="],
1212
1213 "seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="],
1214
852 "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],1215 "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
853 1216
854 "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],1217 "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
@@ -857,6 +1220,8 @@ · 2 unmodified lines
857 1220
858 "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],1221 "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
859 1222
1223 "shiki": ["shiki@4.0.1", "", { "dependencies": { "@shikijs/core": "4.0.1", "@shikijs/engine-javascript": "4.0.1", "@shikijs/engine-oniguruma": "4.0.1", "@shikijs/langs": "4.0.1", "@shikijs/themes": "4.0.1", "@shikijs/types": "4.0.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ=="],
1224
860 "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],1225 "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
861 1226
862 "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],1227 "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
@@ -867,10 +1232,18 @@ · 4 unmodified lines
867 1232
868 "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],1233 "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
869 1234
1235 "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
1236
1237 "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
1238
870 "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],1239 "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],
871 1240
1241 "solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="],
1242
872 "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],1243 "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
873 1244
1245 "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
1246
874 "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],1247 "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
875 1248
876 "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],1249 "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
@@ -879,16 +1252,34 @@ · 2 unmodified lines
879 1252
880 "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],1253 "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
881 1254
1255 "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
1256
1257 "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
1258
882 "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],1259 "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
883 1260
1261 "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
1262
1263 "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
1264
884 "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="],1265 "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="],
885 1266
886 "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],1267 "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
887 1268
1269 "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
1270
1271 "tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
1272
1273 "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
1274
888 "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="],1275 "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="],
889 1276
890 "tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="],1277 "tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="],
891 1278
1279 "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
1280
1281 "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="],
1282
892 "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],1283 "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
893 1284
894 "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],1285 "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
@@ -907,8 +1298,26 @@ · 12 unmodified lines
907 1298
908 "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="],1299 "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="],
909 1300
1301 "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
1302
910 "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],1303 "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
911 1304
1305 "turbo": ["turbo@2.8.14", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.14", "turbo-darwin-arm64": "2.8.14", "turbo-linux-64": "2.8.14", "turbo-linux-arm64": "2.8.14", "turbo-windows-64": "2.8.14", "turbo-windows-arm64": "2.8.14" }, "bin": { "turbo": "bin/turbo" } }, "sha512-UCTxeMNYT1cKaHiIFdLCQ7ulI+jw5i5uOnJOrRXsgUD7G3+OjlUjwVd7JfeVt2McWSVGjYA3EVW/v1FSsJ5DtA=="],
1306
1307 "turbo-darwin-64": ["turbo-darwin-64@2.8.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-9sFi7n2lLfEsGWi5OEoA/eTtQU2BPKtzSYKqufMtDeRmqMT9vKjbv9gJCRkllSVE9BOXA0qXC3diyX8V8rKIKw=="],
1308
1309 "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aS4yJuy6A1PCLws+PJpZP0qCURG8Y5iVx13z/WAbKyeDTY6W6PiGgcEllSaeLGxyn++382ztN/EZH85n2zZ6VQ=="],
1310
1311 "turbo-linux-64": ["turbo-linux-64@2.8.14", "", { "os": "linux", "cpu": "x64" }, "sha512-XC6wPUDJkakjhNLaS0NrHDMiujRVjH+naEAwvKLArgqRaFkNxjmyNDRM4eu3soMMFmjym6NTxYaF74rvET+Orw=="],
1312
1313 "turbo-linux-arm64": ["turbo-linux-arm64@2.8.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ChfE7isyVNjZrVSPDwcfqcHLG/FuIBbOFxnt1FM8vSuBGzHAs8AlTdwFNIxlEMJfZ8Ad9mdMxdmsCUPIWiQ6cg=="],
1314
1315 "turbo-windows-64": ["turbo-windows-64@2.8.14", "", { "os": "win32", "cpu": "x64" }, "sha512-FTbIeQL1ycLFW2t9uQNMy+bRSzi3Xhwun/e7ZhFBdM+U0VZxxrtfYEBM9CHOejlfqomk6Jh7aRz0sJoqYn39Hg=="],
1316
1317 "turbo-windows-arm64": ["turbo-windows-arm64@2.8.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-KgZX12cTyhY030qS7ieT8zRkhZZE2VWJasDFVUSVVn17nR7IShpv68/7j5UqJNeRLIGF1XPK0phsP5V5yw3how=="],
1318
1319 "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
1320
912 "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],1321 "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
913 1322
914 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],1323 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
@@ -917,12 +1326,30 @@ · 2 unmodified lines
917 1326
918 "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],1327 "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
919 1328
1329 "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
1330
1331 "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
1332
1333 "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
1334
1335 "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
1336
1337 "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
1338
920 "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],1339 "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
921 1340
1341 "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
1342
1343 "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
1344
922 "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],1345 "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
923 1346
924 "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],1347 "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
925 1348
1349 "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
1350
1351 "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
1352
926 "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],1353 "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
927 1354
928 "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],1355 "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
@@ -941,14 +1368,38 @@ · 12 unmodified lines
941 1368
942 "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],1369 "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
943 1370
1371 "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
1372
944 "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],1373 "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
945 1374
946 "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],1375 "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
947 1376
948 "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],1377 "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
949 1378
1379 "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
1380
950 "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],1381 "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
951 1382
1383 "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
1384
1385 "@react-grab/cli/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
1386
1387 "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
1388
1389 "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
1390
1391 "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
1392
1393 "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
1394
1395 "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
1396
1397 "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
1398
1399 "jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1400
1401 "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
1402
952 "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],1403 "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
953 1404
954 "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],1405 "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
package.json+2−0
@@ -19,12 +19,14 @@ · 18 unmodified lines
19 "zod": "^4.3.6"19 "zod": "^4.3.6"
20 },20 },
21 "scripts": {21 "scripts": {
22 "dev": "turbo run dev --filter=@executor-v3/web",
22 "executor": "./apps/executor/bin/executor",23 "executor": "./apps/executor/bin/executor",
23 "typecheck": "bun run --filter='*' typecheck",24 "typecheck": "bun run --filter='*' typecheck",
24 "knip": "knip"25 "knip": "knip"
25 },26 },
26 "devDependencies": {27 "devDependencies": {
27 "knip": "^5.85.0",28 "knip": "^5.85.0",
29 "turbo": "^2.5.6",
28 "typescript": "^5.9.3"30 "typescript": "^5.9.3"
29 }31 }
30}32}
packages/ai-sdk-adapter/src/example-dynamic.ts+4−4
@@ -21,8 +21,8 @@ const catalog: ToolCatalog = { · 20 unmodified lines
21 path: asToolPath("source.src_api.github.issues.list"),21 path: asToolPath("source.src_api.github.issues.list"),
22 sourceKey: "source.src_api",22 sourceKey: "source.src_api",
23 description: "Hydrated metadata for selected path",23 description: "Hydrated metadata for selected path",
24 inputHint: "object",24 inputType: "object",
25 outputHint: "object",25 outputType: "object",
26 },26 },
27 ].filter((tool) => !namespace || tool.path.startsWith(`source.${namespace}.`)));27 ].filter((tool) => !namespace || tool.path.startsWith(`source.${namespace}.`)));
28 },28 },
@@ -31,8 +31,8 @@ const catalog: ToolCatalog = { · 2 unmodified lines
31 path,31 path,
32 sourceKey: "source.src_api",32 sourceKey: "source.src_api",
33 description: "Hydrated metadata for selected path",33 description: "Hydrated metadata for selected path",
34 inputHint: "object",34 inputType: "object",
35 outputHint: "object",35 outputType: "object",
36 });36 });
37 },37 },
38 searchTools({ limit }) {38 searchTools({ limit }) {
packages/codemode-core/src/discovery.ts+4−6
@@ -24,8 +24,8 @@ const searchableTextForTool = (tool: ToolDescriptor): string => · 23 unmodified lines
24 tool.path,24 tool.path,
25 tool.sourceKey,25 tool.sourceKey,
26 tool.description ?? "",26 tool.description ?? "",
27 tool.inputHint ?? "",27 tool.inputType ?? "",
28 tool.outputHint ?? "",28 tool.outputType ?? "",
29 ]29 ]
30 .join(" ")30 .join(" ")
31 .toLowerCase();31 .toLowerCase();
@@ -44,7 +44,6 @@ const projectDescriptor = (input: { · 12 unmodified lines
44 ...descriptor,44 ...descriptor,
45 inputSchemaJson: undefined,45 inputSchemaJson: undefined,
46 outputSchemaJson: undefined,46 outputSchemaJson: undefined,
47 refHintKeys: undefined,
48 };47 };
49};48};
50 49
@@ -229,13 +228,12 @@ export function createDiscoveryPrimitivesFromToolCatalog(input: { · 178 unmodified lines
229 score: hit.score,228 score: hit.score,
230 description: descriptor.description,229 description: descriptor.description,
231 interaction: descriptor.interaction ?? "auto",230 interaction: descriptor.interaction ?? "auto",
232 inputHint: descriptor.inputHint,231 inputType: descriptor.inputType,
233 outputHint: descriptor.outputHint,232 outputType: descriptor.outputType,
234 ...(includeSchemas233 ...(includeSchemas
235 ? {234 ? {
236 inputSchemaJson: descriptor.inputSchemaJson,235 inputSchemaJson: descriptor.inputSchemaJson,
237 outputSchemaJson: descriptor.outputSchemaJson,236 outputSchemaJson: descriptor.outputSchemaJson,
238 refHintKeys: descriptor.refHintKeys,
239 }237 }
240 : {}),238 : {}),
241 };239 };
packages/codemode-core/src/index.test.ts+4−5
@@ -287,16 +287,16 @@ describe("codemode-core", () => { · 286 unmodified lines
287 path: asToolPath("source.docs.search"),287 path: asToolPath("source.docs.search"),
288 sourceKey: "source.docs",288 sourceKey: "source.docs",
289 description: "Search docs",289 description: "Search docs",
290 inputHint: "object",290 inputType: "object",
291 outputHint: "object",291 outputType: "object",
292 },292 },
293 "source.issues.create": {293 "source.issues.create": {
294 path: asToolPath("source.issues.create"),294 path: asToolPath("source.issues.create"),
295 sourceKey: "source.issues",295 sourceKey: "source.issues",
296 description: "Create issue",296 description: "Create issue",
297 interaction: "required",297 interaction: "required",
298 inputHint: "object",298 inputType: "object",
299 outputHint: "object",299 outputType: "object",
300 },300 },
301 };301 };
302 302
@@ -327,7 +327,6 @@ describe("codemode-core", () => { · 24 unmodified lines
327 ...descriptor,327 ...descriptor,
328 inputSchemaJson: includeSchemas ? descriptor.inputSchemaJson : undefined,328 inputSchemaJson: includeSchemas ? descriptor.inputSchemaJson : undefined,
329 outputSchemaJson: includeSchemas ? descriptor.outputSchemaJson : undefined,329 outputSchemaJson: includeSchemas ? descriptor.outputSchemaJson : undefined,
330 refHintKeys: includeSchemas ? descriptor.refHintKeys : undefined,
331 })),330 })),
332 ),331 ),
333 getToolByPath: ({ path }) =>332 getToolByPath: ({ path }) =>
packages/codemode-core/src/index.ts+1−0
@@ -1,5 +1,6 @@
1export * from "./types";1export * from "./types";
2export * from "./json-schema";2export * from "./json-schema";
3export * from "./schema-types";
3export * from "./tool-map";4export * from "./tool-map";
4export * from "./discovery";5export * from "./discovery";
5export * from "./system-tools";6export * from "./system-tools";
packages/codemode-core/src/schema-types.ts+125−0
@@ -0,0 +1,125 @@
1type JsonSchemaRecord = Record<string, unknown>;
2
3const asRecord = (value: unknown): JsonSchemaRecord =>
4 typeof value === "object" && value !== null && !Array.isArray(value)
5 ? (value as JsonSchemaRecord)
6 : {};
7
8const asStringArray = (value: unknown): Array<string> =>
9 Array.isArray(value)
10 ? value.filter((item): item is string => typeof item === "string")
11 : [];
12
13const truncate = (value: string, maxLength: number): string =>
14 value.length <= maxLength
15 ? value
16 : `${value.slice(0, Math.max(0, maxLength - 4))} ...`;
17
18const propertyLabel = (
19 name: string,
20 schema: JsonSchemaRecord,
21 optional: boolean,
22 maxLength: number,
23): string =>
24 `${name}${optional ? "?" : ""}: ${schemaToTypeSignature(schema, maxLength)}`;
25
26const compositeLabel = (
27 key: "oneOf" | "anyOf" | "allOf",
28 schema: JsonSchemaRecord,
29 maxLength: number,
30): string | null => {
31 const items = Array.isArray(schema[key]) ? schema[key].map(asRecord) : [];
32 if (items.length === 0) {
33 return null;
34 }
35
36 const labels = items
37 .map((item) => schemaToTypeSignature(item, maxLength))
38 .filter((label) => label.length > 0);
39
40 if (labels.length === 0) {
41 return null;
42 }
43
44 return labels.join(key === "allOf" ? " & " : " | ");
45};
46
47export const schemaToTypeSignature = (
48 input: unknown,
49 maxLength: number = 220,
50): string => {
51 const schema = asRecord(input);
52
53 if (typeof schema.$ref === "string") {
54 const ref = schema.$ref.trim();
55 return ref.length > 0 ? ref.split("/").at(-1) ?? ref : "unknown";
56 }
57
58 if ("const" in schema) {
59 return JSON.stringify(schema.const);
60 }
61
62 const enumValues = Array.isArray(schema.enum) ? schema.enum : [];
63 if (enumValues.length > 0) {
64 return truncate(
65 enumValues.map((value) => JSON.stringify(value)).join(" | "),
66 maxLength,
67 );
68 }
69
70 const composite =
71 compositeLabel("oneOf", schema, maxLength)
72 ?? compositeLabel("anyOf", schema, maxLength)
73 ?? compositeLabel("allOf", schema, maxLength);
74 if (composite) {
75 return truncate(composite, maxLength);
76 }
77
78 if (schema.type === "array") {
79 const itemLabel = schema.items
80 ? schemaToTypeSignature(schema.items, maxLength)
81 : "unknown";
82 return truncate(`${itemLabel}[]`, maxLength);
83 }
84
85 if (schema.type === "object" || schema.properties) {
86 const properties = asRecord(schema.properties);
87 const keys = Object.keys(properties);
88 if (keys.length === 0) {
89 return schema.additionalProperties ? "Record<string, unknown>" : "object";
90 }
91
92 const required = new Set(asStringArray(schema.required));
93 const parts = keys.map((key) =>
94 propertyLabel(key, asRecord(properties[key]), !required.has(key), maxLength)
95 );
96
97 return truncate(`{ ${parts.join(", ")} }`, maxLength);
98 }
99
100 if (Array.isArray(schema.type)) {
101 return truncate(schema.type.join(" | "), maxLength);
102 }
103
104 if (typeof schema.type === "string") {
105 return schema.type;
106 }
107
108 return "unknown";
109};
110
111export const typeSignatureFromSchemaJson = (
112 schemaJson: string | undefined,
113 fallback: string,
114 maxLength: number = 220,
115): string => {
116 if (!schemaJson) {
117 return fallback;
118 }
119
120 try {
121 return schemaToTypeSignature(JSON.parse(schemaJson) as unknown, maxLength);
122 } catch {
123 return fallback;
124 }
125};
packages/codemode-core/src/system-tools.ts+8−6
@@ -47,11 +47,14 @@ const catalogToolsOutputSchema = Schema.standardSchemaV1( · 46 unmodified lines
47 sourceKey: Schema.String,47 sourceKey: Schema.String,
48 description: Schema.optional(Schema.String),48 description: Schema.optional(Schema.String),
49 interaction: Schema.optional(Schema.String),49 interaction: Schema.optional(Schema.String),
50 inputHint: Schema.optional(Schema.String),50 inputType: Schema.optional(Schema.String),
51 outputHint: Schema.optional(Schema.String),51 outputType: Schema.optional(Schema.String),
52 inputSchemaJson: Schema.optional(Schema.String),52 inputSchemaJson: Schema.optional(Schema.String),
53 outputSchemaJson: Schema.optional(Schema.String),53 outputSchemaJson: Schema.optional(Schema.String),
54 refHintKeys: Schema.optional(Schema.Array(Schema.String)),54 exampleInputJson: Schema.optional(Schema.String),
55 exampleOutputJson: Schema.optional(Schema.String),
56 providerKind: Schema.optional(Schema.String),
57 providerDataJson: Schema.optional(Schema.String),
55 }),58 }),
56 ),59 ),
57 }),60 }),
@@ -81,11 +84,10 @@ const discoverResultItemSchema = Schema.Struct({ · 23 unmodified lines
81 score: Schema.Number,84 score: Schema.Number,
82 description: Schema.optional(Schema.String),85 description: Schema.optional(Schema.String),
83 interaction: Schema.optional(Schema.String),86 interaction: Schema.optional(Schema.String),
84 inputHint: Schema.optional(Schema.String),87 inputType: Schema.optional(Schema.String),
85 outputHint: Schema.optional(Schema.String),88 outputType: Schema.optional(Schema.String),
86 inputSchemaJson: Schema.optional(Schema.String),89 inputSchemaJson: Schema.optional(Schema.String),
87 outputSchemaJson: Schema.optional(Schema.String),90 outputSchemaJson: Schema.optional(Schema.String),
88 refHintKeys: Schema.optional(Schema.Array(Schema.String)),
89});91});
90 92
91const discoverOutputSchema = Schema.standardSchemaV1(93const discoverOutputSchema = Schema.standardSchemaV1(
packages/codemode-core/src/tool-map.ts+18−47
@@ -2,6 +2,7 @@ import type { StandardSchemaV1 } from "@standard-schema/spec"; · 1 unmodified line
2import * as Data from "effect/Data";2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4import * as JSONSchema from "effect/JSONSchema";4import * as JSONSchema from "effect/JSONSchema";
5import { typeSignatureFromSchemaJson } from "./schema-types";
5 6
6import type {7import type {
7 ElicitationRequest,8 ElicitationRequest,
@@ -355,50 +356,11 @@ const stringifySchema = (value: unknown): string | undefined => { · 347 unmodified lines
355 }356 }
356};357};
357 358
358const inferHintFromSchemaJson = (359const inferTypeFromSchemaJson = (
359 schemaJson: string | undefined,360 schemaJson: string | undefined,
360 fallback: string,361 fallback: string,
361): string => {362 maxLength: number = 240,
362 if (!schemaJson) {363): string => typeSignatureFromSchemaJson(schemaJson, fallback, maxLength);
363 return fallback;
364 }
365
366 try {
367 const parsed = JSON.parse(schemaJson) as Record<string, unknown>;
368 const title = typeof parsed.title === "string" ? parsed.title.trim() : "";
369 if (title.length > 0) {
370 return title;
371 }
372
373 if (parsed.type === "object") {
374 const properties =
375 parsed.properties
376 && typeof parsed.properties === "object"
377 && !Array.isArray(parsed.properties)
378 ? Object.keys(parsed.properties as Record<string, unknown>)
379 : [];
380 if (properties.length > 0) {
381 const shown = properties.slice(0, 3).join(", ");
382 return properties.length <= 3
383 ? `object { ${shown} }`
384 : `object { ${shown}, ... }`;
385 }
386 return "object";
387 }
388
389 if (parsed.type === "array") {
390 return "array";
391 }
392
393 if (typeof parsed.type === "string") {
394 return parsed.type;
395 }
396 } catch {
397 // Ignore malformed schema and fall back.
398 }
399
400 return fallback;
401};
402 364
403export function createToolsFromRecord(input: {365export function createToolsFromRecord(input: {
404 tools: Record<string, ExecutableTool>;366 tools: Record<string, ExecutableTool>;
@@ -470,13 +432,22 @@ export function toolDescriptorsFromTools(input: { · 65 unmodified lines
470 description: definition.description,432 description: definition.description,
471 interaction: metadata?.interaction,433 interaction: metadata?.interaction,
472 elicitation: metadata?.elicitation,434 elicitation: metadata?.elicitation,
473 inputHint:435 inputType:
474 metadata?.inputHint ?? inferHintFromSchemaJson(inputSchemaJson, "input"),436 metadata?.inputType ?? inferTypeFromSchemaJson(inputSchemaJson, "unknown"),
475 outputHint:437 outputType:
476 metadata?.outputHint ?? inferHintFromSchemaJson(outputSchemaJson, "output"),438 metadata?.outputType ?? inferTypeFromSchemaJson(outputSchemaJson, "unknown"),
477 inputSchemaJson,439 inputSchemaJson,
478 outputSchemaJson,440 outputSchemaJson,
479 refHintKeys: metadata?.refHintKeys,441 ...(metadata?.exampleInputJson
442 ? { exampleInputJson: metadata.exampleInputJson }
443 : {}),
444 ...(metadata?.exampleOutputJson
445 ? { exampleOutputJson: metadata.exampleOutputJson }
446 : {}),
447 ...(metadata?.providerKind ? { providerKind: metadata.providerKind } : {}),
448 ...(metadata?.providerDataJson
449 ? { providerDataJson: metadata.providerDataJson }
450 : {}),
480 } satisfies ToolDescriptor;451 } satisfies ToolDescriptor;
481 });452 });
482}453}
packages/codemode-core/src/types.ts+12−6
@@ -77,12 +77,15 @@ export type OnElicitation = ( · 76 unmodified lines
77export type ToolMetadata = {77export type ToolMetadata = {
78 interaction?: "auto" | "required";78 interaction?: "auto" | "required";
79 elicitation?: ElicitationRequest;79 elicitation?: ElicitationRequest;
80 inputHint?: string;80 inputType?: string;
81 outputHint?: string;81 outputType?: string;
82 inputSchemaJson?: string;82 inputSchemaJson?: string;
83 outputSchemaJson?: string;83 outputSchemaJson?: string;
84 refHintKeys?: readonly string[];84 exampleInputJson?: string;
85 exampleOutputJson?: string;
85 sourceKey?: string;86 sourceKey?: string;
87 providerKind?: string;
88 providerDataJson?: string;
86};89};
87 90
88export type ToolExecutionContext = {91export type ToolExecutionContext = {
@@ -127,11 +130,14 @@ export type ToolDescriptor = { · 38 unmodified lines
127 description?: string;130 description?: string;
128 interaction?: "auto" | "required";131 interaction?: "auto" | "required";
129 elicitation?: ElicitationRequest;132 elicitation?: ElicitationRequest;
130 inputHint?: string;133 inputType?: string;
131 outputHint?: string;134 outputType?: string;
132 inputSchemaJson?: string;135 inputSchemaJson?: string;
133 outputSchemaJson?: string;136 outputSchemaJson?: string;
134 refHintKeys?: readonly string[];137 exampleInputJson?: string;
138 exampleOutputJson?: string;
139 providerKind?: string;
140 providerDataJson?: string;
135};141};
136 142
137export type ToolNamespace = {143export type ToolNamespace = {
packages/codemode-openapi/openapi-extractor-rs/.gitignore+1−0
@@ -0,0 +1 @@
1target/
packages/codemode-openapi/openapi-extractor-rs/Cargo.lock+445−0
@@ -0,0 +1,445 @@
1# This file is automatically @generated by Cargo.
2# It is not intended for manual editing.
3version = 4
4
5[[package]]
6name = "anstream"
7version = "0.6.21"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
10dependencies = [
11 "anstyle",
12 "anstyle-parse",
13 "anstyle-query",
14 "anstyle-wincon",
15 "colorchoice",
16 "is_terminal_polyfill",
17 "utf8parse",
18]
19
20[[package]]
21name = "anstyle"
22version = "1.0.13"
23source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
25
26[[package]]
27name = "anstyle-parse"
28version = "0.2.7"
29source = "registry+https://github.com/rust-lang/crates.io-index"
30checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
31dependencies = [
32 "utf8parse",
33]
34
35[[package]]
36name = "anstyle-query"
37version = "1.1.5"
38source = "registry+https://github.com/rust-lang/crates.io-index"
39checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
40dependencies = [
41 "windows-sys",
42]
43
44[[package]]
45name = "anstyle-wincon"
46version = "3.0.11"
47source = "registry+https://github.com/rust-lang/crates.io-index"
48checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
49dependencies = [
50 "anstyle",
51 "once_cell_polyfill",
52 "windows-sys",
53]
54
55[[package]]
56name = "block-buffer"
57version = "0.10.4"
58source = "registry+https://github.com/rust-lang/crates.io-index"
59checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
60dependencies = [
61 "generic-array",
62]
63
64[[package]]
65name = "bumpalo"
66version = "3.20.2"
67source = "registry+https://github.com/rust-lang/crates.io-index"
68checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
69
70[[package]]
71name = "cfg-if"
72version = "1.0.4"
73source = "registry+https://github.com/rust-lang/crates.io-index"
74checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
75
76[[package]]
77name = "clap"
78version = "4.5.60"
79source = "registry+https://github.com/rust-lang/crates.io-index"
80checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
81dependencies = [
82 "clap_builder",
83 "clap_derive",
84]
85
86[[package]]
87name = "clap_builder"
88version = "4.5.60"
89source = "registry+https://github.com/rust-lang/crates.io-index"
90checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
91dependencies = [
92 "anstream",
93 "anstyle",
94 "clap_lex",
95 "strsim",
96]
97
98[[package]]
99name = "clap_derive"
100version = "4.5.55"
101source = "registry+https://github.com/rust-lang/crates.io-index"
102checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
103dependencies = [
104 "heck",
105 "proc-macro2",
106 "quote",
107 "syn",
108]
109
110[[package]]
111name = "clap_lex"
112version = "1.0.0"
113source = "registry+https://github.com/rust-lang/crates.io-index"
114checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
115
116[[package]]
117name = "colorchoice"
118version = "1.0.4"
119source = "registry+https://github.com/rust-lang/crates.io-index"
120checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
121
122[[package]]
123name = "cpufeatures"
124version = "0.2.17"
125source = "registry+https://github.com/rust-lang/crates.io-index"
126checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
127dependencies = [
128 "libc",
129]
130
131[[package]]
132name = "crypto-common"
133version = "0.1.7"
134source = "registry+https://github.com/rust-lang/crates.io-index"
135checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
136dependencies = [
137 "generic-array",
138 "typenum",
139]
140
141[[package]]
142name = "digest"
143version = "0.10.7"
144source = "registry+https://github.com/rust-lang/crates.io-index"
145checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
146dependencies = [
147 "block-buffer",
148 "crypto-common",
149]
150
151[[package]]
152name = "equivalent"
153version = "1.0.2"
154source = "registry+https://github.com/rust-lang/crates.io-index"
155checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
156
157[[package]]
158name = "generic-array"
159version = "0.14.7"
160source = "registry+https://github.com/rust-lang/crates.io-index"
161checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
162dependencies = [
163 "typenum",
164 "version_check",
165]
166
167[[package]]
168name = "hashbrown"
169version = "0.16.1"
170source = "registry+https://github.com/rust-lang/crates.io-index"
171checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
172
173[[package]]
174name = "heck"
175version = "0.5.0"
176source = "registry+https://github.com/rust-lang/crates.io-index"
177checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
178
179[[package]]
180name = "indexmap"
181version = "2.13.0"
182source = "registry+https://github.com/rust-lang/crates.io-index"
183checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
184dependencies = [
185 "equivalent",
186 "hashbrown",
187]
188
189[[package]]
190name = "is_terminal_polyfill"
191version = "1.70.2"
192source = "registry+https://github.com/rust-lang/crates.io-index"
193checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
194
195[[package]]
196name = "itoa"
197version = "1.0.17"
198source = "registry+https://github.com/rust-lang/crates.io-index"
199checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
200
201[[package]]
202name = "libc"
203version = "0.2.182"
204source = "registry+https://github.com/rust-lang/crates.io-index"
205checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
206
207[[package]]
208name = "memchr"
209version = "2.8.0"
210source = "registry+https://github.com/rust-lang/crates.io-index"
211checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
212
213[[package]]
214name = "once_cell"
215version = "1.21.3"
216source = "registry+https://github.com/rust-lang/crates.io-index"
217checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
218
219[[package]]
220name = "once_cell_polyfill"
221version = "1.70.2"
222source = "registry+https://github.com/rust-lang/crates.io-index"
223checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
224
225[[package]]
226name = "openapi-extractor-rs"
227version = "0.1.0"
228dependencies = [
229 "clap",
230 "serde",
231 "serde_json",
232 "serde_yaml",
233 "sha2",
234 "wasm-bindgen",
235]
236
237[[package]]
238name = "proc-macro2"
239version = "1.0.106"
240source = "registry+https://github.com/rust-lang/crates.io-index"
241checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
242dependencies = [
243 "unicode-ident",
244]
245
246[[package]]
247name = "quote"
248version = "1.0.44"
249source = "registry+https://github.com/rust-lang/crates.io-index"
250checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
251dependencies = [
252 "proc-macro2",
253]
254
255[[package]]
256name = "rustversion"
257version = "1.0.22"
258source = "registry+https://github.com/rust-lang/crates.io-index"
259checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
260
261[[package]]
262name = "ryu"
263version = "1.0.23"
264source = "registry+https://github.com/rust-lang/crates.io-index"
265checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
266
267[[package]]
268name = "serde"
269version = "1.0.228"
270source = "registry+https://github.com/rust-lang/crates.io-index"
271checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
272dependencies = [
273 "serde_core",
274 "serde_derive",
275]
276
277[[package]]
278name = "serde_core"
279version = "1.0.228"
280source = "registry+https://github.com/rust-lang/crates.io-index"
281checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
282dependencies = [
283 "serde_derive",
284]
285
286[[package]]
287name = "serde_derive"
288version = "1.0.228"
289source = "registry+https://github.com/rust-lang/crates.io-index"
290checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
291dependencies = [
292 "proc-macro2",
293 "quote",
294 "syn",
295]
296
297[[package]]
298name = "serde_json"
299version = "1.0.149"
300source = "registry+https://github.com/rust-lang/crates.io-index"
301checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
302dependencies = [
303 "itoa",
304 "memchr",
305 "serde",
306 "serde_core",
307 "zmij",
308]
309
310[[package]]
311name = "serde_yaml"
312version = "0.9.34+deprecated"
313source = "registry+https://github.com/rust-lang/crates.io-index"
314checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
315dependencies = [
316 "indexmap",
317 "itoa",
318 "ryu",
319 "serde",
320 "unsafe-libyaml",
321]
322
323[[package]]
324name = "sha2"
325version = "0.10.9"
326source = "registry+https://github.com/rust-lang/crates.io-index"
327checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
328dependencies = [
329 "cfg-if",
330 "cpufeatures",
331 "digest",
332]
333
334[[package]]
335name = "strsim"
336version = "0.11.1"
337source = "registry+https://github.com/rust-lang/crates.io-index"
338checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
339
340[[package]]
341name = "syn"
342version = "2.0.117"
343source = "registry+https://github.com/rust-lang/crates.io-index"
344checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
345dependencies = [
346 "proc-macro2",
347 "quote",
348 "unicode-ident",
349]
350
351[[package]]
352name = "typenum"
353version = "1.19.0"
354source = "registry+https://github.com/rust-lang/crates.io-index"
355checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
356
357[[package]]
358name = "unicode-ident"
359version = "1.0.24"
360source = "registry+https://github.com/rust-lang/crates.io-index"
361checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
362
363[[package]]
364name = "unsafe-libyaml"
365version = "0.2.11"
366source = "registry+https://github.com/rust-lang/crates.io-index"
367checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
368
369[[package]]
370name = "utf8parse"
371version = "0.2.2"
372source = "registry+https://github.com/rust-lang/crates.io-index"
373checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
374
375[[package]]
376name = "version_check"
377version = "0.9.5"
378source = "registry+https://github.com/rust-lang/crates.io-index"
379checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
380
381[[package]]
382name = "wasm-bindgen"
383version = "0.2.114"
384source = "registry+https://github.com/rust-lang/crates.io-index"
385checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
386dependencies = [
387 "cfg-if",
388 "once_cell",
389 "rustversion",
390 "wasm-bindgen-macro",
391 "wasm-bindgen-shared",
392]
393
394[[package]]
395name = "wasm-bindgen-macro"
396version = "0.2.114"
397source = "registry+https://github.com/rust-lang/crates.io-index"
398checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
399dependencies = [
400 "quote",
401 "wasm-bindgen-macro-support",
402]
403
404[[package]]
405name = "wasm-bindgen-macro-support"
406version = "0.2.114"
407source = "registry+https://github.com/rust-lang/crates.io-index"
408checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
409dependencies = [
410 "bumpalo",
411 "proc-macro2",
412 "quote",
413 "syn",
414 "wasm-bindgen-shared",
415]
416
417[[package]]
418name = "wasm-bindgen-shared"
419version = "0.2.114"
420source = "registry+https://github.com/rust-lang/crates.io-index"
421checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
422dependencies = [
423 "unicode-ident",
424]
425
426[[package]]
427name = "windows-link"
428version = "0.2.1"
429source = "registry+https://github.com/rust-lang/crates.io-index"
430checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
431
432[[package]]
433name = "windows-sys"
434version = "0.61.2"
435source = "registry+https://github.com/rust-lang/crates.io-index"
436checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
437dependencies = [
438 "windows-link",
439]
440
441[[package]]
442name = "zmij"
443version = "1.0.21"
444source = "registry+https://github.com/rust-lang/crates.io-index"
445checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
packages/codemode-openapi/openapi-extractor-rs/Cargo.toml+17−0
@@ -0,0 +1,17 @@
1[package]
2name = "openapi-extractor-rs"
3version = "0.1.0"
4edition = "2024"
5
6[lib]
7crate-type = ["cdylib", "rlib"]
8
9[dependencies]
10clap = { version = "4.5.40", features = ["derive"] }
11serde = { version = "1.0.228", features = ["derive"] }
12serde_json = "1.0.145"
13serde_yaml = "0.9.34"
14sha2 = "0.10.9"
15
16[target.'cfg(target_arch = "wasm32")'.dependencies]
17wasm-bindgen = "0.2.114"
packages/codemode-openapi/openapi-extractor-rs/build-wasm.sh+20−0
@@ -0,0 +1,20 @@
1#!/usr/bin/env bash
2set -euo pipefail
3
4ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5CRATE_DIR="$ROOT_DIR/openapi-extractor-rs"
6OUT_DIR="$ROOT_DIR/src/openapi-extractor-wasm"
7
8cd "$CRATE_DIR"
9
10rustup target add wasm32-unknown-unknown >/dev/null
11
12cargo build --release --target wasm32-unknown-unknown
13
14wasm-bindgen \
15 --target web \
16 --out-dir "$OUT_DIR" \
17 --out-name openapi_extractor \
18 "$CRATE_DIR/target/wasm32-unknown-unknown/release/openapi_extractor_rs.wasm"
19
20rm -rf "$CRATE_DIR/target"
packages/codemode-openapi/openapi-extractor-rs/src/lib.rs+1018−0
@@ -0,0 +1,1018 @@
1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
2
3use serde::Serialize;
4use serde_json::{Map, Value, json};
5use sha2::{Digest, Sha256};
6
7#[cfg(target_arch = "wasm32")]
8use wasm_bindgen::prelude::*;
9
10const OPEN_API_HTTP_METHODS: [&str; 8] = [
11 "get",
12 "put",
13 "post",
14 "delete",
15 "patch",
16 "head",
17 "options",
18 "trace",
19];
20
21const OPEN_API_PARAMETER_LOCATIONS: [&str; 4] = ["path", "query", "header", "cookie"];
22
23#[derive(Serialize, Clone)]
24struct OpenApiToolParameter {
25 name: String,
26 location: String,
27 required: bool,
28}
29
30#[derive(Serialize, Clone)]
31#[serde(rename_all = "camelCase")]
32struct OpenApiToolRequestBody {
33 required: bool,
34 content_types: Vec<String>,
35}
36
37#[derive(Serialize, Clone)]
38#[serde(rename_all = "camelCase")]
39struct OpenApiInvocationPayload {
40 method: String,
41 path_template: String,
42 parameters: Vec<OpenApiToolParameter>,
43 request_body: Option<OpenApiToolRequestBody>,
44}
45
46#[derive(Serialize, Clone)]
47#[serde(rename_all = "camelCase")]
48struct DiscoveryTypingPayload {
49 #[serde(skip_serializing_if = "Option::is_none")]
50 input_schema_json: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 output_schema_json: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 ref_hint_keys: Option<Vec<String>>,
55}
56
57#[derive(Serialize, Clone)]
58#[serde(rename_all = "camelCase")]
59struct OpenApiExample {
60 value_json: String,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 media_type: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 label: Option<String>,
65}
66
67#[derive(Serialize, Clone)]
68#[serde(rename_all = "camelCase")]
69struct OpenApiParameterDocumentation {
70 name: String,
71 location: String,
72 required: bool,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 description: Option<String>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 examples: Option<Vec<OpenApiExample>>,
77}
78
79#[derive(Serialize, Clone)]
80#[serde(rename_all = "camelCase")]
81struct OpenApiRequestBodyDocumentation {
82 #[serde(skip_serializing_if = "Option::is_none")]
83 description: Option<String>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 examples: Option<Vec<OpenApiExample>>,
86}
87
88#[derive(Serialize, Clone)]
89#[serde(rename_all = "camelCase")]
90struct OpenApiResponseDocumentation {
91 status_code: String,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 description: Option<String>,
94 content_types: Vec<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 examples: Option<Vec<OpenApiExample>>,
97}
98
99#[derive(Serialize, Clone)]
100#[serde(rename_all = "camelCase")]
101struct OpenApiToolDocumentation {
102 #[serde(skip_serializing_if = "Option::is_none")]
103 summary: Option<String>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 deprecated: Option<bool>,
106 parameters: Vec<OpenApiParameterDocumentation>,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 request_body: Option<OpenApiRequestBodyDocumentation>,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 response: Option<OpenApiResponseDocumentation>,
111}
112
113#[derive(Serialize, Clone)]
114#[serde(rename_all = "camelCase")]
115struct OpenApiExtractedTool {
116 tool_id: String,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 operation_id: Option<String>,
119 tags: Vec<String>,
120 name: String,
121 description: Option<String>,
122 method: String,
123 path: String,
124 invocation: OpenApiInvocationPayload,
125 operation_hash: String,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 typing: Option<DiscoveryTypingPayload>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 documentation: Option<OpenApiToolDocumentation>,
130}
131
132#[derive(Serialize)]
133#[serde(rename_all = "camelCase")]
134struct OpenApiToolManifest {
135 version: u8,
136 source_hash: String,
137 tools: Vec<OpenApiExtractedTool>,
138 #[serde(skip_serializing_if = "Option::is_none")]
139 ref_hint_table: Option<BTreeMap<String, String>>,
140}
141
142pub fn extract_manifest_json(
143 source_name: &str,
144 openapi_document_text: &str,
145 pretty: bool,
146) -> Result<String, String> {
147 let spec = parse_openapi_document(openapi_document_text)?;
148 let manifest = extract_openapi_manifest(source_name, &spec)?;
149
150 if pretty {
151 serde_json::to_string_pretty(&manifest).map_err(|error| format!("serialize manifest: {error}"))
152 } else {
153 serde_json::to_string(&manifest).map_err(|error| format!("serialize manifest: {error}"))
154 }
155}
156
157#[cfg(target_arch = "wasm32")]
158#[wasm_bindgen]
159pub fn extract_manifest_json_wasm(
160 source_name: &str,
161 openapi_document_text: &str,
162) -> Result<String, JsValue> {
163 extract_manifest_json(source_name, openapi_document_text, false)
164 .map_err(|error| JsValue::from_str(&error))
165}
166
167fn parse_openapi_document(input: &str) -> Result<Value, String> {
168 let text = input.trim();
169 if text.is_empty() {
170 return Err("OpenAPI document is empty".to_string());
171 }
172
173 match serde_json::from_str::<Value>(text) {
174 Ok(value) => Ok(value),
175 Err(_) => serde_yaml::from_str::<Value>(text)
176 .map_err(|error| format!("Unable to parse OpenAPI document as JSON or YAML: {error}")),
177 }
178}
179
180fn as_object(value: &Value) -> Option<&Map<String, Value>> {
181 value.as_object()
182}
183
184fn as_trimmed_string(value: Option<&Value>) -> Option<String> {
185 let candidate = value?.as_str()?.trim();
186 if candidate.is_empty() {
187 return None;
188 }
189
190 Some(candidate.to_string())
191}
192
193fn response_status_rank(status_code: &str) -> i32 {
194 if status_code.len() == 3
195 && status_code.starts_with('2')
196 && status_code.chars().all(|character| character.is_ascii_digit())
197 {
198 return 0;
199 }
200
201 if status_code == "default" {
202 return 1;
203 }
204
205 2
206}
207
208fn to_stable_value(value: &Value) -> Value {
209 match value {
210 Value::Array(items) => Value::Array(items.iter().map(to_stable_value).collect()),
211 Value::Object(object) => {
212 let mut keys: Vec<&String> = object.keys().collect();
213 keys.sort();
214
215 let mut stable = Map::new();
216 for key in keys {
217 if let Some(item) = object.get(key) {
218 stable.insert(key.clone(), to_stable_value(item));
219 }
220 }
221
222 Value::Object(stable)
223 }
224 Value::Number(number) => {
225 if let Some(float_value) = number.as_f64()
226 && float_value.is_finite()
227 && float_value.fract() == 0.0
228 {
229 if float_value >= 0.0 && float_value <= u64::MAX as f64 {
230 let unsigned = float_value as u64;
231 if unsigned as f64 == float_value {
232 return Value::Number(serde_json::Number::from(unsigned));
233 }
234 }
235
236 if float_value >= i64::MIN as f64 && float_value <= i64::MAX as f64 {
237 let signed = float_value as i64;
238 if signed as f64 == float_value {
239 return Value::Number(serde_json::Number::from(signed));
240 }
241 }
242 }
243
244 Value::Number(number.clone())
245 }
246 _ => value.clone(),
247 }
248}
249
250fn hash_unknown(value: &Value) -> Result<String, String> {
251 let stable_json =
252 serde_json::to_string(&to_stable_value(value)).map_err(|error| format!("hash json: {error}"))?;
253
254 let mut hasher = Sha256::new();
255 hasher.update(stable_json.as_bytes());
256 Ok(format!("{:x}", hasher.finalize()))
257}
258
259fn encode_stable_json(value: &Value) -> Result<String, String> {
260 serde_json::to_string(&to_stable_value(value)).map_err(|error| format!("encode stable json: {error}"))
261}
262
263fn to_extracted_tool_parameter_from_record(record: &Map<String, Value>) -> Option<OpenApiToolParameter> {
264 let name = record.get("name")?.as_str()?.trim();
265
266 if name.is_empty() {
267 return None;
268 }
269
270 let location = record.get("in")?.as_str()?;
271 if !OPEN_API_PARAMETER_LOCATIONS.contains(&location) {
272 return None;
273 }
274
275 let required = location == "path" || record.get("required").and_then(Value::as_bool) == Some(true);
276
277 Some(OpenApiToolParameter {
278 name: name.to_string(),
279 location: location.to_string(),
280 required,
281 })
282}
283
284fn to_extracted_tool_parameter(root: &Value, value: &Value) -> Option<OpenApiToolParameter> {
285 let record = resolve_local_reference_object(root, value)?;
286 to_extracted_tool_parameter_from_record(&record)
287}
288
289fn merge_parameters(root: &Value, path_item: &Map<String, Value>, operation: &Map<String, Value>) -> Vec<OpenApiToolParameter> {
290 let mut merged: Vec<OpenApiToolParameter> = collect_parameter_record_by_key(root, path_item, operation)
291 .into_values()
292 .filter_map(|record| to_extracted_tool_parameter_from_record(&record))
293 .collect();
294 merged.sort_by(|left, right| {
295 left.location
296 .cmp(&right.location)
297 .then_with(|| left.name.cmp(&right.name))
298 });
299 merged
300}
301
302fn extract_request_body(root: &Value, operation: &Map<String, Value>) -> Option<OpenApiToolRequestBody> {
303 let request_body = resolve_request_body_record(root, operation)?;
304 let content = request_body.get("content").and_then(as_object);
305
306 Some(OpenApiToolRequestBody {
307 required: request_body.get("required").and_then(Value::as_bool) == Some(true),
308 content_types: collect_content_types(content),
309 })
310}
311
312fn build_invocation_metadata(
313 root: &Value,
314 method: &str,
315 path_value: &str,
316 path_item: &Map<String, Value>,
317 operation: &Map<String, Value>,
318) -> OpenApiInvocationPayload {
319 OpenApiInvocationPayload {
320 method: method.to_string(),
321 path_template: path_value.to_string(),
322 parameters: merge_parameters(root, path_item, operation),
323 request_body: extract_request_body(root, operation),
324 }
325}
326
327fn collect_ref_keys(value: &Value, refs: &mut BTreeSet<String>) {
328 match value {
329 Value::Array(items) => {
330 for item in items {
331 collect_ref_keys(item, refs);
332 }
333 }
334 Value::Object(record) => {
335 if let Some(reference) = record.get("$ref").and_then(Value::as_str)
336 && reference.starts_with("#/")
337 {
338 refs.insert(reference.to_string());
339 }
340
341 for nested in record.values() {
342 collect_ref_keys(nested, refs);
343 }
344 }
345 _ => {}
346 }
347}
348
349fn resolve_json_pointer<'a>(root: &'a Value, pointer: &str) -> Option<&'a Value> {
350 if !pointer.starts_with("#/") {
351 return None;
352 }
353
354 let mut current = root;
355 for part in pointer[2..].split('/') {
356 let decoded = part.replace("~1", "/").replace("~0", "~");
357 let next = current.as_object()?.get(&decoded)?;
358 current = next;
359 }
360
361 Some(current)
362}
363
364fn resolve_local_reference_value(
365 root: &Value,
366 value: &Value,
367 active_refs: &HashSet<String>,
368) -> Value {
369 let Some(record) = as_object(value) else {
370 return value.clone();
371 };
372
373 let Some(reference) = record.get("$ref").and_then(Value::as_str) else {
374 return value.clone();
375 };
376
377 if !reference.starts_with("#/") || active_refs.contains(reference) {
378 return value.clone();
379 }
380
381 let Some(resolved) = resolve_json_pointer(root, reference) else {
382 return value.clone();
383 };
384
385 let mut next_active_refs = active_refs.clone();
386 next_active_refs.insert(reference.to_string());
387
388 let mut resolved_value = resolve_local_reference_value(root, resolved, &next_active_refs);
389
390 if let Some(resolved_record) = resolved_value.as_object_mut() {
391 for (key, nested) in record {
392 if key != "$ref" {
393 resolved_record.insert(key.clone(), nested.clone());
394 }
395 }
396 return resolved_value;
397 }
398
399 value.clone()
400}
401
402fn resolve_local_reference_object(root: &Value, value: &Value) -> Option<Map<String, Value>> {
403 as_object(&resolve_local_reference_value(root, value, &HashSet::new())).cloned()
404}
405
406fn collect_parameter_record_by_key(
407 root: &Value,
408 path_item: &Map<String, Value>,
409 operation: &Map<String, Value>,
410) -> HashMap<String, Map<String, Value>> {
411 let mut records = HashMap::new();
412
413 let mut add_parameters = |candidate: Option<&Value>| {
414 let Some(items) = candidate.and_then(Value::as_array) else {
415 return;
416 };
417
418 for item in items {
419 let Some(record) = resolve_local_reference_object(root, item) else {
420 continue;
421 };
422
423 let Some(parameter) = to_extracted_tool_parameter_from_record(&record) else {
424 continue;
425 };
426
427 records.insert(
428 format!("{}:{}", parameter.location, parameter.name),
429 record,
430 );
431 }
432 };
433
434 add_parameters(path_item.get("parameters"));
435 add_parameters(operation.get("parameters"));
436
437 records
438}
439
440fn resolve_request_body_record(root: &Value, operation: &Map<String, Value>) -> Option<Map<String, Value>> {
441 resolve_local_reference_object(root, operation.get("requestBody")?)
442}
443
444fn collect_content_types(content: Option<&Map<String, Value>>) -> Vec<String> {
445 let mut content_types = content
446 .map(|object| object.keys().cloned().collect::<Vec<String>>())
447 .unwrap_or_default();
448 content_types.sort();
449 content_types
450}
451
452fn pick_content_entry(content: Option<&Value>) -> Option<(String, Map<String, Value>)> {
453 let content_record = as_object(content?)?;
454
455 let mut keys: Vec<String> = content_record.keys().cloned().collect();
456 keys.sort();
457
458 let mut preferred = Vec::with_capacity(keys.len() + 1);
459 preferred.push("application/json".to_string());
460 preferred.extend(keys);
461
462 let mut seen = HashSet::new();
463 for media_type in preferred {
464 if !seen.insert(media_type.clone()) {
465 continue;
466 }
467
468 let Some(media_type_value) = content_record.get(&media_type).and_then(as_object) else {
469 continue;
470 };
471
472 return Some((media_type, media_type_value.clone()));
473 }
474
475 None
476}
477
478fn pick_example_values(value: &Value, label: Option<String>, media_type: Option<String>) -> Result<Vec<OpenApiExample>, String> {
479 let mut examples = Vec::new();
480 let Some(record) = as_object(value) else {
481 return Ok(examples);
482 };
483
484 if let Some(example_value) = record.get("example") {
485 examples.push(OpenApiExample {
486 value_json: encode_stable_json(example_value)?,
487 media_type: media_type.clone(),
488 label: label.clone(),
489 });
490 }
491
492 if let Some(example_values) = record.get("examples").and_then(Value::as_object) {
493 let mut keys: Vec<String> = example_values.keys().cloned().collect();
494 keys.sort();
495
496 for key in keys {
497 let Some(example_entry) = example_values.get(&key) else {
498 continue;
499 };
500 let example_record = as_object(example_entry)
501 .and_then(|entry| entry.get("value"))
502 .unwrap_or(example_entry);
503
504 examples.push(OpenApiExample {
505 value_json: encode_stable_json(example_record)?,
506 media_type: media_type.clone(),
507 label: Some(key),
508 });
509 }
510 }
511
512 Ok(examples)
513}
514
515fn extract_examples_from_schema(schema: Option<&Value>) -> Result<Vec<OpenApiExample>, String> {
516 let Some(schema_value) = schema else {
517 return Ok(Vec::new());
518 };
519
520 pick_example_values(schema_value, None, None)
521}
522
523fn extract_examples_from_media_type(
524 media_type: &str,
525 media_type_record: &Map<String, Value>,
526) -> Result<Vec<OpenApiExample>, String> {
527 let direct = pick_example_values(
528 &Value::Object(media_type_record.clone()),
529 None,
530 Some(media_type.to_string()),
531 )?;
532 if !direct.is_empty() {
533 return Ok(direct);
534 }
535
536 extract_examples_from_schema(media_type_record.get("schema")).map(|examples| {
537 examples
538 .into_iter()
539 .map(|mut example| {
540 example.media_type = Some(media_type.to_string());
541 example
542 })
543 .collect()
544 })
545}
546
547fn pick_preferred_response_record(
548 root: &Value,
549 operation: &Map<String, Value>,
550) -> Option<(String, Map<String, Value>)> {
551 let responses = as_object(operation.get("responses")?)?;
552 let mut response_codes: Vec<String> = responses.keys().cloned().collect();
553 response_codes.sort_by(|left, right| {
554 response_status_rank(left)
555 .cmp(&response_status_rank(right))
556 .then_with(|| left.cmp(right))
557 });
558
559 for response_code in response_codes {
560 let Some(response) = responses.get(&response_code) else {
561 continue;
562 };
563 let Some(response_record) = resolve_local_reference_object(root, response) else {
564 continue;
565 };
566
567 return Some((response_code, response_record));
568 }
569
570 None
571}
572
573fn pick_schema_from_content(content: Option<&Value>) -> Option<Value> {
574 if let Some((_media_type, media_type_value)) = pick_content_entry(content) {
575 if let Some(schema) = media_type_value.get("schema") {
576 return Some(schema.clone());
577 }
578 }
579
580 None
581}
582
583fn extract_request_body_schema(root: &Value, operation: &Map<String, Value>) -> Option<Value> {
584 let request_body = resolve_request_body_record(root, operation)?;
585 pick_schema_from_content(request_body.get("content"))
586}
587
588fn extract_response_schema(root: &Value, operation: &Map<String, Value>) -> Option<Value> {
589 let (_status_code, response) = pick_preferred_response_record(root, operation)?;
590 pick_schema_from_content(response.get("content"))
591}
592
593fn collect_parameter_schema_by_key(
594 root: &Value,
595 path_item: &Map<String, Value>,
596 operation: &Map<String, Value>,
597) -> HashMap<String, Value> {
598 let mut schemas = HashMap::new();
599
600 for (key, record) in collect_parameter_record_by_key(root, path_item, operation) {
601 let Some(schema) = record.get("schema") else {
602 continue;
603 };
604
605 schemas.insert(key, schema.clone());
606 }
607
608 schemas
609}
610
611fn build_input_schema(
612 root: &Value,
613 path_item: &Map<String, Value>,
614 operation: &Map<String, Value>,
615 invocation: &OpenApiInvocationPayload,
616) -> Option<Value> {
617 let parameter_schema_by_key = collect_parameter_schema_by_key(root, path_item, operation);
618
619 let mut properties = Map::new();
620 let mut required = BTreeSet::new();
621
622 for parameter in &invocation.parameters {
623 let key = format!("{}:{}", parameter.location, parameter.name);
624 let schema = parameter_schema_by_key
625 .get(&key)
626 .cloned()
627 .unwrap_or_else(|| json!({ "type": "string" }));
628
629 properties.insert(parameter.name.clone(), schema);
630
631 if parameter.required {
632 required.insert(parameter.name.clone());
633 }
634 }
635
636 let request_body_schema = extract_request_body_schema(root, operation);
637 if let Some(schema) = request_body_schema {
638 properties.insert("body".to_string(), schema);
639
640 if invocation
641 .request_body
642 .as_ref()
643 .map(|request_body| request_body.required)
644 == Some(true)
645 {
646 required.insert("body".to_string());
647 }
648 }
649
650 if properties.is_empty() {
651 return None;
652 }
653
654 let required_list: Vec<String> = required.into_iter().collect();
655
656 Some(json!({
657 "type": "object",
658 "properties": properties,
659 "required": required_list,
660 "additionalProperties": false,
661 }))
662}
663
664fn build_tool_typing(
665 root: &Value,
666 path_item: &Map<String, Value>,
667 operation: &Map<String, Value>,
668 invocation: &OpenApiInvocationPayload,
669) -> Result<Option<DiscoveryTypingPayload>, String> {
670 let input_schema = build_input_schema(root, path_item, operation, invocation);
671 let output_schema = extract_response_schema(root, operation);
672
673 if input_schema.is_none() && output_schema.is_none() {
674 return Ok(None);
675 }
676
677 let mut refs = BTreeSet::new();
678 if let Some(schema) = &input_schema {
679 collect_ref_keys(schema, &mut refs);
680 }
681
682 if let Some(schema) = &output_schema {
683 collect_ref_keys(schema, &mut refs);
684 }
685
686 let ref_hint_keys = if refs.is_empty() {
687 None
688 } else {
689 Some(refs.into_iter().collect())
690 };
691
692 Ok(Some(DiscoveryTypingPayload {
693 input_schema_json: input_schema
694 .as_ref()
695 .map(encode_stable_json)
696 .transpose()?,
697 output_schema_json: output_schema
698 .as_ref()
699 .map(encode_stable_json)
700 .transpose()?,
701 ref_hint_keys,
702 }))
703}
704
705fn build_parameter_documentation(
706 root: &Value,
707 path_item: &Map<String, Value>,
708 operation: &Map<String, Value>,
709 invocation: &OpenApiInvocationPayload,
710) -> Result<Vec<OpenApiParameterDocumentation>, String> {
711 let records_by_key = collect_parameter_record_by_key(root, path_item, operation);
712
713 invocation
714 .parameters
715 .iter()
716 .map(|parameter| {
717 let key = format!("{}:{}", parameter.location, parameter.name);
718 let record = records_by_key.get(&key);
719
720 let mut examples = record
721 .map(|item| pick_example_values(&Value::Object(item.clone()), None, None))
722 .transpose()?
723 .unwrap_or_default();
724
725 if examples.is_empty() {
726 examples = extract_examples_from_schema(record.and_then(|item| item.get("schema")))?;
727 }
728
729 Ok(OpenApiParameterDocumentation {
730 name: parameter.name.clone(),
731 location: parameter.location.clone(),
732 required: parameter.required,
733 description: record.and_then(|item| as_trimmed_string(item.get("description"))),
734 examples: if examples.is_empty() { None } else { Some(examples) },
735 })
736 })
737 .collect()
738}
739
740fn build_request_body_documentation(
741 root: &Value,
742 operation: &Map<String, Value>,
743) -> Result<Option<OpenApiRequestBodyDocumentation>, String> {
744 let Some(request_body) = resolve_request_body_record(root, operation) else {
745 return Ok(None);
746 };
747
748 let examples = if let Some((media_type, media_type_record)) = pick_content_entry(request_body.get("content")) {
749 extract_examples_from_media_type(&media_type, &media_type_record)?
750 } else {
751 Vec::new()
752 };
753
754 Ok(Some(OpenApiRequestBodyDocumentation {
755 description: as_trimmed_string(request_body.get("description")),
756 examples: if examples.is_empty() { None } else { Some(examples) },
757 }))
758}
759
760fn build_response_documentation(
761 root: &Value,
762 operation: &Map<String, Value>,
763) -> Result<Option<OpenApiResponseDocumentation>, String> {
764 let Some((status_code, response)) = pick_preferred_response_record(root, operation) else {
765 return Ok(None);
766 };
767
768 let content = response.get("content").and_then(as_object);
769 let content_types = collect_content_types(content);
770 let examples = if let Some((media_type, media_type_record)) = pick_content_entry(response.get("content")) {
771 extract_examples_from_media_type(&media_type, &media_type_record)?
772 } else {
773 Vec::new()
774 };
775
776 Ok(Some(OpenApiResponseDocumentation {
777 status_code,
778 description: as_trimmed_string(response.get("description")),
779 content_types,
780 examples: if examples.is_empty() { None } else { Some(examples) },
781 }))
782}
783
784fn build_tool_documentation(
785 root: &Value,
786 path_item: &Map<String, Value>,
787 operation: &Map<String, Value>,
788 invocation: &OpenApiInvocationPayload,
789) -> Result<Option<OpenApiToolDocumentation>, String> {
790 let summary = as_trimmed_string(operation.get("summary"));
791 let deprecated = operation.get("deprecated").and_then(Value::as_bool);
792 let parameters = build_parameter_documentation(root, path_item, operation, invocation)?;
793 let request_body = build_request_body_documentation(root, operation)?;
794 let response = build_response_documentation(root, operation)?;
795
796 if summary.is_none() && deprecated.is_none() && parameters.is_empty() && request_body.is_none() && response.is_none() {
797 return Ok(None);
798 }
799
800 Ok(Some(OpenApiToolDocumentation {
801 summary,
802 deprecated,
803 parameters,
804 request_body,
805 response,
806 }))
807}
808
809fn build_ref_hint_table(open_api_spec: &Value, initial_ref_keys: &[String]) -> Result<BTreeMap<String, String>, String> {
810 let mut queue = VecDeque::new();
811 let mut queued = HashSet::new();
812
813 for ref_key in initial_ref_keys {
814 if queued.insert(ref_key.clone()) {
815 queue.push_back(ref_key.clone());
816 }
817 }
818
819 let mut seen = HashSet::new();
820 let mut table = BTreeMap::new();
821
822 while let Some(ref_key) = queue.pop_front() {
823 if !seen.insert(ref_key.clone()) {
824 continue;
825 }
826
827 let Some(resolved) = resolve_json_pointer(open_api_spec, &ref_key) else {
828 continue;
829 };
830
831 table.insert(ref_key.clone(), encode_stable_json(resolved)?);
832
833 let mut nested = BTreeSet::new();
834 collect_ref_keys(resolved, &mut nested);
835 for nested_ref in nested {
836 if !seen.contains(&nested_ref) {
837 queue.push_back(nested_ref);
838 }
839 }
840 }
841
842 Ok(table)
843}
844
845fn normalize_path_for_tool_id(path_value: &str) -> String {
846 let trimmed = path_value.trim();
847 let no_leading = trimmed.strip_prefix('/').unwrap_or(trimmed);
848
849 let mut normalized = String::new();
850 let mut last_was_underscore = false;
851
852 for character in no_leading.chars() {
853 if character.is_ascii_alphanumeric() {
854 normalized.push(character);
855 last_was_underscore = false;
856 continue;
857 }
858
859 if !last_was_underscore {
860 normalized.push('_');
861 last_was_underscore = true;
862 }
863 }
864
865 let without_edges = normalized.trim_matches('_').to_lowercase();
866 if without_edges.is_empty() {
867 return "root".to_string();
868 }
869
870 without_edges
871}
872
873fn build_operation_id(operation: &Map<String, Value>) -> Option<String> {
874 as_trimmed_string(operation.get("operationId"))
875}
876
877fn build_tags(operation: &Map<String, Value>) -> Vec<String> {
878 operation
879 .get("tags")
880 .and_then(Value::as_array)
881 .map(|items| {
882 items
883 .iter()
884 .filter_map(|item| item.as_str().map(str::trim))
885 .filter(|item| !item.is_empty())
886 .map(ToOwned::to_owned)
887 .collect::<Vec<String>>()
888 })
889 .unwrap_or_default()
890}
891
892
893fn build_tool_id(method: &str, path_value: &str, operation: &Map<String, Value>) -> String {
894 if let Some(operation_id) = build_operation_id(operation) {
895 return operation_id;
896 }
897
898 format!("{}_{}", method, normalize_path_for_tool_id(path_value))
899}
900
901fn build_tool_name(method: &str, path_value: &str, operation: &Map<String, Value>) -> String {
902 if let Some(summary) = as_trimmed_string(operation.get("summary")) {
903 return summary;
904 }
905
906 if let Some(operation_id) = build_operation_id(operation) {
907 return operation_id;
908 }
909
910 format!("{} {}", method.to_uppercase(), path_value)
911}
912
913fn build_tool_description(operation: &Map<String, Value>) -> Option<String> {
914 as_trimmed_string(operation.get("description")).or_else(|| as_trimmed_string(operation.get("summary")))
915}
916
917fn ensure_unique_tool_ids(source_name: &str, tools: &[OpenApiExtractedTool]) -> Result<(), String> {
918 let mut seen = HashSet::new();
919
920 for tool in tools {
921 if !seen.insert(tool.tool_id.clone()) {
922 return Err(format!(
923 "OpenAPI extraction failed for '{source_name}': duplicate toolId {} ({})",
924 tool.tool_id,
925 tool.path
926 ));
927 }
928 }
929
930 Ok(())
931}
932
933fn extract_openapi_manifest(source_name: &str, openapi_spec: &Value) -> Result<OpenApiToolManifest, String> {
934 let spec_record = as_object(openapi_spec)
935 .ok_or_else(|| format!("OpenAPI extraction failed for '{source_name}': spec must be object"))?;
936
937 let Some(paths) = spec_record.get("paths").and_then(as_object) else {
938 return Ok(OpenApiToolManifest {
939 version: 2,
940 source_hash: hash_unknown(openapi_spec)?,
941 tools: Vec::new(),
942 ref_hint_table: None,
943 });
944 };
945
946 let mut path_keys: Vec<String> = paths.keys().cloned().collect();
947 path_keys.sort();
948
949 let mut tools = Vec::new();
950
951 for path_value in path_keys {
952 let Some(path_item) = paths.get(&path_value).and_then(as_object) else {
953 continue;
954 };
955
956 for method in OPEN_API_HTTP_METHODS {
957 let Some(operation) = path_item.get(method).and_then(as_object) else {
958 continue;
959 };
960
961 let invocation = build_invocation_metadata(openapi_spec, method, &path_value, path_item, operation);
962 let typing = build_tool_typing(openapi_spec, path_item, operation, &invocation)?;
963 let documentation = build_tool_documentation(openapi_spec, path_item, operation, &invocation)?;
964
965 let operation_hash_input = json!({
966 "method": method,
967 "path": path_value,
968 "operation": operation,
969 "invocation": invocation,
970 });
971
972 tools.push(OpenApiExtractedTool {
973 tool_id: build_tool_id(method, &path_value, operation),
974 operation_id: build_operation_id(operation),
975 tags: build_tags(operation),
976 name: build_tool_name(method, &path_value, operation),
977 description: build_tool_description(operation),
978 method: method.to_string(),
979 path: path_value.clone(),
980 invocation,
981 operation_hash: hash_unknown(&operation_hash_input)?,
982 typing,
983 documentation,
984 });
985 }
986 }
987
988 tools.sort_by(|left, right| {
989 left.tool_id
990 .to_lowercase()
991 .cmp(&right.tool_id.to_lowercase())
992 .then_with(|| left.tool_id.cmp(&right.tool_id))
993 });
994 ensure_unique_tool_ids(source_name, &tools)?;
995
996 let direct_ref_keys: Vec<String> = tools
997 .iter()
998 .flat_map(|tool| {
999 tool.typing
1000 .as_ref()
1001 .and_then(|typing| typing.ref_hint_keys.clone())
1002 .unwrap_or_default()
1003 })
1004 .collect();
1005
1006 let ref_hint_table = build_ref_hint_table(openapi_spec, &direct_ref_keys)?;
1007
1008 Ok(OpenApiToolManifest {
1009 version: 2,
1010 source_hash: hash_unknown(openapi_spec)?,
1011 tools,
1012 ref_hint_table: if ref_hint_table.is_empty() {
1013 None
1014 } else {
1015 Some(ref_hint_table)
1016 },
1017 })
1018}
packages/codemode-openapi/openapi-extractor-rs/src/main.rs+58−0
@@ -0,0 +1,58 @@
1use std::fs;
2use std::io::{self, Read, Write};
3
4use clap::Parser;
5
6#[derive(Parser, Debug)]
7#[command(name = "openapi-extractor-rs")]
8struct Cli {
9 #[arg(long)]
10 source_name: String,
11 #[arg(long)]
12 input: String,
13 #[arg(long)]
14 output: Option<String>,
15 #[arg(long, default_value_t = false)]
16 pretty: bool,
17}
18
19fn main() {
20 if let Err(error) = run() {
21 eprintln!("{error}");
22 std::process::exit(1);
23 }
24}
25
26fn run() -> Result<(), String> {
27 let args = Cli::parse();
28 let source_text = read_input_text(&args.input)?;
29
30 let output_json =
31 openapi_extractor_rs::extract_manifest_json(&args.source_name, &source_text, args.pretty)?;
32
33 if let Some(path) = args.output {
34 fs::write(path, output_json).map_err(|error| format!("write output file: {error}"))?;
35 } else {
36 let mut stdout = io::stdout().lock();
37 stdout
38 .write_all(output_json.as_bytes())
39 .map_err(|error| format!("write stdout: {error}"))?;
40 stdout
41 .write_all(b"\n")
42 .map_err(|error| format!("write stdout newline: {error}"))?;
43 }
44
45 Ok(())
46}
47
48fn read_input_text(input: &str) -> Result<String, String> {
49 if input == "-" {
50 let mut buffer = String::new();
51 io::stdin()
52 .read_to_string(&mut buffer)
53 .map_err(|error| format!("read stdin: {error}"))?;
54 return Ok(buffer);
55 }
56
57 fs::read_to_string(input).map_err(|error| format!("read input file '{input}': {error}"))
58}
packages/codemode-openapi/package.json+1−0
@@ -9,6 +9,7 @@ · 8 unmodified lines
9 "src"9 "src"
10 ],10 ],
11 "scripts": {11 "scripts": {
12 "extractor:build": "./openapi-extractor-rs/build-wasm.sh",
12 "typecheck": "bunx tsc --noEmit -p tsconfig.json",13 "typecheck": "bunx tsc --noEmit -p tsconfig.json",
13 "test": "vitest run",14 "test": "vitest run",
14 "test:watch": "vitest"15 "test:watch": "vitest"
packages/codemode-openapi/src/index.ts+20−0
@@ -6,11 +6,19 @@ export { · 5 unmodified lines
6 OpenApiExtractionError,6 OpenApiExtractionError,
7 extractOpenApiManifest,7 extractOpenApiManifest,
8} from "./openapi-extraction";8} from "./openapi-extraction";
9export {
10 compileOpenApiToolDefinitions,
11 openApiProviderDataJsonFromDefinition,
12 OpenApiToolDefinitionSchema,
13 type OpenApiToolDefinition,
14} from "./openapi-definitions";
15export { buildOpenApiToolPresentation, type OpenApiToolPresentation } from "./openapi-tool-presentation";
9export {16export {
10 OpenApiToolInvocationError,17 OpenApiToolInvocationError,
11 createOpenApiToolsFromManifest,18 createOpenApiToolsFromManifest,
12 createOpenApiToolsFromSpec,19 createOpenApiToolsFromSpec,
13} from "./openapi-tools";20} from "./openapi-tools";
21export { resolveSchemaJsonWithRefHints, resolveTypingSchemasWithRefHints } from "./openapi-schema-refs";
14export {22export {
15 OPEN_API_HTTP_METHODS,23 OPEN_API_HTTP_METHODS,
16 OPEN_API_PARAMETER_LOCATIONS,24 OPEN_API_PARAMETER_LOCATIONS,
@@ -22,6 +30,11 @@ export { · 5 unmodified lines
22 OpenApiToolManifestSchema,30 OpenApiToolManifestSchema,
23 OpenApiToolParameterSchema,31 OpenApiToolParameterSchema,
24 OpenApiToolRequestBodySchema,32 OpenApiToolRequestBodySchema,
33 OpenApiExampleSchema,
34 OpenApiParameterDocumentationSchema,
35 OpenApiRequestBodyDocumentationSchema,
36 OpenApiResponseDocumentationSchema,
37 OpenApiToolDocumentationSchema,
25 type DiscoveryTypingPayload,38 type DiscoveryTypingPayload,
26 type OpenApiExtractedTool,39 type OpenApiExtractedTool,
27 type OpenApiHttpMethod,40 type OpenApiHttpMethod,
@@ -35,3 +48,10 @@ export { · 7 unmodified lines
35 type OpenApiToolParameter,48 type OpenApiToolParameter,
36 type OpenApiToolRequestBody,49 type OpenApiToolRequestBody,
37} from "./openapi-types";50} from "./openapi-types";
51export type {
52 OpenApiExample,
53 OpenApiParameterDocumentation,
54 OpenApiRequestBodyDocumentation,
55 OpenApiResponseDocumentation,
56 OpenApiToolDocumentation,
57} from "./openapi-types";
packages/codemode-openapi/src/openapi-definitions.ts+254−0
@@ -0,0 +1,254 @@
1import { Schema } from "effect";
2
3import {
4 DiscoveryTypingPayloadSchema,
5 OpenApiHttpMethodSchema,
6 OpenApiInvocationPayloadSchema,
7 OpenApiToolDocumentationSchema,
8 type OpenApiExtractedTool,
9 type OpenApiToolManifest,
10} from "./openapi-types";
11
12const VERSION_SEGMENT_REGEX = /^v\d+(?:[._-]\d+)?$/i;
13const IGNORED_PATH_SEGMENTS = new Set(["api"]);
14
15const splitWords = (value: string): Array<string> =>
16 value
17 .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
18 .replace(/([A-Z]+)([A-Z][a-z0-9]+)/g, "$1 $2")
19 .replace(/[^a-zA-Z0-9]+/g, " ")
20 .trim()
21 .split(/\s+/)
22 .filter((part) => part.length > 0);
23
24const normalizeWord = (value: string): string => value.toLowerCase();
25
26const toCamelCase = (value: string): string => {
27 const words = splitWords(value).map(normalizeWord);
28 if (words.length === 0) {
29 return "tool";
30 }
31
32 const [first, ...rest] = words;
33 return `${first}${rest.map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join("")}`;
34};
35
36const toPascalCase = (value: string): string => {
37 const camel = toCamelCase(value);
38 return `${camel[0]?.toUpperCase() ?? ""}${camel.slice(1)}`;
39};
40
41const normalizeGroupSegment = (value: string | undefined): string | null => {
42 const candidate = value?.trim();
43 if (!candidate) {
44 return null;
45 }
46
47 return toCamelCase(candidate);
48};
49
50const pathSegmentsFromTemplate = (pathTemplate: string): Array<string> =>
51 pathTemplate
52 .split("/")
53 .map((segment) => segment.trim())
54 .filter((segment) => segment.length > 0);
55
56const isPathParameterSegment = (segment: string): boolean =>
57 segment.startsWith("{") && segment.endsWith("}");
58
59const deriveVersionSegment = (pathTemplate: string): string | undefined =>
60 pathSegmentsFromTemplate(pathTemplate)
61 .map((segment) => segment.toLowerCase())
62 .find((segment) => VERSION_SEGMENT_REGEX.test(segment));
63
64const derivePathGroup = (pathTemplate: string): string => {
65 for (const segment of pathSegmentsFromTemplate(pathTemplate)) {
66 const lower = segment.toLowerCase();
67 if (VERSION_SEGMENT_REGEX.test(lower)) {
68 continue;
69 }
70 if (IGNORED_PATH_SEGMENTS.has(lower)) {
71 continue;
72 }
73 if (isPathParameterSegment(segment)) {
74 continue;
75 }
76
77 return normalizeGroupSegment(segment) ?? "root";
78 }
79
80 return "root";
81};
82
83const splitOperationIdSegments = (value: string): Array<string> =>
84 value
85 .split(/[/.]+/)
86 .map((segment) => segment.trim())
87 .filter((segment) => segment.length > 0);
88
89const deriveLeafSeed = (tool: OpenApiExtractedTool, group: string): string => {
90 const preferredSource = tool.operationId ?? tool.toolId;
91 const segments = splitOperationIdSegments(preferredSource);
92 if (segments.length > 1) {
93 const [first, ...rest] = segments;
94 if ((normalizeGroupSegment(first) ?? first) === group && rest.length > 0) {
95 return rest.join(" ");
96 }
97 }
98
99 return preferredSource;
100};
101
102const fallbackLeafSeed = (tool: OpenApiExtractedTool, group: string): string => {
103 const relevantSegments = pathSegmentsFromTemplate(tool.path)
104 .filter((segment) => !VERSION_SEGMENT_REGEX.test(segment.toLowerCase()))
105 .filter((segment) => !IGNORED_PATH_SEGMENTS.has(segment.toLowerCase()))
106 .filter((segment) => !isPathParameterSegment(segment))
107 .map((segment) => normalizeGroupSegment(segment) ?? segment)
108 .filter((segment) => segment !== group);
109
110 const segmentSuffix = relevantSegments
111 .map((segment) => toPascalCase(segment))
112 .join("");
113
114 return `${tool.method}${segmentSuffix || "Operation"}`;
115};
116
117const deriveLeaf = (tool: OpenApiExtractedTool, group: string): string => {
118 const preferred = toCamelCase(deriveLeafSeed(tool, group));
119 if (preferred.length > 0 && preferred !== group) {
120 return preferred;
121 }
122
123 return toCamelCase(fallbackLeafSeed(tool, group));
124};
125
126const defaultDescription = (tool: OpenApiExtractedTool): string =>
127 tool.description ?? `${tool.method.toUpperCase()} ${tool.path}`;
128
129export const OpenApiToolDefinitionSchema = Schema.Struct({
130 toolId: Schema.String,
131 rawToolId: Schema.String,
132 operationId: Schema.optional(Schema.String),
133 name: Schema.String,
134 description: Schema.String,
135 group: Schema.String,
136 leaf: Schema.String,
137 tags: Schema.Array(Schema.String),
138 versionSegment: Schema.optional(Schema.String),
139 method: OpenApiHttpMethodSchema,
140 path: Schema.String,
141 invocation: OpenApiInvocationPayloadSchema,
142 operationHash: Schema.String,
143 typing: Schema.optional(DiscoveryTypingPayloadSchema),
144 documentation: Schema.optional(OpenApiToolDocumentationSchema),
145});
146
147export type OpenApiToolDefinition = typeof OpenApiToolDefinitionSchema.Type;
148
149type MutableOpenApiToolDefinition = {
150 -readonly [Key in keyof OpenApiToolDefinition]: OpenApiToolDefinition[Key];
151};
152
153const withResolvedToolIds = (
154 definitions: Array<Omit<OpenApiToolDefinition, "toolId">>,
155): Array<OpenApiToolDefinition> => {
156 const staged: Array<MutableOpenApiToolDefinition> = definitions.map((definition) => ({
157 ...definition,
158 toolId: `${definition.group}.${definition.leaf}`,
159 }));
160
161 const applyCandidates = (
162 candidates: Array<MutableOpenApiToolDefinition>,
163 factory: (definition: MutableOpenApiToolDefinition) => string,
164 ): void => {
165 const byToolId = new Map<string, Array<MutableOpenApiToolDefinition>>();
166 for (const candidate of candidates) {
167 const bucket = byToolId.get(candidate.toolId) ?? [];
168 bucket.push(candidate);
169 byToolId.set(candidate.toolId, bucket);
170 }
171
172 for (const bucket of byToolId.values()) {
173 if (bucket.length < 2) {
174 continue;
175 }
176
177 for (const definition of bucket) {
178 definition.toolId = factory(definition);
179 }
180 }
181 };
182
183 applyCandidates(staged, (definition) =>
184 definition.versionSegment
185 ? `${definition.group}.${definition.versionSegment}.${definition.leaf}`
186 : definition.toolId,
187 );
188
189 applyCandidates(staged, (definition) => {
190 const prefix = definition.versionSegment
191 ? `${definition.group}.${definition.versionSegment}`
192 : definition.group;
193 return `${prefix}.${definition.leaf}${toPascalCase(definition.method)}`;
194 });
195
196 applyCandidates(staged, (definition) => {
197 const prefix = definition.versionSegment
198 ? `${definition.group}.${definition.versionSegment}`
199 : definition.group;
200 return `${prefix}.${definition.leaf}${toPascalCase(definition.method)}${definition.operationHash.slice(0, 8)}`;
201 });
202
203 return staged;
204};
205
206export const compileOpenApiToolDefinitions = (
207 manifest: OpenApiToolManifest,
208): Array<OpenApiToolDefinition> => {
209 const definitions = manifest.tools.map((tool) => {
210 const group = normalizeGroupSegment(tool.tags[0]) ?? derivePathGroup(tool.path);
211 const leaf = deriveLeaf(tool, group);
212
213 return {
214 rawToolId: tool.toolId,
215 operationId: tool.operationId,
216 name: tool.name,
217 description: defaultDescription(tool),
218 group,
219 leaf,
220 tags: [...tool.tags],
221 versionSegment: deriveVersionSegment(tool.path),
222 method: tool.method,
223 path: tool.path,
224 invocation: tool.invocation,
225 operationHash: tool.operationHash,
226 typing: tool.typing,
227 documentation: tool.documentation,
228 } satisfies Omit<OpenApiToolDefinition, "toolId">;
229 });
230
231 return withResolvedToolIds(definitions).sort((left, right) =>
232 left.toolId.localeCompare(right.toolId)
233 || left.rawToolId.localeCompare(right.rawToolId)
234 || left.operationHash.localeCompare(right.operationHash),
235 );
236};
237
238export const openApiProviderDataJsonFromDefinition = (
239 definition: OpenApiToolDefinition,
240): string =>
241 JSON.stringify({
242 kind: "openapi",
243 toolId: definition.toolId,
244 rawToolId: definition.rawToolId,
245 operationId: definition.operationId,
246 group: definition.group,
247 leaf: definition.leaf,
248 tags: definition.tags,
249 versionSegment: definition.versionSegment,
250 method: definition.method,
251 path: definition.path,
252 operationHash: definition.operationHash,
253 documentation: definition.documentation,
254 });
packages/codemode-openapi/src/openapi-extraction.real-specs.test.ts+118−0
@@ -0,0 +1,118 @@
1import { describe, expect, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";
3
4import { extractOpenApiManifest } from "./openapi-extraction";
5import { createOpenApiToolsFromManifest } from "./openapi-tools";
6import type { OpenApiJsonObject } from "./openapi-types";
7
8const VERCEL_OPENAPI_SPEC_URL = "https://openapi.vercel.sh/";
9
10const fetchVercelOpenApiSpec = Effect.tryPromise({
11 try: async () => {
12 const response = await fetch(VERCEL_OPENAPI_SPEC_URL);
13 if (!response.ok) {
14 throw new Error(`Failed to fetch Vercel OpenAPI spec: HTTP ${response.status} ${response.statusText}`);
15 }
16
17 return response.json() as Promise<unknown>;
18 },
19 catch: (cause) =>
20 cause instanceof Error ? cause : new Error(String(cause)),
21});
22
23type ParsedInputSchema = {
24 type?: string;
25 properties?: Record<string, unknown>;
26 required?: Array<string>;
27};
28
29const parseInputSchema = (inputSchemaJson: string | undefined): ParsedInputSchema | null =>
30 inputSchemaJson ? (JSON.parse(inputSchemaJson) as ParsedInputSchema) : null;
31
32const expectedTools = [
33 {
34 toolId: "createAuthToken",
35 method: "post",
36 path: "/v3/user/tokens",
37 tags: ["authentication"],
38 requiredInputs: ["body"],
39 expectedPath: "source.vercel.authentication.createAuthToken",
40 },
41 {
42 toolId: "getProjectMembers",
43 method: "get",
44 path: "/v1/projects/{idOrName}/members",
45 tags: ["projectMembers"],
46 requiredInputs: ["idOrName"],
47 expectedPath: "source.vercel.projectMembers.getProjectMembers",
48 },
49 {
50 toolId: "addProjectDomain",
51 method: "post",
52 path: "/v10/projects/{idOrName}/domains",
53 tags: ["projects"],
54 requiredInputs: ["body", "idOrName"],
55 expectedPath: "source.vercel.projects.addProjectDomain",
56 },
57 {
58 toolId: "getDeploymentEvents",
59 method: "get",
60 path: "/v3/deployments/{idOrUrl}/events",
61 tags: ["deployments"],
62 requiredInputs: ["idOrUrl"],
63 expectedPath: "source.vercel.deployments.getDeploymentEvents",
64 },
65 {
66 toolId: "create-event",
67 method: "post",
68 path: "/v1/installations/{integrationConfigurationId}/events",
69 tags: ["marketplace"],
70 requiredInputs: ["body", "integrationConfigurationId"],
71 expectedPath: "source.vercel.marketplace.createEvent",
72 },
73] as const;
74
75describe("openapi-extraction real specs", () => {
76 it.effect("extracts and derives tool paths from Vercel's live OpenAPI spec", () =>
77 Effect.gen(function* () {
78 const spec = (yield* fetchVercelOpenApiSpec) as OpenApiJsonObject;
79 const manifest = yield* extractOpenApiManifest("vercel", spec);
80
81 expect(manifest.version).toBe(2);
82 expect(manifest.tools.length).toBeGreaterThan(250);
83
84 const selectedTools = expectedTools.map((expectedTool) => {
85 const tool = manifest.tools.find((candidate) => candidate.toolId === expectedTool.toolId);
86
87 expect(tool).toBeDefined();
88 expect(tool?.method).toBe(expectedTool.method);
89 expect(tool?.path).toBe(expectedTool.path);
90 expect(tool?.tags).toEqual(expectedTool.tags);
91
92 const inputSchema = parseInputSchema(tool?.typing?.inputSchemaJson);
93 expect(inputSchema?.type).toBe("object");
94 expect(Object.keys(inputSchema?.properties ?? {})).toEqual(
95 expect.arrayContaining([...expectedTool.requiredInputs]),
96 );
97 expect(inputSchema?.required ?? []).toEqual(
98 expect.arrayContaining([...expectedTool.requiredInputs]),
99 );
100
101 return tool!;
102 });
103
104 const tools = createOpenApiToolsFromManifest({
105 manifest: {
106 ...manifest,
107 tools: selectedTools,
108 },
109 baseUrl: "https://api.vercel.com",
110 namespace: "source.vercel",
111 });
112
113 expect(Object.keys(tools).sort()).toEqual(
114 expectedTools.map((tool) => tool.expectedPath).sort(),
115 );
116 }),
117 120_000);
118});
packages/codemode-openapi/src/openapi-extractor-wasm/openapi_extractor_bg.wasm+0−0

Binary file not shown.

packages/codemode-openapi/src/openapi-schema-refs.ts+135−0
@@ -0,0 +1,135 @@
1type JsonObject = Record<string, unknown>;
2
3type DiscoveryTypingLike = {
4 inputSchemaJson?: string;
5 outputSchemaJson?: string;
6};
7
8const isJsonObject = (value: unknown): value is JsonObject =>
9 typeof value === "object" && value !== null && !Array.isArray(value);
10
11const parseJson = (value: string): unknown | null => {
12 try {
13 return JSON.parse(value) as unknown;
14 } catch {
15 return null;
16 }
17};
18
19const resolveSchemaNode = (
20 value: unknown,
21 refHintTable: Readonly<Record<string, string>>,
22 parsedHintCache: Map<string, unknown>,
23 activeRefs: ReadonlySet<string>,
24): unknown => {
25 if (Array.isArray(value)) {
26 return value.map((item) =>
27 resolveSchemaNode(item, refHintTable, parsedHintCache, activeRefs)
28 );
29 }
30
31 if (!isJsonObject(value)) {
32 return value;
33 }
34
35 const ref = typeof value.$ref === "string" ? value.$ref : null;
36 if (ref && ref.startsWith("#/") && !activeRefs.has(ref)) {
37 let resolvedRefValue = parsedHintCache.get(ref);
38
39 if (resolvedRefValue === undefined) {
40 const rawHint = refHintTable[ref];
41 resolvedRefValue = typeof rawHint === "string" ? parseJson(rawHint) : null;
42 parsedHintCache.set(ref, resolvedRefValue);
43 }
44
45 if (resolvedRefValue !== null && resolvedRefValue !== undefined) {
46 const nextActiveRefs = new Set(activeRefs);
47 nextActiveRefs.add(ref);
48
49 const { $ref: _ignoredRef, ...rest } = value;
50 const resolvedTarget = resolveSchemaNode(
51 resolvedRefValue,
52 refHintTable,
53 parsedHintCache,
54 nextActiveRefs,
55 );
56
57 if (Object.keys(rest).length === 0) {
58 return resolvedTarget;
59 }
60
61 const resolvedRest = resolveSchemaNode(
62 rest,
63 refHintTable,
64 parsedHintCache,
65 activeRefs,
66 );
67
68 if (isJsonObject(resolvedTarget) && isJsonObject(resolvedRest)) {
69 return { ...resolvedTarget, ...resolvedRest };
70 }
71
72 return resolvedTarget;
73 }
74 }
75
76 const next: JsonObject = {};
77 for (const [key, nestedValue] of Object.entries(value)) {
78 next[key] = resolveSchemaNode(
79 nestedValue,
80 refHintTable,
81 parsedHintCache,
82 activeRefs,
83 );
84 }
85
86 return next;
87};
88
89export const resolveSchemaJsonWithRefHints = (
90 schemaJson: string | undefined,
91 refHintTable: Readonly<Record<string, string>> | undefined,
92): string | null => {
93 if (!schemaJson) {
94 return null;
95 }
96
97 const parsedSchema = parseJson(schemaJson);
98 if (parsedSchema === null) {
99 return schemaJson;
100 }
101
102 if (!refHintTable || Object.keys(refHintTable).length === 0) {
103 return schemaJson;
104 }
105
106 const resolved = resolveSchemaNode(
107 parsedSchema,
108 refHintTable,
109 new Map<string, unknown>(),
110 new Set<string>(),
111 );
112
113 try {
114 return JSON.stringify(resolved);
115 } catch {
116 return schemaJson;
117 }
118};
119
120export const resolveTypingSchemasWithRefHints = (
121 typing: DiscoveryTypingLike | undefined,
122 refHintTable: Readonly<Record<string, string>> | undefined,
123): {
124 inputSchemaJson: string | null;
125 outputSchemaJson: string | null;
126} => ({
127 inputSchemaJson: resolveSchemaJsonWithRefHints(
128 typing?.inputSchemaJson,
129 refHintTable,
130 ),
131 outputSchemaJson: resolveSchemaJsonWithRefHints(
132 typing?.outputSchemaJson,
133 refHintTable,
134 ),
135});
packages/codemode-openapi/src/openapi-tool-presentation.ts+106−0
@@ -0,0 +1,106 @@
1import { typeSignatureFromSchemaJson } from "@executor-v3/codemode-core";
2
3import {
4 resolveTypingSchemasWithRefHints,
5} from "./openapi-schema-refs";
6import type {
7 OpenApiExample,
8 OpenApiToolDocumentation,
9 OpenApiToolManifest,
10} from "./openapi-types";
11import type { OpenApiToolDefinition } from "./openapi-definitions";
12
13const parseJson = (value: string): unknown | undefined => {
14 try {
15 return JSON.parse(value) as unknown;
16 } catch {
17 return undefined;
18 }
19};
20
21const firstExample = (
22 examples: ReadonlyArray<OpenApiExample> | undefined,
23): OpenApiExample | undefined => examples?.[0];
24
25const buildExampleInputJson = (
26 documentation: OpenApiToolDocumentation | undefined,
27): string | undefined => {
28 if (!documentation) {
29 return undefined;
30 }
31
32 const input: Record<string, unknown> = {};
33
34 for (const parameter of documentation.parameters) {
35 const example = firstExample(parameter.examples);
36 if (!example) {
37 continue;
38 }
39
40 const parsedValue = parseJson(example.valueJson);
41 if (parsedValue !== undefined) {
42 input[parameter.name] = parsedValue;
43 }
44 }
45
46 const requestBodyExample = firstExample(documentation.requestBody?.examples);
47 if (requestBodyExample) {
48 const parsedValue = parseJson(requestBodyExample.valueJson);
49 if (parsedValue !== undefined) {
50 input.body = parsedValue;
51 }
52 }
53
54 return Object.keys(input).length > 0 ? JSON.stringify(input) : undefined;
55};
56
57const buildExampleOutputJson = (
58 documentation: OpenApiToolDocumentation | undefined,
59): string | undefined => firstExample(documentation?.response?.examples)?.valueJson;
60
61export type OpenApiToolPresentation = {
62 inputType: string;
63 outputType: string;
64 inputSchemaJson?: string;
65 outputSchemaJson?: string;
66 exampleInputJson?: string;
67 exampleOutputJson?: string;
68 providerDataJson: string;
69};
70
71export const buildOpenApiToolPresentation = (input: {
72 manifest: OpenApiToolManifest;
73 definition: OpenApiToolDefinition;
74}): OpenApiToolPresentation => {
75 const resolvedSchemas = resolveTypingSchemasWithRefHints(
76 input.definition.typing,
77 input.manifest.refHintTable,
78 );
79 const inputSchemaJson = resolvedSchemas.inputSchemaJson ?? undefined;
80 const outputSchemaJson = resolvedSchemas.outputSchemaJson ?? undefined;
81 const exampleInputJson = buildExampleInputJson(input.definition.documentation);
82 const exampleOutputJson = buildExampleOutputJson(input.definition.documentation);
83
84 return {
85 inputType: typeSignatureFromSchemaJson(inputSchemaJson, "unknown", 320),
86 outputType: typeSignatureFromSchemaJson(outputSchemaJson, "unknown", 320),
87 ...(inputSchemaJson ? { inputSchemaJson } : {}),
88 ...(outputSchemaJson ? { outputSchemaJson } : {}),
89 ...(exampleInputJson ? { exampleInputJson } : {}),
90 ...(exampleOutputJson ? { exampleOutputJson } : {}),
91 providerDataJson: JSON.stringify({
92 kind: "openapi",
93 toolId: input.definition.toolId,
94 rawToolId: input.definition.rawToolId,
95 operationId: input.definition.operationId,
96 group: input.definition.group,
97 leaf: input.definition.leaf,
98 tags: input.definition.tags,
99 versionSegment: input.definition.versionSegment,
100 method: input.definition.method,
101 path: input.definition.path,
102 operationHash: input.definition.operationHash,
103 documentation: input.definition.documentation,
104 }),
105 };
106};
packages/codemode-openapi/src/openapi-tools.test.ts+59−0
@@ -277,6 +277,8 @@ describe("openapi-tools", () => { · 276 unmodified lines
277 277
278 const resolvedGet = resolveToolDefinition(tools["source.demo.repos.getRepo"]!);278 const resolvedGet = resolveToolDefinition(tools["source.demo.repos.getRepo"]!);
279 expect(resolvedGet.metadata?.sourceKey).toBe("api.demo");279 expect(resolvedGet.metadata?.sourceKey).toBe("api.demo");
280 expect(resolvedGet.metadata?.providerKind).toBe("openapi");
281 expect(resolvedGet.metadata?.providerDataJson).toContain('"group":"repos"');
280 expect(resolvedGet.metadata?.inputSchemaJson).toBeDefined();282 expect(resolvedGet.metadata?.inputSchemaJson).toBeDefined();
281 }),283 }),
282 );284 );
@@ -303,6 +305,7 @@ describe("openapi-tools", () => { · 20 unmodified lines
303 );305 );
304 306
305 expect(result).toEqual({307 expect(result).toEqual({
308 ok: true,
306 status: 200,309 status: 200,
307 headers: expect.any(Object),310 headers: expect.any(Object),
308 body: {311 body: {
@@ -337,6 +340,7 @@ describe("openapi-tools", () => { · 28 unmodified lines
337 );340 );
338 341
339 expect(getResult).toEqual({342 expect(getResult).toEqual({
343 ok: true,
340 status: 200,344 status: 200,
341 headers: expect.any(Object),345 headers: expect.any(Object),
342 body: {346 body: {
@@ -354,6 +358,7 @@ describe("openapi-tools", () => { · 11 unmodified lines
354 );358 );
355 359
356 expect(postResult).toEqual({360 expect(postResult).toEqual({
361 ok: true,
357 status: 201,362 status: 201,
358 headers: expect.any(Object),363 headers: expect.any(Object),
359 body: {364 body: {
@@ -433,6 +438,7 @@ describe("openapi-tools", () => { · 73 unmodified lines
433 );438 );
434 439
435 expect(result).toEqual({440 expect(result).toEqual({
441 ok: true,
436 status: 200,442 status: 200,
437 headers: expect.any(Object),443 headers: expect.any(Object),
438 body: {444 body: {
@@ -482,6 +488,7 @@ describe("openapi-tools", () => { · 43 unmodified lines
482 );488 );
483 489
484 expect(result).toEqual({490 expect(result).toEqual({
491 ok: true,
485 status: 200,492 status: 200,
486 headers: expect.any(Object),493 headers: expect.any(Object),
487 body: {494 body: {
@@ -493,4 +500,56 @@ describe("openapi-tools", () => { · 5 unmodified lines
493 expect(server.requests[0]?.path).toBe("/repos/octocat/hello-world");500 expect(server.requests[0]?.path).toBe("/repos/octocat/hello-world");
494 }),501 }),
495 );502 );
503
504 it.effect("normalizes slash-based operationIds into grouped dotted tool paths", () =>
505 Effect.gen(function* () {
506 const tools = createOpenApiToolsFromManifest({
507 manifest: {
508 version: 2,
509 sourceHash: "fixture",
510 tools: [
511 {
512 toolId: "actions/create-workflow-dispatch",
513 operationId: "actions/create-workflow-dispatch",
514 tags: ["actions"],
515 name: "Create workflow dispatch",
516 description: null,
517 method: "post",
518 path: "/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches",
519 invocation: {
520 method: "post",
521 pathTemplate: "/repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches",
522 parameters: [],
523 requestBody: null,
524 },
525 operationHash: "hash_actions_create_workflow_dispatch",
526 },
527 {
528 toolId: "users/get-authenticated",
529 operationId: "users/get-authenticated",
530 tags: ["users"],
531 name: "Get authenticated user",
532 description: null,
533 method: "get",
534 path: "/user",
535 invocation: {
536 method: "get",
537 pathTemplate: "/user",
538 parameters: [],
539 requestBody: null,
540 },
541 operationHash: "hash_users_get_authenticated",
542 },
543 ],
544 },
545 baseUrl: "https://api.github.com",
546 namespace: "source.github",
547 });
548
549 expect(Object.keys(tools).sort()).toEqual([
550 "source.github.actions.createWorkflowDispatch",
551 "source.github.users.getAuthenticated",
552 ]);
553 }),
554 );
496});555});
packages/codemode-openapi/src/openapi-tools.ts+107−29
@@ -14,6 +14,11 @@ import { · 13 unmodified lines
14 unknownInputSchema,14 unknownInputSchema,
15} from "@executor-v3/codemode-core";15} from "@executor-v3/codemode-core";
16 16
17import {
18 compileOpenApiToolDefinitions,
19 type OpenApiToolDefinition,
20} from "./openapi-definitions";
21import { buildOpenApiToolPresentation } from "./openapi-tool-presentation";
17import {22import {
18 extractOpenApiManifest,23 extractOpenApiManifest,
19 type OpenApiExtractionError,24 type OpenApiExtractionError,
@@ -35,6 +40,44 @@ export class OpenApiToolInvocationError extends Data.TaggedError( · 15 unmodified lines
35 details: string | null;40 details: string | null;
36}> {}41}> {}
37 42
43const BLOCKED_RESPONSE_HEADER_NAMES = new Set([
44 "authorization",
45 "authentication-info",
46 "cookie",
47 "proxy-authenticate",
48 "proxy-authorization",
49 "set-cookie",
50 "set-cookie2",
51 "www-authenticate",
52 "x-api-key",
53 "x-auth-token",
54 "x-csrf-token",
55]);
56
57const NOISY_RESPONSE_HEADER_NAMES = new Set([
58 "alt-svc",
59 "cf-ray",
60 "server",
61 "traceparent",
62 "tracestate",
63 "via",
64 "x-cache",
65 "x-cache-hits",
66 "x-powered-by",
67 "x-request-id",
68 "x-runtime",
69 "x-served-by",
70 "x-trace-id",
71]);
72
73const NOISY_RESPONSE_HEADER_PREFIXES = [
74 "cf-",
75 "trace-",
76 "x-amz-cf-",
77 "x-b3-",
78 "x-cloud-trace-",
79];
80
38const isRecord = (value: unknown): value is Record<string, unknown> =>81const isRecord = (value: unknown): value is Record<string, unknown> =>
39 typeof value === "object" && value !== null && !Array.isArray(value);82 typeof value === "object" && value !== null && !Array.isArray(value);
40 83
@@ -201,13 +244,35 @@ const normalizeHttpUrl = (value: string): string => { · 160 unmodified lines
201 }244 }
202};245};
203 246
247const shouldDropResponseHeader = (headerName: string): boolean =>
248 BLOCKED_RESPONSE_HEADER_NAMES.has(headerName)
249 || NOISY_RESPONSE_HEADER_NAMES.has(headerName)
250 || NOISY_RESPONSE_HEADER_PREFIXES.some((prefix) => headerName.startsWith(prefix));
251
252const sanitizeResponseHeaders = (
253 headers: Readonly<Record<string, string>>,
254): Record<string, string> => {
255 const sanitized: Record<string, string> = {};
256
257 for (const [rawName, rawValue] of Object.entries(headers)) {
258 const name = rawName.trim().toLowerCase();
259 if (name.length === 0 || shouldDropResponseHeader(name)) {
260 continue;
261 }
262
263 sanitized[name] = rawValue.length > 4000 ? `${rawValue.slice(0, 4000)}...` : rawValue;
264 }
265
266 return sanitized;
267};
268
204const decodeHttpClientResponseBody = (269const decodeHttpClientResponseBody = (
205 response: Awaited<ReturnType<HttpClient.HttpClient["execute"]>> extends Effect.Effect<270 response: Awaited<ReturnType<HttpClient.HttpClient["execute"]>> extends Effect.Effect<
206 infer A,271 infer Value,
207 infer _E,272 infer _Error,
208 infer _R273 infer _Requirements
209 >274 >
210 ? A275 ? Value
211 : never,276 : never,
212): Effect.Effect<unknown, Error, never> => {277): Effect.Effect<unknown, Error, never> => {
213 const contentType = response.headers["content-type"]?.toLowerCase() ?? "";278 const contentType = response.headers["content-type"]?.toLowerCase() ?? "";
@@ -242,8 +307,7 @@ const inputSchemaFromTypingJson = (inputSchemaJson: string | undefined) => { · 28 unmodified lines
242 }307 }
243};308};
244 309
245 310export type CreateOpenApiToolsFromManifestInput = {
246type CreateOpenApiToolsFromManifestInput = {
247 manifest: OpenApiToolManifest;311 manifest: OpenApiToolManifest;
248 baseUrl: string;312 baseUrl: string;
249 namespace?: string;313 namespace?: string;
@@ -365,32 +429,38 @@ const buildFetchRequest = (input: { · 115 unmodified lines
365 };429 };
366};430};
367 431
432const createToolPath = (namespace: string | undefined, definition: OpenApiToolDefinition): string =>
433 namespace ? `${namespace}.${definition.toolId}` : definition.toolId;
434
368export const createOpenApiToolsFromManifest = (435export const createOpenApiToolsFromManifest = (
369 input: CreateOpenApiToolsFromManifestInput,436 input: CreateOpenApiToolsFromManifestInput,
370): ToolMap => {437): ToolMap => {
371 const baseUrl = normalizeHttpUrl(input.baseUrl);438 const baseUrl = normalizeHttpUrl(input.baseUrl);
372 const sourceKey = input.sourceKey ?? "openapi.generated";439 const sourceKey = input.sourceKey ?? "openapi.generated";
373 const namespace = input.namespace;
374 const defaultHeaders = input.defaultHeaders ?? {};440 const defaultHeaders = input.defaultHeaders ?? {};
375 const credentialHeaders = input.credentialHeaders ?? {};441 const credentialHeaders = input.credentialHeaders ?? {};
376 const httpClientLayer = input.httpClientLayer ?? FetchHttpClient.layer;442 const httpClientLayer = input.httpClientLayer ?? FetchHttpClient.layer;
377 443
444 const definitions = compileOpenApiToolDefinitions(input.manifest);
378 const result: ToolMap = {};445 const result: ToolMap = {};
379 446
380 for (const extracted of input.manifest.tools) {447 for (const definition of definitions) {
381 const toolPath = namespace448 const toolPath = createToolPath(input.namespace, definition);
382 ? `${namespace}.${extracted.toolId}`449 const presentation = buildOpenApiToolPresentation({
383 : extracted.toolId;450 manifest: input.manifest,
451 definition,
452 });
384 453
385 const description = extracted.description ?? `${extracted.method.toUpperCase()} ${extracted.path}`;
386 result[toolPath] = toTool({454 result[toolPath] = toTool({
387 tool: {455 tool: {
388 description,456 description: definition.description,
389 inputSchema: inputSchemaFromTypingJson(extracted.typing?.inputSchemaJson),457 inputSchema: inputSchemaFromTypingJson(
458 presentation.inputSchemaJson ?? definition.typing?.inputSchemaJson,
459 ),
390 execute: async (args: unknown) => {460 execute: async (args: unknown) => {
391 const decodedArgs = asToolArgs(args);461 const decodedArgs = asToolArgs(args);
392 const request = buildFetchRequest({462 const request = buildFetchRequest({
393 payload: extracted.invocation,463 payload: definition.invocation,
394 args: decodedArgs,464 args: decodedArgs,
395 baseUrl,465 baseUrl,
396 defaultHeaders,466 defaultHeaders,
@@ -422,8 +492,9 @@ export const createOpenApiToolsFromManifest = ( · 25 unmodified lines
422 const body = yield* decodeHttpClientResponseBody(response);492 const body = yield* decodeHttpClientResponseBody(response);
423 493
424 return {494 return {
495 ok: response.status >= 200 && response.status < 300,
425 status: response.status,496 status: response.status,
426 headers: { ...response.headers },497 headers: sanitizeResponseHeaders(response.headers),
427 body,498 body,
428 };499 };
429 }).pipe(500 }).pipe(
@@ -434,9 +505,14 @@ export const createOpenApiToolsFromManifest = ( · 4 unmodified lines
434 },505 },
435 metadata: {506 metadata: {
436 sourceKey,507 sourceKey,
437 inputSchemaJson: extracted.typing?.inputSchemaJson,508 inputType: presentation.inputType,
438 outputSchemaJson: extracted.typing?.outputSchemaJson,509 outputType: presentation.outputType,
439 refHintKeys: extracted.typing?.refHintKeys,510 inputSchemaJson: presentation.inputSchemaJson,
511 outputSchemaJson: presentation.outputSchemaJson,
512 exampleInputJson: presentation.exampleInputJson,
513 exampleOutputJson: presentation.exampleOutputJson,
514 providerKind: "openapi",
515 providerDataJson: presentation.providerDataJson,
440 },516 },
441 });517 });
442 }518 }
@@ -454,20 +530,22 @@ export const createOpenApiToolsFromSpec = (input: { · 11 unmodified lines
454 credentialHeaders?: Readonly<Record<string, string>>;530 credentialHeaders?: Readonly<Record<string, string>>;
455 httpClientLayer?: Layer.Layer<HttpClient.HttpClient, never, never>;531 httpClientLayer?: Layer.Layer<HttpClient.HttpClient, never, never>;
456}): Effect.Effect<532}): Effect.Effect<
457 { manifest: OpenApiToolManifest; tools: ToolMap },533 { manifest: OpenApiToolManifest; definitions: Array<OpenApiToolDefinition>; tools: ToolMap },
458 OpenApiExtractionError534 OpenApiExtractionError
459> =>535> =>
460 Effect.map(536 Effect.map(
461 extractOpenApiManifest(input.sourceName, input.openApiSpec),537 extractOpenApiManifest(input.sourceName, input.openApiSpec),
462 (manifest: OpenApiToolManifest) => ({538 (manifest: OpenApiToolManifest) => ({
463 manifest,
464 tools: createOpenApiToolsFromManifest({
465 manifest,539 manifest,
466 baseUrl: input.baseUrl,540 definitions: compileOpenApiToolDefinitions(manifest),
467 namespace: input.namespace,541 tools: createOpenApiToolsFromManifest({
468 sourceKey: input.sourceKey,542 manifest,
469 defaultHeaders: input.defaultHeaders,543 baseUrl: input.baseUrl,
470 credentialHeaders: input.credentialHeaders,544 namespace: input.namespace,
471 httpClientLayer: input.httpClientLayer,545 sourceKey: input.sourceKey,
546 defaultHeaders: input.defaultHeaders,
547 credentialHeaders: input.credentialHeaders,
548 httpClientLayer: input.httpClientLayer,
549 }),
472 }),550 }),
473 }));551 );
packages/codemode-openapi/src/openapi-types.ts+43−2
@@ -13,7 +13,6 @@ export type OpenApiJsonObject = { · 12 unmodified lines
13 [key: string]: OpenApiJsonValue;13 [key: string]: OpenApiJsonValue;
14};14};
15 15
16// Accepts Effect-generated OpenAPI specs and generic JSON-like OpenAPI objects.
17export type OpenApiSpecInput = string | OpenAPISpec | OpenApiJsonObject;16export type OpenApiSpecInput = string | OpenAPISpec | OpenApiJsonObject;
18 17
19export const OPEN_API_HTTP_METHODS = [18export const OPEN_API_HTTP_METHODS = [
@@ -64,8 +63,44 @@ export const DiscoveryTypingPayloadSchema = Schema.Struct({ · 44 unmodified lines
64 refHintKeys: Schema.optional(Schema.Array(Schema.String)),63 refHintKeys: Schema.optional(Schema.Array(Schema.String)),
65});64});
66 65
66export const OpenApiExampleSchema = Schema.Struct({
67 valueJson: Schema.String,
68 mediaType: Schema.optional(Schema.String),
69 label: Schema.optional(Schema.String),
70});
71
72export const OpenApiParameterDocumentationSchema = Schema.Struct({
73 name: Schema.String,
74 location: OpenApiParameterLocationSchema,
75 required: Schema.Boolean,
76 description: Schema.optional(Schema.String),
77 examples: Schema.optional(Schema.Array(OpenApiExampleSchema)),
78});
79
80export const OpenApiRequestBodyDocumentationSchema = Schema.Struct({
81 description: Schema.optional(Schema.String),
82 examples: Schema.optional(Schema.Array(OpenApiExampleSchema)),
83});
84
85export const OpenApiResponseDocumentationSchema = Schema.Struct({
86 statusCode: Schema.String,
87 description: Schema.optional(Schema.String),
88 contentTypes: Schema.Array(Schema.String),
89 examples: Schema.optional(Schema.Array(OpenApiExampleSchema)),
90});
91
92export const OpenApiToolDocumentationSchema = Schema.Struct({
93 summary: Schema.optional(Schema.String),
94 deprecated: Schema.optional(Schema.Boolean),
95 parameters: Schema.Array(OpenApiParameterDocumentationSchema),
96 requestBody: Schema.optional(OpenApiRequestBodyDocumentationSchema),
97 response: Schema.optional(OpenApiResponseDocumentationSchema),
98});
99
67export const OpenApiExtractedToolSchema = Schema.Struct({100export const OpenApiExtractedToolSchema = Schema.Struct({
68 toolId: Schema.String,101 toolId: Schema.String,
102 operationId: Schema.optional(Schema.String),
103 tags: Schema.Array(Schema.String),
69 name: Schema.String,104 name: Schema.String,
70 description: Schema.NullOr(Schema.String),105 description: Schema.NullOr(Schema.String),
71 method: OpenApiHttpMethodSchema,106 method: OpenApiHttpMethodSchema,
@@ -73,10 +108,11 @@ export const OpenApiExtractedToolSchema = Schema.Struct({ · 1 unmodified line
73 invocation: OpenApiInvocationPayloadSchema,108 invocation: OpenApiInvocationPayloadSchema,
74 operationHash: Schema.String,109 operationHash: Schema.String,
75 typing: Schema.optional(DiscoveryTypingPayloadSchema),110 typing: Schema.optional(DiscoveryTypingPayloadSchema),
111 documentation: Schema.optional(OpenApiToolDocumentationSchema),
76});112});
77 113
78export const OpenApiToolManifestSchema = Schema.Struct({114export const OpenApiToolManifestSchema = Schema.Struct({
79 version: Schema.Literal(1),115 version: Schema.Literal(2),
80 sourceHash: Schema.String,116 sourceHash: Schema.String,
81 tools: Schema.Array(OpenApiExtractedToolSchema),117 tools: Schema.Array(OpenApiExtractedToolSchema),
82 refHintTable: Schema.optional(118 refHintTable: Schema.optional(
@@ -93,5 +129,10 @@ export type OpenApiToolParameter = typeof OpenApiToolParameterSchema.Type; · 10 unmodified lines
93export type OpenApiToolRequestBody = typeof OpenApiToolRequestBodySchema.Type;129export type OpenApiToolRequestBody = typeof OpenApiToolRequestBodySchema.Type;
94export type OpenApiInvocationPayload = typeof OpenApiInvocationPayloadSchema.Type;130export type OpenApiInvocationPayload = typeof OpenApiInvocationPayloadSchema.Type;
95export type DiscoveryTypingPayload = typeof DiscoveryTypingPayloadSchema.Type;131export type DiscoveryTypingPayload = typeof DiscoveryTypingPayloadSchema.Type;
132export type OpenApiExample = typeof OpenApiExampleSchema.Type;
133export type OpenApiParameterDocumentation = typeof OpenApiParameterDocumentationSchema.Type;
134export type OpenApiRequestBodyDocumentation = typeof OpenApiRequestBodyDocumentationSchema.Type;
135export type OpenApiResponseDocumentation = typeof OpenApiResponseDocumentationSchema.Type;
136export type OpenApiToolDocumentation = typeof OpenApiToolDocumentationSchema.Type;
96export type OpenApiExtractedTool = typeof OpenApiExtractedToolSchema.Type;137export type OpenApiExtractedTool = typeof OpenApiExtractedToolSchema.Type;
97export type OpenApiToolManifest = typeof OpenApiToolManifestSchema.Type;138export type OpenApiToolManifest = typeof OpenApiToolManifestSchema.Type;
packages/control-plane/drizzle/20260307134000_openapi_tool_identity/migration.sql+37−0
@@ -0,0 +1,37 @@
1ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_raw_tool_id" text;--> statement-breakpoint
2ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_operation_id" text;--> statement-breakpoint
3ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_tags_json" text;--> statement-breakpoint
4UPDATE "tool_artifacts"
5SET
6 "openapi_raw_tool_id" = "tool_id",
7 "openapi_operation_id" = "tool_id",
8 "openapi_tags_json" = '[]'
9WHERE "provider_kind" = 'openapi';--> statement-breakpoint
10ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_mcp_shape_check";--> statement-breakpoint
11ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_openapi_shape_check";--> statement-breakpoint
12ALTER TABLE "tool_artifacts"
13ADD CONSTRAINT "tool_artifacts_mcp_shape_check" CHECK (
14 "provider_kind" <> 'mcp'
15 OR (
16 "mcp_tool_name" is not null
17 and "openapi_method" is null
18 and "openapi_path_template" is null
19 and "openapi_operation_hash" is null
20 and "openapi_raw_tool_id" is null
21 and "openapi_operation_id" is null
22 and "openapi_tags_json" is null
23 and "openapi_request_body_required" is null
24 )
25);--> statement-breakpoint
26ALTER TABLE "tool_artifacts"
27ADD CONSTRAINT "tool_artifacts_openapi_shape_check" CHECK (
28 "provider_kind" <> 'openapi'
29 OR (
30 "mcp_tool_name" is null
31 and "openapi_method" in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
32 and "openapi_path_template" is not null
33 and "openapi_operation_hash" is not null
34 and "openapi_raw_tool_id" is not null
35 and "openapi_tags_json" is not null
36 )
37);
packages/control-plane/drizzle/20260307152000_source_document_text/migration.sql+1−0
@@ -0,0 +1 @@
1ALTER TABLE "sources" ADD COLUMN "source_document_text" text;
packages/control-plane/package.json+3−1
@@ -3,7 +3,9 @@ · 2 unmodified lines
3 "private": true,3 "private": true,
4 "type": "module",4 "type": "module",
5 "exports": {5 "exports": {
6 ".": "./src/index.ts"6 ".": "./src/index.ts",
7 "./client": "./src/client.ts",
8 "./schema": "./src/schema/index.ts"
7 },9 },
8 "imports": {10 "imports": {
9 "#schema": "./src/schema/index.ts",11 "#schema": "./src/schema/index.ts",
packages/control-plane/src/api/sources/api.ts+33−0
@@ -3,6 +3,10 @@ import { · 2 unmodified lines
3 ExecutionInteractionIdSchema,3 ExecutionInteractionIdSchema,
4 SourceAuthSchema,4 SourceAuthSchema,
5 SourceIdSchema,5 SourceIdSchema,
6 SourceInspectionDiscoverPayloadSchema,
7 SourceInspectionDiscoverResultSchema,
8 SourceInspectionSchema,
9 SourceInspectionToolDetailSchema,
6 SourceKindSchema,10 SourceKindSchema,
7 SourceSchema,11 SourceSchema,
8 SourceStatusSchema,12 SourceStatusSchema,
@@ -72,6 +76,7 @@ export type UpdateSourcePayload = typeof UpdateSourcePayloadSchema.Type; · 63 unmodified lines
72 76
73const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);77const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
74const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema);78const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema);
79const toolPathParam = HttpApiSchema.param("toolPath", Schema.String);
75 80
76const CredentialPageUrlParamsSchema = Schema.Struct({81const CredentialPageUrlParamsSchema = Schema.Struct({
77 interactionId: ExecutionInteractionIdSchema,82 interactionId: ExecutionInteractionIdSchema,
@@ -163,4 +168,32 @@ export class SourcesApi extends HttpApiGroup.make("sources") · 85 unmodified lines
163 .addError(ControlPlaneNotFoundError)168 .addError(ControlPlaneNotFoundError)
164 .addError(ControlPlaneStorageError),169 .addError(ControlPlaneStorageError),
165 )170 )
171 .add(
172 HttpApiEndpoint.get("inspection")`/workspaces/${workspaceIdParam}/sources/${sourceIdParam}/inspection`
173 .addSuccess(SourceInspectionSchema)
174 .addError(ControlPlaneBadRequestError)
175 .addError(ControlPlaneUnauthorizedError)
176 .addError(ControlPlaneForbiddenError)
177 .addError(ControlPlaneNotFoundError)
178 .addError(ControlPlaneStorageError),
179 )
180 .add(
181 HttpApiEndpoint.get("inspectionTool")`/workspaces/${workspaceIdParam}/sources/${sourceIdParam}/tools/${toolPathParam}/inspection`
182 .addSuccess(SourceInspectionToolDetailSchema)
183 .addError(ControlPlaneBadRequestError)
184 .addError(ControlPlaneUnauthorizedError)
185 .addError(ControlPlaneForbiddenError)
186 .addError(ControlPlaneNotFoundError)
187 .addError(ControlPlaneStorageError),
188 )
189 .add(
190 HttpApiEndpoint.post("inspectionDiscover")`/workspaces/${workspaceIdParam}/sources/${sourceIdParam}/inspection/discover`
191 .setPayload(SourceInspectionDiscoverPayloadSchema)
192 .addSuccess(SourceInspectionDiscoverResultSchema)
193 .addError(ControlPlaneBadRequestError)
194 .addError(ControlPlaneUnauthorizedError)
195 .addError(ControlPlaneForbiddenError)
196 .addError(ControlPlaneNotFoundError)
197 .addError(ControlPlaneStorageError),
198 )
166 .prefix("/v1") {}199 .prefix("/v1") {}
packages/control-plane/src/api/sources/http.ts+37−0
@@ -17,6 +17,11 @@ import { · 16 unmodified lines
17 removeSource,17 removeSource,
18 updateSource,18 updateSource,
19} from "../../runtime/sources-operations";19} from "../../runtime/sources-operations";
20import {
21 discoverSourceInspectionTools,
22 getSourceInspection,
23 getSourceInspectionToolDetail,
24} from "../../runtime/source-inspection";
20import {25import {
21 completeSourceCredentialSetup,26 completeSourceCredentialSetup,
22 getSourceCredentialInteraction,27 getSourceCredentialInteraction,
@@ -512,6 +517,38 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 489 unmodified lines
512 ),517 ),
513 ),518 ),
514 )519 )
520 .handle("inspection", ({ path }) =>
521 withWorkspaceRequestActor("sources.inspection", path.workspaceId, () =>
522 withPolicy(requireReadSources(path.workspaceId))(
523 getSourceInspection({
524 workspaceId: path.workspaceId,
525 sourceId: path.sourceId,
526 }),
527 ),
528 ),
529 )
530 .handle("inspectionTool", ({ path }) =>
531 withWorkspaceRequestActor("sources.inspection_tool", path.workspaceId, () =>
532 withPolicy(requireReadSources(path.workspaceId))(
533 getSourceInspectionToolDetail({
534 workspaceId: path.workspaceId,
535 sourceId: path.sourceId,
536 toolPath: path.toolPath,
537 }),
538 ),
539 ),
540 )
541 .handle("inspectionDiscover", ({ path, payload }) =>
542 withWorkspaceRequestActor("sources.inspection_discover", path.workspaceId, () =>
543 withPolicy(requireReadSources(path.workspaceId))(
544 discoverSourceInspectionTools({
545 workspaceId: path.workspaceId,
546 sourceId: path.sourceId,
547 payload,
548 }),
549 ),
550 ),
551 )
515 .handle("update", ({ path, payload }) =>552 .handle("update", ({ path, payload }) =>
516 withWorkspaceRequestActor("sources.update", path.workspaceId, () =>553 withWorkspaceRequestActor("sources.update", path.workspaceId, () =>
517 withPolicy(requireWriteSources(path.workspaceId))(554 withPolicy(requireWriteSources(path.workspaceId))(
packages/control-plane/src/client.ts+2−2
@@ -6,8 +6,8 @@ import { · 5 unmodified lines
6} from "@effect/platform";6} from "@effect/platform";
7import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
8 8
9import { ControlPlaneApi } from "./api";9import { ControlPlaneApi } from "./api/api";
10import { ControlPlaneAuthHeaders } from "./runtime";10import { ControlPlaneAuthHeaders } from "./runtime/actor-resolver";
11 11
12export const createControlPlaneClient = (input: {12export const createControlPlaneClient = (input: {
13 baseUrl: string;13 baseUrl: string;
packages/control-plane/src/persistence/control-plane-rows.test.ts+1−0
@@ -70,6 +70,7 @@ describe("control-plane-persistence-drizzle", () => { · 69 unmodified lines
70 authHeaderName: null,70 authHeaderName: null,
71 authPrefix: null,71 authPrefix: null,
72 sourceHash: null,72 sourceHash: null,
73 sourceDocumentText: null,
73 lastError: null,74 lastError: null,
74 createdAt: now,75 createdAt: now,
75 updatedAt: now,76 updatedAt: now,
packages/control-plane/src/persistence/repos/sources-repo.ts+1−0
@@ -35,6 +35,7 @@ const toSourceUpdateSet = ( · 34 unmodified lines
35 if (patch.authHeaderName !== undefined) set.authHeaderName = patch.authHeaderName;35 if (patch.authHeaderName !== undefined) set.authHeaderName = patch.authHeaderName;
36 if (patch.authPrefix !== undefined) set.authPrefix = patch.authPrefix;36 if (patch.authPrefix !== undefined) set.authPrefix = patch.authPrefix;
37 if (patch.sourceHash !== undefined) set.sourceHash = patch.sourceHash;37 if (patch.sourceHash !== undefined) set.sourceHash = patch.sourceHash;
38 if (patch.sourceDocumentText !== undefined) set.sourceDocumentText = patch.sourceDocumentText;
38 if (patch.lastError !== undefined) set.lastError = patch.lastError;39 if (patch.lastError !== undefined) set.lastError = patch.lastError;
39 if (patch.updatedAt !== undefined) set.updatedAt = patch.updatedAt;40 if (patch.updatedAt !== undefined) set.updatedAt = patch.updatedAt;
40 41
packages/control-plane/src/persistence/schema.ts+9−0
@@ -135,6 +135,7 @@ export const sourcesTable = pgTable( · 134 unmodified lines
135 authHeaderName: text("auth_header_name"),135 authHeaderName: text("auth_header_name"),
136 authPrefix: text("auth_prefix"),136 authPrefix: text("auth_prefix"),
137 sourceHash: text("source_hash"),137 sourceHash: text("source_hash"),
138 sourceDocumentText: text("source_document_text"),
138 lastError: text("last_error"),139 lastError: text("last_error"),
139 createdAt: bigint("created_at", { mode: "number" }).notNull(),140 createdAt: bigint("created_at", { mode: "number" }).notNull(),
140 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),141 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
@@ -205,6 +206,9 @@ export const toolArtifactsTable = pgTable( · 64 unmodified lines
205 openApiMethod: text("openapi_method"),206 openApiMethod: text("openapi_method"),
206 openApiPathTemplate: text("openapi_path_template"),207 openApiPathTemplate: text("openapi_path_template"),
207 openApiOperationHash: text("openapi_operation_hash"),208 openApiOperationHash: text("openapi_operation_hash"),
209 openApiRawToolId: text("openapi_raw_tool_id"),
210 openApiOperationId: text("openapi_operation_id"),
211 openApiTagsJson: text("openapi_tags_json"),
208 openApiRequestBodyRequired: boolean("openapi_request_body_required"),212 openApiRequestBodyRequired: boolean("openapi_request_body_required"),
209 createdAt: bigint("created_at", { mode: "number" }).notNull(),213 createdAt: bigint("created_at", { mode: "number" }).notNull(),
210 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),214 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
@@ -236,6 +240,9 @@ export const toolArtifactsTable = pgTable( · 25 unmodified lines
236 and ${table.openApiMethod} is null240 and ${table.openApiMethod} is null
237 and ${table.openApiPathTemplate} is null241 and ${table.openApiPathTemplate} is null
238 and ${table.openApiOperationHash} is null242 and ${table.openApiOperationHash} is null
243 and ${table.openApiRawToolId} is null
244 and ${table.openApiOperationId} is null
245 and ${table.openApiTagsJson} is null
239 and ${table.openApiRequestBodyRequired} is null246 and ${table.openApiRequestBodyRequired} is null
240 )`,247 )`,
241 ),248 ),
@@ -247,6 +254,8 @@ export const toolArtifactsTable = pgTable( · 5 unmodified lines
247 and ${table.openApiMethod} in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')254 and ${table.openApiMethod} in ('get', 'put', 'post', 'delete', 'patch', 'head', 'options', 'trace')
248 and ${table.openApiPathTemplate} is not null255 and ${table.openApiPathTemplate} is not null
249 and ${table.openApiOperationHash} is not null256 and ${table.openApiOperationHash} is not null
257 and ${table.openApiRawToolId} is not null
258 and ${table.openApiTagsJson} is not null
250 )`,259 )`,
251 ),260 ),
252 ],261 ],
packages/control-plane/src/runtime/control-plane-runtime.test.ts+2−0
@@ -159,6 +159,7 @@ describe("control-plane-runtime", () => { · 158 unmodified lines
159 authHeaderName: null,159 authHeaderName: null,
160 authPrefix: null,160 authPrefix: null,
161 sourceHash: null,161 sourceHash: null,
162 sourceDocumentText: null,
162 lastError: null,163 lastError: null,
163 createdAt: now,164 createdAt: now,
164 updatedAt: now,165 updatedAt: now,
@@ -307,6 +308,7 @@ describe("control-plane-runtime", () => { · 142 unmodified lines
307 authHeaderName: null,308 authHeaderName: null,
308 authPrefix: null,309 authPrefix: null,
309 sourceHash: null,310 sourceHash: null,
311 sourceDocumentText: null,
310 lastError: null,312 lastError: null,
311 createdAt: now,313 createdAt: now,
312 updatedAt: now,314 updatedAt: now,
packages/control-plane/src/runtime/executor-tools.ts+2−2
@@ -373,8 +373,8 @@ export const createExecutorToolMap = (input: { · 372 unmodified lines
373 },373 },
374 },374 },
375 metadata: {375 metadata: {
376 inputHint: EXECUTOR_SOURCES_ADD_INPUT_HINT,376 inputType: EXECUTOR_SOURCES_ADD_INPUT_HINT,
377 outputHint: EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE,377 outputType: EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE,
378 inputSchemaJson: EXECUTOR_SOURCES_ADD_INPUT_SCHEMA_JSON,378 inputSchemaJson: EXECUTOR_SOURCES_ADD_INPUT_SCHEMA_JSON,
379 outputSchemaJson: EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA_JSON,379 outputSchemaJson: EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA_JSON,
380 sourceKey: "executor",380 sourceKey: "executor",
packages/control-plane/src/runtime/index.ts+2−1
@@ -57,6 +57,7 @@ export * from "./schema-type-signature"; · 56 unmodified lines
57export * from "./source-auth-service";57export * from "./source-auth-service";
58export * from "./store";58export * from "./store";
59export * from "./workspace-execution-environment";59export * from "./workspace-execution-environment";
60export * from "./source-inspection";
60 61
61export type RuntimeControlPlaneOptions = {62export type RuntimeControlPlaneOptions = {
62 actorResolver?: ControlPlaneActorResolverShape;63 actorResolver?: ControlPlaneActorResolverShape;
@@ -114,7 +115,7 @@ const createRuntimeLayerFromContext = ( · 51 unmodified lines
114 | RuntimeExecutionResolverService115 | RuntimeExecutionResolverService
115 >;116 >;
116 117
117 return Layer.succeedContext(runtimeContext);118 return Layer.succeedContext(runtimeContext) as RuntimeControlPlaneLayer;
118};119};
119 120
120export const createRuntimeControlPlaneLayer = (121export const createRuntimeControlPlaneLayer = (
packages/control-plane/src/runtime/source-auth-service.ts+14−7
@@ -165,7 +165,7 @@ const persistSource = (rows: SqlControlPlaneRows, source: Source) => · 164 unmodified lines
165 if (Option.isNone(existing)) {165 if (Option.isNone(existing)) {
166 yield* rows.sources.insert(sourceRecord);166 yield* rows.sources.insert(sourceRecord);
167 } else {167 } else {
168 const { id: _id, workspaceId: _workspaceId, createdAt: _createdAt, ...patch } = sourceRecord;168 const { id: _id, workspaceId: _workspaceId, createdAt: _createdAt, sourceDocumentText: _sourceDocumentText, ...patch } = sourceRecord;
169 yield* rows.sources.update(source.workspaceId, source.id, patch);169 yield* rows.sources.update(source.workspaceId, source.id, patch);
170 }170 }
171 171
@@ -431,12 +431,19 @@ const updateSourceStatus = (rows: SqlControlPlaneRows, source: Source, input: { · 259 unmodified lines
431 lastError?: string | null;431 lastError?: string | null;
432 auth?: Source["auth"];432 auth?: Source["auth"];
433}) =>433}) =>
434 persistSource(rows, {434 Effect.gen(function* () {
435 ...source,435 const latest = yield* loadSourceById(rows, {
436 status: input.status,436 workspaceId: source.workspaceId,
437 lastError: input.lastError ?? null,437 sourceId: source.id,
438 auth: input.auth ?? source.auth,438 });
439 updatedAt: Date.now(),439
440 return yield* persistSource(rows, {
441 ...latest,
442 status: input.status,
443 lastError: input.lastError ?? null,
444 auth: input.auth ?? latest.auth,
445 updatedAt: Date.now(),
446 });
440 });447 });
441 448
442const upsertSecretMaterial = (rows: SqlControlPlaneRows, input: {449const upsertSecretMaterial = (rows: SqlControlPlaneRows, input: {
packages/control-plane/src/runtime/source-definitions.ts+1−0
@@ -255,6 +255,7 @@ export const splitSourceForStorage = (input: { · 254 unmodified lines
255 input.source.auth.kind === "none" ? null : input.source.auth.headerName,255 input.source.auth.kind === "none" ? null : input.source.auth.headerName,
256 authPrefix: input.source.auth.kind === "none" ? null : input.source.auth.prefix,256 authPrefix: input.source.auth.kind === "none" ? null : input.source.auth.prefix,
257 sourceHash: input.source.sourceHash,257 sourceHash: input.source.sourceHash,
258 sourceDocumentText: null,
258 lastError: input.source.lastError,259 lastError: input.source.lastError,
259 createdAt: input.source.createdAt,260 createdAt: input.source.createdAt,
260 updatedAt: input.source.updatedAt,261 updatedAt: input.source.updatedAt,
packages/control-plane/src/runtime/source-inspection.ts+520−0
@@ -0,0 +1,520 @@
1import { typeSignatureFromSchemaJson } from "@executor-v3/codemode-core";
2import {
3 buildOpenApiToolPresentation,
4 compileOpenApiToolDefinitions,
5 extractOpenApiManifest,
6 type OpenApiToolDefinition,
7 type OpenApiToolManifest,
8} from "@executor-v3/codemode-openapi";
9import type {
10 Source,
11 SourceId,
12 SourceInspection,
13 SourceInspectionDiscoverPayload,
14 SourceInspectionDiscoverResult,
15 SourceInspectionDiscoverResultItem,
16 SourceInspectionToolDetail,
17 SourceInspectionToolSummary,
18 StoredSourceRecord,
19 WorkspaceId,
20} from "#schema";
21import {
22 ControlPlaneNotFoundError,
23 ControlPlaneStorageError,
24} from "#api";
25import * as Effect from "effect/Effect";
26import * as Option from "effect/Option";
27
28import { operationErrors } from "./operation-errors";
29import { projectSourceFromStorage } from "./source-definitions";
30import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store";
31import { namespaceFromSourceName } from "./tool-artifacts";
32
33const sourceInspectOps = {
34 bundle: operationErrors("sources.inspect.bundle"),
35 tool: operationErrors("sources.inspect.tool"),
36 discover: operationErrors("sources.inspect.discover"),
37} as const;
38
39const asPrettyJson = (value: unknown): string => JSON.stringify(value, null, 2);
40
41const tokenize = (value: string): Array<string> =>
42 value
43 .trim()
44 .toLowerCase()
45 .split(/[^a-z0-9]+/)
46 .filter(Boolean);
47
48const searchTextFromSummary = (summary: SourceInspectionToolSummary): string =>
49 [
50 summary.path,
51 summary.toolId,
52 summary.rawToolId ?? "",
53 summary.operationId ?? "",
54 summary.title ?? "",
55 summary.description ?? "",
56 summary.method ?? "",
57 summary.pathTemplate ?? "",
58 summary.tags.join(" "),
59 summary.inputType ?? "",
60 summary.outputType ?? "",
61 ]
62 .join(" ")
63 .toLowerCase();
64
65type InspectionToolRecord = {
66 summary: SourceInspectionToolSummary;
67 detail: SourceInspectionToolDetail;
68 searchText: string;
69};
70
71type ResolvedSourceInspection = {
72 source: Source;
73 namespace: string;
74 pipelineKind: SourceInspection["pipelineKind"];
75 rawDocumentText: string | null;
76 manifestJson: string | null;
77 definitionsJson: string | null;
78 tools: ReadonlyArray<InspectionToolRecord>;
79};
80
81const loadSourceRecord = (input: {
82 workspaceId: WorkspaceId;
83 sourceId: SourceId;
84}) =>
85 Effect.flatMap(ControlPlaneStore, (store) =>
86 Effect.gen(function* () {
87 const sourceRecord = yield* sourceInspectOps.bundle.child("record").mapStorage(
88 store.sources.getByWorkspaceAndId(input.workspaceId, input.sourceId),
89 );
90 if (Option.isNone(sourceRecord)) {
91 return yield* Effect.fail(
92 sourceInspectOps.bundle.notFound(
93 "Source not found",
94 `workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,
95 ),
96 );
97 }
98
99 const credentialBinding = yield* sourceInspectOps.bundle.child("binding").mapStorage(
100 store.sourceCredentialBindings.getByWorkspaceAndSourceId(
101 input.workspaceId,
102 input.sourceId,
103 ),
104 );
105
106 const source = yield* projectSourceFromStorage({
107 sourceRecord: sourceRecord.value,
108 credentialBinding: Option.isSome(credentialBinding)
109 ? credentialBinding.value
110 : null,
111 }).pipe(
112 Effect.mapError((cause) =>
113 sourceInspectOps.bundle.unknownStorage(
114 cause,
115 "Failed projecting stored source",
116 ),
117 ),
118 );
119
120 return {
121 store,
122 source,
123 sourceRecord: sourceRecord.value,
124 };
125 }),
126 );
127
128const persistedToolSummaryFromArtifact = (input: {
129 source: Source;
130 artifact: {
131 path: string;
132 toolId: string;
133 title: string | null;
134 description: string | null;
135 providerKind: string;
136 openApiRawToolId: string | null;
137 openApiOperationId: string | null;
138 openApiTagsJson: string | null;
139 openApiMethod: SourceInspectionToolSummary["method"];
140 openApiPathTemplate: string | null;
141 inputSchemaJson: string | null;
142 outputSchemaJson: string | null;
143 };
144}): SourceInspectionToolSummary => ({
145 path: input.artifact.path,
146 sourceKey: input.source.id,
147 ...(input.artifact.title ? { title: input.artifact.title } : {}),
148 ...(input.artifact.description ? { description: input.artifact.description } : {}),
149 providerKind: input.artifact.providerKind,
150 toolId: input.artifact.toolId,
151 rawToolId: input.artifact.openApiRawToolId,
152 operationId: input.artifact.openApiOperationId,
153 group: null,
154 leaf: null,
155 tags: input.artifact.openApiTagsJson
156 ? ((JSON.parse(input.artifact.openApiTagsJson) as Array<string>) ?? [])
157 : [],
158 method: input.artifact.openApiMethod,
159 pathTemplate: input.artifact.openApiPathTemplate,
160 ...(input.artifact.inputSchemaJson
161 ? {
162 inputType: typeSignatureFromSchemaJson(
163 input.artifact.inputSchemaJson,
164 "unknown",
165 320,
166 ),
167 }
168 : {}),
169 ...(input.artifact.outputSchemaJson
170 ? {
171 outputType: typeSignatureFromSchemaJson(
172 input.artifact.outputSchemaJson,
173 "unknown",
174 320,
175 ),
176 }
177 : {}),
178});
179
180const openApiToolRecord = (input: {
181 source: Source;
182 namespace: string;
183 manifest: OpenApiToolManifest;
184 definition: OpenApiToolDefinition;
185}): InspectionToolRecord => {
186 const presentation = buildOpenApiToolPresentation({
187 manifest: input.manifest,
188 definition: input.definition,
189 });
190 const path = `${input.namespace}.${input.definition.toolId}`;
191 const summary: SourceInspectionToolSummary = {
192 path,
193 sourceKey: input.source.id,
194 title: input.definition.name,
195 description: input.definition.description,
196 providerKind: "openapi",
197 toolId: input.definition.toolId,
198 rawToolId: input.definition.rawToolId,
199 operationId: input.definition.operationId ?? null,
200 group: input.definition.group,
201 leaf: input.definition.leaf,
202 tags: [...input.definition.tags],
203 method: input.definition.method,
204 pathTemplate: input.definition.path,
205 inputType: presentation.inputType,
206 outputType: presentation.outputType,
207 };
208
209 return {
210 summary,
211 detail: {
212 summary,
213 definitionJson: asPrettyJson(input.definition),
214 documentationJson: input.definition.documentation
215 ? asPrettyJson(input.definition.documentation)
216 : null,
217 providerDataJson: presentation.providerDataJson,
218 inputSchemaJson: presentation.inputSchemaJson ?? null,
219 outputSchemaJson: presentation.outputSchemaJson ?? null,
220 exampleInputJson: presentation.exampleInputJson ?? null,
221 exampleOutputJson: presentation.exampleOutputJson ?? null,
222 },
223 searchText: searchTextFromSummary(summary),
224 };
225};
226
227const loadPersistedInspection = (input: {
228 store: ControlPlaneStoreShape;
229 source: Source;
230}): Effect.Effect<ResolvedSourceInspection, Error, never> =>
231 Effect.gen(function* () {
232 const artifacts = yield* sourceInspectOps.bundle.child("artifacts").mapStorage(
233 input.store.toolArtifacts.listByWorkspaceId(input.source.workspaceId, {
234 sourceId: input.source.id,
235 limit: 1000,
236 }),
237 );
238 const namespace = input.source.namespace ?? namespaceFromSourceName(input.source.name);
239 const tools = artifacts.map((artifact) => {
240 const summary = persistedToolSummaryFromArtifact({
241 source: input.source,
242 artifact,
243 });
244 return {
245 summary,
246 detail: {
247 summary,
248 definitionJson: null,
249 documentationJson: null,
250 providerDataJson: null,
251 inputSchemaJson: artifact.inputSchemaJson,
252 outputSchemaJson: artifact.outputSchemaJson,
253 exampleInputJson: null,
254 exampleOutputJson: null,
255 },
256 searchText: searchTextFromSummary(summary),
257 } satisfies InspectionToolRecord;
258 });
259
260 return {
261 source: input.source,
262 namespace,
263 pipelineKind: "persisted",
264 rawDocumentText: null,
265 manifestJson: null,
266 definitionsJson: null,
267 tools,
268 } satisfies ResolvedSourceInspection;
269 });
270
271const loadOpenApiInspection = (input: {
272 source: Source;
273 sourceRecord: StoredSourceRecord;
274}): Effect.Effect<ResolvedSourceInspection, Error, never> =>
275 Effect.gen(function* () {
276 const rawDocumentText = input.sourceRecord.sourceDocumentText;
277 if (!rawDocumentText) {
278 return yield* Effect.fail(new Error("Missing stored OpenAPI document"));
279 }
280
281 const manifest = yield* extractOpenApiManifest(
282 input.source.name,
283 rawDocumentText,
284 ).pipe(
285 Effect.mapError((cause) =>
286 cause instanceof Error ? cause : new Error(String(cause)),
287 ),
288 );
289 const definitions = compileOpenApiToolDefinitions(manifest);
290 const namespace = input.source.namespace ?? namespaceFromSourceName(input.source.name);
291 const tools = definitions.map((definition) =>
292 openApiToolRecord({
293 source: input.source,
294 namespace,
295 manifest,
296 definition,
297 }),
298 );
299
300 return {
301 source: input.source,
302 namespace,
303 pipelineKind: "openapi",
304 rawDocumentText,
305 manifestJson: asPrettyJson(manifest),
306 definitionsJson: asPrettyJson(definitions),
307 tools,
308 } satisfies ResolvedSourceInspection;
309 });
310
311const resolveSourceInspection = (input: {
312 workspaceId: WorkspaceId;
313 sourceId: SourceId;
314}) =>
315 Effect.gen(function* () {
316 const { store, source, sourceRecord } = yield* loadSourceRecord(input);
317
318 if (source.kind === "openapi" && sourceRecord.sourceDocumentText) {
319 return yield* loadOpenApiInspection({
320 source,
321 sourceRecord,
322 }).pipe(
323 Effect.catchAll(() =>
324 loadPersistedInspection({
325 store,
326 source,
327 }),
328 ),
329 );
330 }
331
332 return yield* loadPersistedInspection({
333 store,
334 source,
335 });
336 });
337
338const scoreTool = (input: {
339 queryTokens: ReadonlyArray<string>;
340 tool: InspectionToolRecord;
341}): SourceInspectionDiscoverResultItem | null => {
342 let score = 0;
343 const reasons: Array<string> = [];
344 const pathTokens = tokenize(input.tool.summary.path);
345 const titleTokens = tokenize(input.tool.summary.title ?? "");
346 const descriptionTokens = tokenize(input.tool.summary.description ?? "");
347 const tagTokens = input.tool.summary.tags.flatMap(tokenize);
348 const typeTokens = tokenize(
349 `${input.tool.summary.inputType ?? ""} ${input.tool.summary.outputType ?? ""}`,
350 );
351 const methodPathTokens = tokenize(
352 `${input.tool.summary.method ?? ""} ${input.tool.summary.pathTemplate ?? ""}`,
353 );
354
355 for (const token of input.queryTokens) {
356 if (pathTokens.includes(token)) {
357 score += 12;
358 reasons.push(`path matches ${token} (+12)`);
359 continue;
360 }
361 if (tagTokens.includes(token)) {
362 score += 10;
363 reasons.push(`tag matches ${token} (+10)`);
364 continue;
365 }
366 if (titleTokens.includes(token)) {
367 score += 8;
368 reasons.push(`title matches ${token} (+8)`);
369 continue;
370 }
371 if (methodPathTokens.includes(token)) {
372 score += 6;
373 reasons.push(`method/path matches ${token} (+6)`);
374 continue;
375 }
376 if (typeTokens.includes(token)) {
377 score += 4;
378 reasons.push(`type signature matches ${token} (+4)`);
379 continue;
380 }
381 if (descriptionTokens.includes(token) || input.tool.searchText.includes(token)) {
382 score += 2;
383 reasons.push(`description/text matches ${token} (+2)`);
384 }
385 }
386
387 if (score <= 0) {
388 return null;
389 }
390
391 return {
392 path: input.tool.summary.path,
393 score,
394 ...(input.tool.summary.description
395 ? { description: input.tool.summary.description }
396 : {}),
397 ...(input.tool.summary.inputType
398 ? { inputType: input.tool.summary.inputType }
399 : {}),
400 ...(input.tool.summary.outputType
401 ? { outputType: input.tool.summary.outputType }
402 : {}),
403 reasons,
404 } satisfies SourceInspectionDiscoverResultItem;
405};
406
407const mapInspectionError = (
408 operation:
409 | typeof sourceInspectOps.bundle
410 | typeof sourceInspectOps.tool
411 | typeof sourceInspectOps.discover,
412 cause: unknown,
413 details: string,
414): ControlPlaneNotFoundError | ControlPlaneStorageError => {
415 if (cause instanceof ControlPlaneNotFoundError) {
416 return cause;
417 }
418 if (cause instanceof ControlPlaneStorageError) {
419 return cause;
420 }
421
422 return operation.unknownStorage(cause, details);
423};
424
425export const getSourceInspection = (input: {
426 workspaceId: WorkspaceId;
427 sourceId: SourceId;
428}) =>
429 Effect.gen(function* () {
430 const inspection = yield* resolveSourceInspection(input);
431
432 return {
433 source: inspection.source,
434 namespace: inspection.namespace,
435 pipelineKind: inspection.pipelineKind,
436 toolCount: inspection.tools.length,
437 rawDocumentText: inspection.rawDocumentText,
438 manifestJson: inspection.manifestJson,
439 definitionsJson: inspection.definitionsJson,
440 tools: inspection.tools.map((tool) => tool.summary),
441 } satisfies SourceInspection;
442 }).pipe(
443 Effect.mapError((cause) =>
444 mapInspectionError(
445 sourceInspectOps.bundle,
446 cause,
447 "Failed building source inspection bundle",
448 )),
449 );
450
451export const getSourceInspectionToolDetail = (input: {
452 workspaceId: WorkspaceId;
453 sourceId: SourceId;
454 toolPath: string;
455}) =>
456 Effect.gen(function* () {
457 const inspection = yield* resolveSourceInspection({
458 workspaceId: input.workspaceId,
459 sourceId: input.sourceId,
460 });
461 const tool = inspection.tools.find((candidate) => candidate.summary.path === input.toolPath);
462
463 if (!tool) {
464 return yield* Effect.fail(
465 sourceInspectOps.tool.notFound(
466 "Tool not found",
467 `workspaceId=${input.workspaceId} sourceId=${input.sourceId} path=${input.toolPath}`,
468 ),
469 );
470 }
471
472 return tool.detail;
473 }).pipe(
474 Effect.mapError((cause) =>
475 mapInspectionError(
476 sourceInspectOps.tool,
477 cause,
478 "Failed building source inspection tool detail",
479 )),
480 );
481
482export const discoverSourceInspectionTools = (input: {
483 workspaceId: WorkspaceId;
484 sourceId: SourceId;
485 payload: SourceInspectionDiscoverPayload;
486}) =>
487 Effect.gen(function* () {
488 const inspection = yield* resolveSourceInspection({
489 workspaceId: input.workspaceId,
490 sourceId: input.sourceId,
491 });
492 const queryTokens = tokenize(input.payload.query);
493 const results = inspection.tools
494 .map((tool) =>
495 scoreTool({
496 queryTokens,
497 tool,
498 }),
499 )
500 .filter((value): value is SourceInspectionDiscoverResultItem => value !== null)
501 .sort((left, right) =>
502 right.score - left.score || left.path.localeCompare(right.path),
503 )
504 .slice(0, input.payload.limit ?? 12);
505
506 return {
507 query: input.payload.query,
508 queryTokens,
509 bestPath: results[0]?.path ?? null,
510 total: results.length,
511 results,
512 } satisfies SourceInspectionDiscoverResult;
513 }).pipe(
514 Effect.mapError((cause) =>
515 mapInspectionError(
516 sourceInspectOps.discover,
517 cause,
518 "Failed building source inspection discovery",
519 )),
520 );
packages/control-plane/src/runtime/sources-operations.ts+4−2
@@ -87,10 +87,11 @@ const syncArtifactsForSource = (input: { · 86 unmodified lines
87 const { sourceRecord } = splitSourceForStorage({87 const { sourceRecord } = splitSourceForStorage({
88 source: erroredSource,88 source: erroredSource,
89 });89 });
90 const { sourceDocumentText: _sourceDocumentText, ...sourcePatch } = sourceRecord;
90 yield* mapPersistenceError(91 yield* mapPersistenceError(
91 input.operation.child("source_error"),92 input.operation.child("source_error"),
92 input.store.sources.update(input.source.workspaceId, input.source.id, {93 input.store.sources.update(input.source.workspaceId, input.source.id, {
93 ...sourceRecord,94 ...sourcePatch,
94 updatedAt: erroredSource.updatedAt,95 updatedAt: erroredSource.updatedAt,
95 }),96 }),
96 );97 );
@@ -264,11 +265,12 @@ export const updateSource = (input: { · 167 unmodified lines
264 const { sourceRecord, credentialBinding } = splitSourceForStorage({265 const { sourceRecord, credentialBinding } = splitSourceForStorage({
265 source: updatedSource,266 source: updatedSource,
266 });267 });
268 const { sourceDocumentText: _sourceDocumentText, ...sourcePatch } = sourceRecord;
267 269
268 const stored = yield* mapPersistenceError(270 const stored = yield* mapPersistenceError(
269 sourceOps.update.child("source"),271 sourceOps.update.child("source"),
270 store.sources.update(input.workspaceId, input.sourceId, {272 store.sources.update(input.workspaceId, input.sourceId, {
271 ...sourceRecord,273 ...sourcePatch,
272 updatedAt: updatedSource.updatedAt,274 updatedAt: updatedSource.updatedAt,
273 }),275 }),
274 );276 );
packages/control-plane/src/runtime/tool-artifacts.ts+28−80
@@ -8,10 +8,7 @@ import { · 7 unmodified lines
8 discoverMcpToolsFromConnector,8 discoverMcpToolsFromConnector,
9 type McpToolManifestEntry,9 type McpToolManifestEntry,
10} from "@executor-v3/codemode-mcp";10} from "@executor-v3/codemode-mcp";
11import {11import { extractOpenApiManifest } from "@executor-v3/codemode-openapi";
12 extractOpenApiManifest,
13 type OpenApiExtractedTool,
14} from "@executor-v3/codemode-openapi";
15import type { SqlControlPlaneRows } from "#persistence";12import type { SqlControlPlaneRows } from "#persistence";
16import {13import {
17 type SecretRef,14 type SecretRef,
@@ -22,6 +19,7 @@ import { · 4 unmodified lines
22 type StoredToolArtifactRequestBodyContentTypeRecord,19 type StoredToolArtifactRequestBodyContentTypeRecord,
23} from "#schema";20} from "#schema";
24import * as Effect from "effect/Effect";21import * as Effect from "effect/Effect";
22import * as Option from "effect/Option";
25 23
26export type ResolveSourceSecretMaterial = (24export type ResolveSourceSecretMaterial = (
27 ref: SecretRef,25 ref: SecretRef,
@@ -98,6 +96,9 @@ const toMcpToolArtifactRecord = (input: { · 70 unmodified lines
98 openApiMethod: null,96 openApiMethod: null,
99 openApiPathTemplate: null,97 openApiPathTemplate: null,
100 openApiOperationHash: null,98 openApiOperationHash: null,
99 openApiRawToolId: null,
100 openApiOperationId: null,
101 openApiTagsJson: null,
101 openApiRequestBodyRequired: null,102 openApiRequestBodyRequired: null,
102 createdAt: input.now,103 createdAt: input.now,
103 updatedAt: input.now,104 updatedAt: input.now,
@@ -105,71 +106,6 @@ const toMcpToolArtifactRecord = (input: { · 1 unmodified line
105 };106 };
106};107};
107 108
108const toOpenApiToolArtifactRecord = (input: {
109 workspaceId: Source["workspaceId"];
110 source: Source;
111 extracted: OpenApiExtractedTool;
112 now: number;
113}): IndexedToolArtifactRecord => {
114 const sourceNamespace = input.source.namespace ?? namespaceFromSourceName(input.source.name);
115 const path = joinToolPath(sourceNamespace, input.extracted.toolId);
116 const searchNamespace = catalogNamespaceFromPath(path);
117 const description =
118 input.extracted.description
119 ?? `${input.extracted.method.toUpperCase()} ${input.extracted.path}`;
120
121 return {
122 artifact: {
123 workspaceId: input.workspaceId,
124 path,
125 toolId: input.extracted.toolId,
126 sourceId: input.source.id,
127 title: input.extracted.name,
128 description,
129 searchNamespace,
130 searchText: normalizeSearchText(
131 path,
132 searchNamespace,
133 input.extracted.name,
134 description,
135 input.extracted.method.toUpperCase(),
136 input.extracted.path,
137 ),
138 inputSchemaJson: input.extracted.typing?.inputSchemaJson ?? null,
139 outputSchemaJson: input.extracted.typing?.outputSchemaJson ?? null,
140 providerKind: "openapi",
141 mcpToolName: null,
142 openApiMethod: input.extracted.method,
143 openApiPathTemplate: input.extracted.invocation.pathTemplate,
144 openApiOperationHash: input.extracted.operationHash,
145 openApiRequestBodyRequired: input.extracted.invocation.requestBody?.required ?? null,
146 createdAt: input.now,
147 updatedAt: input.now,
148 },
149 parameters: input.extracted.invocation.parameters.map((parameter, position) => ({
150 workspaceId: input.workspaceId,
151 path,
152 position,
153 name: parameter.name,
154 location: parameter.location,
155 required: parameter.required,
156 })),
157 requestBodyContentTypes:
158 input.extracted.invocation.requestBody?.contentTypes.map((contentType, position) => ({
159 workspaceId: input.workspaceId,
160 path,
161 position,
162 contentType,
163 })) ?? [],
164 refHintKeys:
165 input.extracted.typing?.refHintKeys?.map((refHintKey, position) => ({
166 workspaceId: input.workspaceId,
167 path,
168 position,
169 refHintKey,
170 })) ?? [],
171 };
172};
173 109
174const shouldIndexSource = (source: Source): boolean =>110const shouldIndexSource = (source: Source): boolean =>
175 source.enabled111 source.enabled
@@ -358,18 +294,30 @@ const indexOpenApiSourceToolArtifacts = (input: { · 182 unmodified lines
358 );294 );
359 295
360 const now = Date.now();296 const now = Date.now();
361 yield* input.rows.toolArtifacts.replaceForSource({297 const updatedSource = yield* input.rows.sources.update(
362 workspaceId: input.source.workspaceId,298 input.source.workspaceId,
363 sourceId: input.source.id,299 input.source.id,
364 artifacts: manifest.tools.map((extracted) =>300 {
365 toOpenApiToolArtifactRecord({301 sourceHash: manifest.sourceHash,
366 workspaceId: input.source.workspaceId,302 sourceDocumentText: openApiDocument,
367 source: input.source,303 updatedAt: now,
368 extracted,304 },
369 now,305 ).pipe(
370 })306 Effect.mapError((cause) =>
307 cause instanceof Error ? cause : new Error(String(cause)),
371 ),308 ),
372 }).pipe(309 );
310
311 if (Option.isNone(updatedSource)) {
312 return yield* Effect.fail(
313 new Error(`Source disappeared while storing OpenAPI document for ${input.source.id}`),
314 );
315 }
316
317 yield* input.rows.toolArtifacts.removeByWorkspaceAndSourceId(
318 input.source.workspaceId,
319 input.source.id,
320 ).pipe(
373 Effect.mapError((cause) =>321 Effect.mapError((cause) =>
374 cause instanceof Error ? cause : new Error(String(cause)),322 cause instanceof Error ? cause : new Error(String(cause)),
375 ),323 ),
packages/control-plane/src/runtime/workspace-execution-environment.test.ts+101−94
@@ -25,6 +25,7 @@ import * as Effect from "effect/Effect"; · 24 unmodified lines
25import * as Exit from "effect/Exit";25import * as Exit from "effect/Exit";
26import * as Layer from "effect/Layer";26import * as Layer from "effect/Layer";
27import * as Scope from "effect/Scope";27import * as Scope from "effect/Scope";
28import * as Option from "effect/Option";
28import { Schema } from "effect";29import { Schema } from "effect";
29import { z } from "zod/v4";30import { z } from "zod/v4";
30 31
@@ -372,6 +373,7 @@ const persistConnectedEchoTool = (input: { · 341 unmodified lines
372 authHeaderName: null,373 authHeaderName: null,
373 authPrefix: null,374 authPrefix: null,
374 sourceHash: null,375 sourceHash: null,
376 sourceDocumentText: null,
375 lastError: null,377 lastError: null,
376 createdAt: now,378 createdAt: now,
377 updatedAt: now,379 updatedAt: now,
@@ -406,6 +408,9 @@ const persistConnectedEchoTool = (input: { · 28 unmodified lines
406 openApiMethod: null,408 openApiMethod: null,
407 openApiPathTemplate: null,409 openApiPathTemplate: null,
408 openApiOperationHash: null,410 openApiOperationHash: null,
411 openApiRawToolId: null,
412 openApiOperationId: null,
413 openApiTagsJson: null,
409 openApiRequestBodyRequired: null,414 openApiRequestBodyRequired: null,
410 createdAt: now,415 createdAt: now,
411 updatedAt: now,416 updatedAt: now,
@@ -414,13 +419,89 @@ const persistConnectedEchoTool = (input: { · 2 unmodified lines
414 });419 });
415 });420 });
416 421
417const persistConnectedGithubSearchArtifacts = (input: {422const persistConnectedGithubOpenApiSource = (input: {
418 persistence: SqlControlPlanePersistence;423 persistence: SqlControlPlanePersistence;
419 workspaceId: WorkspaceId;424 workspaceId: WorkspaceId;
420 sourceId: SourceId;425 sourceId: SourceId;
421}) =>426}) =>
422 Effect.gen(function* () {427 Effect.gen(function* () {
423 const now = Date.now();428 const now = Date.now();
429 const openApiDocumentText = JSON.stringify({
430 openapi: "3.0.3",
431 info: {
432 title: "GitHub",
433 version: "1.0.0",
434 },
435 paths: {
436 "/repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}": {
437 get: {
438 operationId: "actions/get-environment-secret",
439 tags: ["actions"],
440 summary: "Get an environment secret",
441 description:
442 "Gets a single environment secret without revealing its encrypted value. Authenticated users must have collaborator access to a repository to create, update, or read secrets.",
443 parameters: [
444 { name: "owner", in: "path", required: true, schema: { type: "string" } },
445 { name: "repo", in: "path", required: true, schema: { type: "string" } },
446 { name: "environment_name", in: "path", required: true, schema: { type: "string" } },
447 { name: "secret_name", in: "path", required: true, schema: { type: "string" } },
448 ],
449 responses: {
450 200: {
451 description: "ok",
452 content: {
453 "application/json": {
454 schema: { type: "object" },
455 },
456 },
457 },
458 },
459 },
460 },
461 "/orgs/{org}/actions/runners/{runner_id}": {
462 get: {
463 operationId: "actions/get-self-hosted-runner-for-org",
464 tags: ["actions"],
465 summary: "Get a self-hosted runner for an organization",
466 description:
467 "Gets a specific self-hosted runner configured in an organization. Authenticated users must have admin access to the organization to use this endpoint.",
468 parameters: [
469 { name: "org", in: "path", required: true, schema: { type: "string" } },
470 { name: "runner_id", in: "path", required: true, schema: { type: "integer" } },
471 ],
472 responses: {
473 200: {
474 description: "ok",
475 content: {
476 "application/json": {
477 schema: { type: "object" },
478 },
479 },
480 },
481 },
482 },
483 },
484 "/user": {
485 get: {
486 operationId: "users/get-authenticated",
487 tags: ["users"],
488 summary: "Get the authenticated user",
489 description:
490 "Gets the authenticated user. OAuth app tokens and personal access tokens need the user scope to include private profile information.",
491 responses: {
492 200: {
493 description: "ok",
494 content: {
495 "application/json": {
496 schema: { type: "object" },
497 },
498 },
499 },
500 },
501 },
502 },
503 },
504 });
424 505
425 yield* input.persistence.rows.sources.insert({506 yield* input.persistence.rows.sources.insert({
426 id: input.sourceId,507 id: input.sourceId,
@@ -440,89 +521,11 @@ const persistConnectedGithubSearchArtifacts = (input: { · 13 unmodified lines
440 authHeaderName: null,521 authHeaderName: null,
441 authPrefix: null,522 authPrefix: null,
442 sourceHash: null,523 sourceHash: null,
524 sourceDocumentText: openApiDocumentText,
443 lastError: null,525 lastError: null,
444 createdAt: now,526 createdAt: now,
445 updatedAt: now,527 updatedAt: now,
446 });528 });
447
448 yield* input.persistence.rows.toolArtifacts.replaceForSource({
449 workspaceId: input.workspaceId,
450 sourceId: input.sourceId,
451 artifacts: [
452 {
453 artifact: {
454 workspaceId: input.workspaceId,
455 path: "github.actions/get-environment-secret",
456 toolId: "actions/get-environment-secret",
457 sourceId: input.sourceId,
458 title: "Get an environment secret",
459 description:
460 "Gets a single environment secret without revealing its encrypted value. Authenticated users must have collaborator access to a repository to create, update, or read secrets.",
461 searchNamespace: "github.actions",
462 searchText:
463 "github.actions/get-environment-secret github.actions get an environment secret gets a single environment secret without revealing its encrypted value authenticated users must have collaborator access to a repository to create update or read secrets GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}",
464 inputSchemaJson: null,
465 outputSchemaJson: null,
466 providerKind: "openapi",
467 mcpToolName: null,
468 openApiMethod: "get",
469 openApiPathTemplate: "/repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}",
470 openApiOperationHash: "op_actions_get_environment_secret",
471 openApiRequestBodyRequired: null,
472 createdAt: now,
473 updatedAt: now,
474 },
475 },
476 {
477 artifact: {
478 workspaceId: input.workspaceId,
479 path: "github.actions/get-self-hosted-runner-for-org",
480 toolId: "actions/get-self-hosted-runner-for-org",
481 sourceId: input.sourceId,
482 title: "Get a self-hosted runner for an organization",
483 description:
484 "Gets a specific self-hosted runner configured in an organization. Authenticated users must have admin access to the organization to use this endpoint.",
485 searchNamespace: "github.actions",
486 searchText:
487 "github.actions/get-self-hosted-runner-for-org github.actions get a self hosted runner for an organization gets a specific self hosted runner configured in an organization authenticated users must have admin access to the organization to use this endpoint GET /orgs/{org}/actions/runners/{runner_id}",
488 inputSchemaJson: null,
489 outputSchemaJson: null,
490 providerKind: "openapi",
491 mcpToolName: null,
492 openApiMethod: "get",
493 openApiPathTemplate: "/orgs/{org}/actions/runners/{runner_id}",
494 openApiOperationHash: "op_actions_get_self_hosted_runner_for_org",
495 openApiRequestBodyRequired: null,
496 createdAt: now,
497 updatedAt: now,
498 },
499 },
500 {
501 artifact: {
502 workspaceId: input.workspaceId,
503 path: "github.users/get-authenticated",
504 toolId: "users/get-authenticated",
505 sourceId: input.sourceId,
506 title: "Get the authenticated user",
507 description:
508 "Gets the authenticated user. OAuth app tokens and personal access tokens need the user scope to include private profile information.",
509 searchNamespace: "github.users",
510 searchText:
511 "github.users/get-authenticated github.users get the authenticated user gets the authenticated user oauth app tokens and personal access tokens need the user scope to include private profile information GET /user",
512 inputSchemaJson: null,
513 outputSchemaJson: null,
514 providerKind: "openapi",
515 mcpToolName: null,
516 openApiMethod: "get",
517 openApiPathTemplate: "/user",
518 openApiOperationHash: "op_users_get_authenticated",
519 openApiRequestBodyRequired: null,
520 createdAt: now,
521 updatedAt: now,
522 },
523 },
524 ],
525 });
526 });529 });
527 530
528const makeResolver = (persistence: SqlControlPlanePersistence) =>531const makeResolver = (persistence: SqlControlPlanePersistence) =>
@@ -597,7 +600,7 @@ describe("workspace-execution-environment", () => { · 68 unmodified lines
597 const persistence = yield* makePersistence;600 const persistence = yield* makePersistence;
598 const workspaceId = WorkspaceIdSchema.make("ws_github_discovery");601 const workspaceId = WorkspaceIdSchema.make("ws_github_discovery");
599 const sourceId = SourceIdSchema.make("src_github_discovery");602 const sourceId = SourceIdSchema.make("src_github_discovery");
600 yield* persistConnectedGithubSearchArtifacts({603 yield* persistConnectedGithubOpenApiSource({
601 persistence,604 persistence,
602 workspaceId,605 workspaceId,
603 sourceId,606 sourceId,
@@ -613,7 +616,7 @@ describe("workspace-execution-environment", () => { · 9 unmodified lines
613 const firstQuery = (yield* environment.toolInvoker.invoke({616 const firstQuery = (yield* environment.toolInvoker.invoke({
614 path: "discover",617 path: "discover",
615 args: {618 args: {
616 query: "get authenticated user",619 query: "current authenticated user profile",
617 limit: 5,620 limit: 5,
618 },621 },
619 })) as {622 })) as {
@@ -621,8 +624,8 @@ describe("workspace-execution-environment", () => { · 1 unmodified line
621 results: Array<{ path: string }>;624 results: Array<{ path: string }>;
622 };625 };
623 626
624 expect(firstQuery.bestPath).toBe("github.users/get-authenticated");627 expect(firstQuery.bestPath).toBe("github.users.getAuthenticated");
625 expect(firstQuery.results[0]?.path).toBe("github.users/get-authenticated");628 expect(firstQuery.results[0]?.path).toBe("github.users.getAuthenticated");
626 629
627 const secondQuery = (yield* environment.toolInvoker.invoke({630 const secondQuery = (yield* environment.toolInvoker.invoke({
628 path: "discover",631 path: "discover",
@@ -635,8 +638,8 @@ describe("workspace-execution-environment", () => { · 6 unmodified lines
635 results: Array<{ path: string }>;638 results: Array<{ path: string }>;
636 };639 };
637 640
638 expect(secondQuery.bestPath).toBe("github.users/get-authenticated");641 expect(secondQuery.bestPath).toBe("github.users.getAuthenticated");
639 expect(secondQuery.results[0]?.path).toBe("github.users/get-authenticated");642 expect(secondQuery.results[0]?.path).toBe("github.users.getAuthenticated");
640 }),643 }),
641 );644 );
642 645
@@ -735,6 +738,7 @@ describe("workspace-execution-environment", () => { · 92 unmodified lines
735 runId: "exec_add_openapi",738 runId: "exec_add_openapi",
736 },739 },
737 })) as {740 })) as {
741 id: SourceId;
738 kind: string;742 kind: string;
739 status: string;743 status: string;
740 auth: {744 auth: {
@@ -758,11 +762,14 @@ describe("workspace-execution-environment", () => { · 17 unmodified lines
758 encodeURIComponent("exec_add_openapi:executor.sources.add:"),762 encodeURIComponent("exec_add_openapi:executor.sources.add:"),
759 );763 );
760 764
761 const storedArtifacts = yield* persistence.rows.toolArtifacts.listByWorkspaceId(765 const storedSource = yield* persistence.rows.sources.getByWorkspaceAndId(
762 workspaceId,766 workspaceId,
767 added.id,
763 );768 );
764 expect(storedArtifacts.length).toBeGreaterThan(0);769 expect(Option.isSome(storedSource)).toBe(true);
765 const repoToolPath = storedArtifacts[0]!.path;770 if (Option.isSome(storedSource)) {
771 expect(storedSource.value.sourceDocumentText).toContain('"operationId":"repos.getRepo"');
772 }
766 773
767 const freshEnvironment = yield* resolveEnvironment({774 const freshEnvironment = yield* resolveEnvironment({
768 workspaceId,775 workspaceId,
@@ -780,10 +787,10 @@ describe("workspace-execution-environment", () => { · 11 unmodified lines
780 bestPath: string | null;787 bestPath: string | null;
781 };788 };
782 789
783 expect(discovered.bestPath).toBe(repoToolPath);790 expect(discovered.bestPath).toBe("github.repos.getRepo");
784 791
785 const invoked = (yield* freshEnvironment.toolInvoker.invoke({792 const invoked = (yield* freshEnvironment.toolInvoker.invoke({
786 path: repoToolPath,793 path: "github.repos.getRepo",
787 args: {794 args: {
788 owner: "vercel",795 owner: "vercel",
789 repo: "ai",796 repo: "ai",
@@ -825,7 +832,7 @@ describe("workspace-execution-environment", () => { · 35 unmodified lines
825 })) as {832 })) as {
826 path: string;833 path: string;
827 description?: string;834 description?: string;
828 inputHint?: string;835 inputType?: string;
829 inputSchemaJson?: string;836 inputSchemaJson?: string;
830 } | null;837 } | null;
831 838
@@ -833,8 +840,8 @@ describe("workspace-execution-environment", () => { · 1 unmodified line
833 expect(described?.description).toContain("Source add input shapes:");840 expect(described?.description).toContain("Source add input shapes:");
834 expect(described?.description).toContain("specUrl");841 expect(described?.description).toContain("specUrl");
835 expect(described?.description).toContain("credential setup");842 expect(described?.description).toContain("credential setup");
836 expect(described?.inputHint).toContain("endpoint");843 expect(described?.inputType).toContain("endpoint");
837 expect(described?.inputHint).not.toContain("auth");844 expect(described?.inputType).not.toContain("auth");
838 845
839 const inputSchema = described?.inputSchemaJson846 const inputSchema = described?.inputSchemaJson
840 ? JSON.parse(described.inputSchemaJson) as Record<string, unknown>847 ? JSON.parse(described.inputSchemaJson) as Record<string, unknown>
packages/control-plane/src/runtime/workspace-execution-environment.ts+249−125
@@ -8,13 +8,19 @@ import { · 7 unmodified lines
8 type ToolInvoker,8 type ToolInvoker,
9 type ToolNamespace,9 type ToolNamespace,
10 type ToolPath,10 type ToolPath,
11 typeSignatureFromSchemaJson,
11} from "@executor-v3/codemode-core";12} from "@executor-v3/codemode-core";
12import {13import {
13 createSdkMcpConnector,14 createSdkMcpConnector,
14 createMcpToolsFromManifest,15 createMcpToolsFromManifest,
15} from "@executor-v3/codemode-mcp";16} from "@executor-v3/codemode-mcp";
16import {17import {
18 buildOpenApiToolPresentation,
19 compileOpenApiToolDefinitions,
17 createOpenApiToolsFromManifest,20 createOpenApiToolsFromManifest,
21 extractOpenApiManifest,
22 type OpenApiToolDefinition,
23 type OpenApiToolManifest,
18} from "@executor-v3/codemode-openapi";24} from "@executor-v3/codemode-openapi";
19import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";25import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc";
20import {26import {
@@ -35,7 +41,7 @@ import type { · 14 unmodified lines
35 ResolveExecutionEnvironment,41 ResolveExecutionEnvironment,
36} from "./execution-state";42} from "./execution-state";
37import { createExecutorToolMap } from "./executor-tools";43import { createExecutorToolMap } from "./executor-tools";
38import { projectSourceFromStorage } from "./source-definitions";44import { projectSourceFromStorage, projectSourcesFromStorage } from "./source-definitions";
39import {45import {
40 RuntimeSourceAuthServiceTag,46 RuntimeSourceAuthServiceTag,
41 createDbBackedSecretMaterialResolver,47 createDbBackedSecretMaterialResolver,
@@ -58,6 +64,7 @@ const tokenize = (value: string): string[] => · 16 unmodified lines
58 .split(/[^a-z0-9]+/)64 .split(/[^a-z0-9]+/)
59 .filter(Boolean);65 .filter(Boolean);
60 66
67
61const LOW_SIGNAL_QUERY_TOKENS = new Set([68const LOW_SIGNAL_QUERY_TOKENS = new Set([
62 "a",69 "a",
63 "an",70 "an",
@@ -207,22 +214,153 @@ const scoreArtifact = ( · 143 unmodified lines
207 return score;214 return score;
208};215};
209 216
210const toDescriptor = (input: {217const catalogNamespaceFromPath = (path: string): string => {
218 const [first, second] = path.split(".");
219 return second ? `${first}.${second}` : first;
220};
221
222const toPersistedDescriptor = (input: {
211 artifact: StoredToolArtifactRecord;223 artifact: StoredToolArtifactRecord;
212 includeSchemas: boolean;224 includeSchemas: boolean;
213 refHintKeys?: readonly string[];
214}): ToolDescriptor => ({225}): ToolDescriptor => ({
215 path: asToolPath(input.artifact.path),226 path: asToolPath(input.artifact.path),
216 sourceKey: input.artifact.sourceId,227 sourceKey: input.artifact.sourceId,
217 description: input.artifact.description ?? input.artifact.title ?? undefined,228 description: input.artifact.description ?? input.artifact.title ?? undefined,
218 interaction: "auto",229 interaction: "auto",
219 inputHint: input.artifact.inputSchemaJson ? "object" : undefined,230 inputType: typeSignatureFromSchemaJson(
220 outputHint: input.artifact.outputSchemaJson ? "output" : undefined,231 input.artifact.inputSchemaJson ?? undefined,
232 "unknown",
233 320,
234 ),
235 outputType: typeSignatureFromSchemaJson(
236 input.artifact.outputSchemaJson ?? undefined,
237 "unknown",
238 320,
239 ),
221 inputSchemaJson: input.includeSchemas ? input.artifact.inputSchemaJson ?? undefined : undefined,240 inputSchemaJson: input.includeSchemas ? input.artifact.inputSchemaJson ?? undefined : undefined,
222 outputSchemaJson: input.includeSchemas ? input.artifact.outputSchemaJson ?? undefined : undefined,241 outputSchemaJson: input.includeSchemas ? input.artifact.outputSchemaJson ?? undefined : undefined,
223 refHintKeys: input.includeSchemas ? input.refHintKeys : undefined,242 ...(input.artifact.providerKind ? { providerKind: input.artifact.providerKind } : {}),
224});243});
225 244
245type OpenApiWorkspaceTool = {
246 path: ToolPath;
247 source: Source;
248 manifest: OpenApiToolManifest;
249 definition: OpenApiToolDefinition;
250 descriptor: ToolDescriptor;
251 searchNamespace: string;
252 searchText: string;
253};
254
255const loadOpenApiWorkspaceTools = (input: {
256 rows: SqlControlPlaneRows;
257 workspaceId: Source["workspaceId"];
258 includeSchemas: boolean;
259}): Effect.Effect<ReadonlyArray<OpenApiWorkspaceTool>, Error, never> =>
260 Effect.gen(function* () {
261 const [sourceRecords, credentialBindings] = yield* Effect.all([
262 input.rows.sources.listByWorkspaceId(input.workspaceId),
263 input.rows.sourceCredentialBindings.listByWorkspaceId(input.workspaceId),
264 ]).pipe(
265 Effect.mapError((cause) =>
266 cause instanceof Error ? cause : new Error(String(cause)),
267 ),
268 );
269
270 const documentBySourceId = new Map(
271 sourceRecords.map((record) => [record.id, record.sourceDocumentText]),
272 );
273 const sources = yield* projectSourcesFromStorage({
274 sourceRecords,
275 credentialBindings,
276 });
277
278 const connectedOpenApiSources = sources.filter((source) =>
279 source.enabled
280 && source.status === "connected"
281 && source.kind === "openapi"
282 && typeof documentBySourceId.get(source.id) === "string"
283 && documentBySourceId.get(source.id)!.length > 0,
284 );
285
286 const toolGroups = yield* Effect.forEach(
287 connectedOpenApiSources,
288 (source) =>
289 Effect.gen(function* () {
290 const openApiDocument = documentBySourceId.get(source.id)!;
291 const manifest = yield* extractOpenApiManifest(
292 source.name,
293 openApiDocument,
294 ).pipe(
295 Effect.mapError((cause) =>
296 cause instanceof Error ? cause : new Error(String(cause)),
297 ),
298 );
299 const definitions = compileOpenApiToolDefinitions(manifest);
300 const namespace = source.namespace ?? namespaceFromSourceName(source.name);
301
302 return definitions.map((definition) => {
303 const presentation = buildOpenApiToolPresentation({
304 manifest,
305 definition,
306 });
307 const path = asToolPath(
308 namespace ? `${namespace}.${definition.toolId}` : definition.toolId,
309 );
310 const searchNamespace = catalogNamespaceFromPath(path);
311 const searchText = [
312 path,
313 searchNamespace,
314 definition.name,
315 definition.description,
316 definition.rawToolId,
317 definition.tags.join(" "),
318 definition.method.toUpperCase(),
319 definition.path,
320 ]
321 .filter((part): part is string => typeof part === "string" && part.length > 0)
322 .join(" ")
323 .toLowerCase();
324
325 return {
326 path,
327 source,
328 manifest,
329 definition,
330 descriptor: {
331 path,
332 sourceKey: source.id,
333 description: definition.description,
334 interaction: "auto",
335 inputType: presentation.inputType,
336 outputType: presentation.outputType,
337 ...(input.includeSchemas && presentation.inputSchemaJson
338 ? { inputSchemaJson: presentation.inputSchemaJson }
339 : {}),
340 ...(input.includeSchemas && presentation.outputSchemaJson
341 ? { outputSchemaJson: presentation.outputSchemaJson }
342 : {}),
343 ...(presentation.exampleInputJson
344 ? { exampleInputJson: presentation.exampleInputJson }
345 : {}),
346 ...(presentation.exampleOutputJson
347 ? { exampleOutputJson: presentation.exampleOutputJson }
348 : {}),
349 providerKind: "openapi",
350 providerDataJson: presentation.providerDataJson,
351 } satisfies ToolDescriptor,
352 searchNamespace,
353 searchText,
354 } satisfies OpenApiWorkspaceTool;
355 });
356 }),
357 { concurrency: "unbounded" },
358 );
359
360 return toolGroups.flat();
361 });
362
363
226const loadSourceById = (input: {364const loadSourceById = (input: {
227 rows: SqlControlPlaneRows;365 rows: SqlControlPlaneRows;
228 workspaceId: Source["workspaceId"];366 workspaceId: Source["workspaceId"];
@@ -265,7 +403,7 @@ const createWorkspaceToolCatalog = (input: { · 36 unmodified lines
265}): ToolCatalog => ({403}): ToolCatalog => ({
266 listNamespaces: ({ limit }) =>404 listNamespaces: ({ limit }) =>
267 Effect.gen(function* () {405 Effect.gen(function* () {
268 const [persisted, executor] = yield* Effect.all([406 const [persisted, openApiTools, executor] = yield* Effect.all([
269 input.rows.toolArtifacts.listNamespacesByWorkspaceId(input.workspaceId, {407 input.rows.toolArtifacts.listNamespacesByWorkspaceId(input.workspaceId, {
270 limit,408 limit,
271 }).pipe(409 }).pipe(
@@ -273,6 +411,11 @@ const createWorkspaceToolCatalog = (input: { · 1 unmodified line
273 cause instanceof Error ? cause : new Error(String(cause)),411 cause instanceof Error ? cause : new Error(String(cause)),
274 ),412 ),
275 ),413 ),
414 loadOpenApiWorkspaceTools({
415 rows: input.rows,
416 workspaceId: input.workspaceId,
417 includeSchemas: false,
418 }),
276 input.executorCatalog.listNamespaces({ limit }),419 input.executorCatalog.listNamespaces({ limit }),
277 ]);420 ]);
278 421
@@ -280,6 +423,13 @@ const createWorkspaceToolCatalog = (input: { · 1 unmodified line
280 for (const namespace of persisted) {423 for (const namespace of persisted) {
281 merged.set(namespace.namespace, namespace);424 merged.set(namespace.namespace, namespace);
282 }425 }
426 for (const tool of openApiTools) {
427 const existing = merged.get(tool.searchNamespace);
428 merged.set(tool.searchNamespace, {
429 namespace: tool.searchNamespace,
430 toolCount: (existing?.toolCount ?? 0) + 1,
431 });
432 }
283 for (const namespace of executor) {433 for (const namespace of executor) {
284 const existing = merged.get(namespace.namespace);434 const existing = merged.get(namespace.namespace);
285 merged.set(namespace.namespace, {435 merged.set(namespace.namespace, {
@@ -299,7 +449,7 @@ const createWorkspaceToolCatalog = (input: { · 13 unmodified lines
299 449
300 listTools: ({ namespace, query, limit, includeSchemas = false }) =>450 listTools: ({ namespace, query, limit, includeSchemas = false }) =>
301 Effect.gen(function* () {451 Effect.gen(function* () {
302 const [persisted, executor] = yield* Effect.all([452 const [persisted, openApiTools, executor] = yield* Effect.all([
303 namespace?.startsWith("executor")453 namespace?.startsWith("executor")
304 ? Effect.succeed([] as readonly StoredToolArtifactRecord[])454 ? Effect.succeed([] as readonly StoredToolArtifactRecord[])
305 : input.rows.toolArtifacts.listByWorkspaceId(input.workspaceId, {455 : input.rows.toolArtifacts.listByWorkspaceId(input.workspaceId, {
@@ -311,6 +461,23 @@ const createWorkspaceToolCatalog = (input: { · 5 unmodified lines
311 cause instanceof Error ? cause : new Error(String(cause)),461 cause instanceof Error ? cause : new Error(String(cause)),
312 ),462 ),
313 ),463 ),
464 loadOpenApiWorkspaceTools({
465 rows: input.rows,
466 workspaceId: input.workspaceId,
467 includeSchemas,
468 }).pipe(
469 Effect.map((tools) =>
470 tools.filter((tool) => {
471 if (namespace && tool.searchNamespace !== namespace) {
472 return false;
473 }
474 if (!query) {
475 return true;
476 }
477 return tokenize(query).every((token) => tool.searchText.includes(token));
478 }),
479 ),
480 ),
314 input.executorCatalog.listTools({481 input.executorCatalog.listTools({
315 ...(namespace !== undefined ? { namespace } : {}),482 ...(namespace !== undefined ? { namespace } : {}),
316 ...(query !== undefined ? { query } : {}),483 ...(query !== undefined ? { query } : {}),
@@ -319,35 +486,18 @@ const createWorkspaceToolCatalog = (input: { · 2 unmodified lines
319 }),486 }),
320 ]);487 ]);
321 488
322 const persistedDescriptors = includeSchemas489 const persistedDescriptors = persisted.map((artifact) =>
323 ? yield* Effect.forEach(490 toPersistedDescriptor({
324 persisted,491 artifact,
325 (artifact) =>492 includeSchemas,
326 input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(493 }),
327 input.workspaceId,494 );
328 artifact.path,
329 ).pipe(
330 Effect.map((rows) =>
331 toDescriptor({
332 artifact,
333 includeSchemas,
334 refHintKeys: rows.map((row) => row.refHintKey),
335 })
336 ),
337 Effect.mapError((cause) =>
338 cause instanceof Error ? cause : new Error(String(cause)),
339 ),
340 ),
341 { concurrency: "unbounded" },
342 )
343 : persisted.map((artifact) =>
344 toDescriptor({
345 artifact,
346 includeSchemas,
347 })
348 );
349 495
350 return [...persistedDescriptors, ...executor]496 return [
497 ...persistedDescriptors,
498 ...openApiTools.map((tool) => tool.descriptor),
499 ...executor,
500 ]
351 .sort((left, right) => left.path.localeCompare(right.path))501 .sort((left, right) => left.path.localeCompare(right.path))
352 .slice(0, limit);502 .slice(0, limit);
353 }),503 }),
@@ -362,6 +512,16 @@ const createWorkspaceToolCatalog = (input: { · 8 unmodified lines
362 return executor;512 return executor;
363 }513 }
364 514
515 const openApiTools = yield* loadOpenApiWorkspaceTools({
516 rows: input.rows,
517 workspaceId: input.workspaceId,
518 includeSchemas,
519 });
520 const openApiTool = openApiTools.find((tool) => tool.path === path);
521 if (openApiTool) {
522 return openApiTool.descriptor;
523 }
524
365 const artifact = yield* input.rows.toolArtifacts.getByWorkspaceAndPath(525 const artifact = yield* input.rows.toolArtifacts.getByWorkspaceAndPath(
366 input.workspaceId,526 input.workspaceId,
367 path,527 path,
@@ -375,29 +535,16 @@ const createWorkspaceToolCatalog = (input: { · 7 unmodified lines
375 return null;535 return null;
376 }536 }
377 537
378 const refHintKeys = includeSchemas538 return toPersistedDescriptor({
379 ? yield* input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
380 input.workspaceId,
381 path,
382 ).pipe(
383 Effect.map((rows) => rows.map((row) => row.refHintKey)),
384 Effect.mapError((cause) =>
385 cause instanceof Error ? cause : new Error(String(cause)),
386 ),
387 )
388 : undefined;
389
390 return toDescriptor({
391 artifact: artifact.value,539 artifact: artifact.value,
392 includeSchemas,540 includeSchemas,
393 refHintKeys,
394 });541 });
395 }),542 }),
396 543
397 searchTools: ({ query, namespace, limit }) =>544 searchTools: ({ query, namespace, limit }) =>
398 Effect.gen(function* () {545 Effect.gen(function* () {
399 const queryTokens = tokenize(query);546 const queryTokens = tokenize(query);
400 const [persisted, executor] = yield* Effect.all([547 const [persisted, openApiTools, executor] = yield* Effect.all([
401 namespace?.startsWith("executor")548 namespace?.startsWith("executor")
402 ? Effect.succeed([] as readonly StoredToolArtifactRecord[])549 ? Effect.succeed([] as readonly StoredToolArtifactRecord[])
403 : input.rows.toolArtifacts.searchByWorkspaceId(input.workspaceId, {550 : input.rows.toolArtifacts.searchByWorkspaceId(input.workspaceId, {
@@ -408,6 +555,15 @@ const createWorkspaceToolCatalog = (input: { · 4 unmodified lines
408 cause instanceof Error ? cause : new Error(String(cause)),555 cause instanceof Error ? cause : new Error(String(cause)),
409 ),556 ),
410 ),557 ),
558 loadOpenApiWorkspaceTools({
559 rows: input.rows,
560 workspaceId: input.workspaceId,
561 includeSchemas: false,
562 }).pipe(
563 Effect.map((tools) =>
564 tools.filter((tool) => !namespace || tool.searchNamespace === namespace),
565 ),
566 ),
411 input.executorCatalog.searchTools({567 input.executorCatalog.searchTools({
412 query,568 query,
413 ...(namespace !== undefined ? { namespace } : {}),569 ...(namespace !== undefined ? { namespace } : {}),
@@ -422,7 +578,17 @@ const createWorkspaceToolCatalog = (input: { · 8 unmodified lines
422 }))578 }))
423 .filter((hit) => hit.score > 0);579 .filter((hit) => hit.score > 0);
424 580
425 return [...persistedHits, ...executor]581 const openApiHits: SearchHit[] = openApiTools
582 .map((tool) => ({
583 path: tool.path,
584 score: tokenize(query).reduce(
585 (total, token) => total + (tool.searchText.includes(token) ? 1 : 0),
586 0,
587 ),
588 }))
589 .filter((hit) => hit.score > 0);
590
591 return [...persistedHits, ...openApiHits, ...executor]
426 .sort((left, right) =>592 .sort((left, right) =>
427 right.score - left.score || left.path.localeCompare(right.path),593 right.score - left.score || left.path.localeCompare(right.path),
428 )594 )
@@ -470,6 +636,37 @@ const createWorkspaceToolInvoker = (input: { · 41 unmodified lines
470 context?: Record<string, unknown>;636 context?: Record<string, unknown>;
471 }) =>637 }) =>
472 Effect.gen(function* () {638 Effect.gen(function* () {
639 const openApiTools = yield* loadOpenApiWorkspaceTools({
640 rows: input.rows,
641 workspaceId: input.workspaceId,
642 includeSchemas: false,
643 });
644 const openApiTool = openApiTools.find((tool) => tool.path === invocation.path);
645 if (openApiTool) {
646 const auth = yield* resolveSourceAuthMaterial({
647 source: openApiTool.source,
648 resolveSecretMaterial: input.resolveSecretMaterial,
649 });
650
651 const tools = createOpenApiToolsFromManifest({
652 manifest: openApiTool.manifest,
653 baseUrl: openApiTool.source.endpoint,
654 namespace: openApiTool.source.namespace ?? namespaceFromSourceName(openApiTool.source.name),
655 sourceKey: openApiTool.source.id,
656 defaultHeaders: openApiTool.source.defaultHeaders ?? {},
657 credentialHeaders: auth.headers,
658 });
659
660 return yield* makeToolInvokerFromTools({
661 tools,
662 onElicitation: input.onElicitation,
663 }).invoke({
664 path: invocation.path,
665 args: invocation.args,
666 context: invocation.context,
667 });
668 }
669
473 const artifactOption = yield* input.rows.toolArtifacts670 const artifactOption = yield* input.rows.toolArtifacts
474 .getByWorkspaceAndPath(input.workspaceId, invocation.path)671 .getByWorkspaceAndPath(input.workspaceId, invocation.path)
475 .pipe(672 .pipe(
@@ -535,79 +732,6 @@ const createWorkspaceToolInvoker = (input: { · 59 unmodified lines
535 });732 });
536 }733 }
537 734
538 if (artifact.providerKind === "openapi") {
539 const [parameters, requestBodyContentTypes, refHintKeys] = yield* Effect.all([
540 input.rows.toolArtifacts.listParametersByWorkspaceAndPath(
541 input.workspaceId,
542 artifact.path,
543 ),
544 input.rows.toolArtifacts.listRequestBodyContentTypesByWorkspaceAndPath(
545 input.workspaceId,
546 artifact.path,
547 ),
548 input.rows.toolArtifacts.listRefHintKeysByWorkspaceAndPath(
549 input.workspaceId,
550 artifact.path,
551 ),
552 ]).pipe(
553 Effect.mapError((cause) =>
554 cause instanceof Error ? cause : new Error(String(cause)),
555 ),
556 );
557
558 const tools = createOpenApiToolsFromManifest({
559 manifest: {
560 version: 1,
561 sourceHash: source.sourceHash ?? "stored",
562 tools: [{
563 toolId: storedToolIdFromArtifact(artifact),
564 name: artifact.title ?? storedToolIdFromArtifact(artifact),
565 description: artifact.description ?? null,
566 method: artifact.openApiMethod!,
567 path: artifact.openApiPathTemplate!,
568 invocation: {
569 method: artifact.openApiMethod!,
570 pathTemplate: artifact.openApiPathTemplate!,
571 parameters: parameters.map((parameter) => ({
572 name: parameter.name,
573 location: parameter.location,
574 required: parameter.required,
575 })),
576 requestBody:
577 artifact.openApiRequestBodyRequired === null
578 ? null
579 : {
580 required: artifact.openApiRequestBodyRequired,
581 contentTypes: requestBodyContentTypes.map((row) => row.contentType),
582 },
583 },
584 operationHash: artifact.openApiOperationHash!,
585 typing: {
586 ...(artifact.inputSchemaJson ? { inputSchemaJson: artifact.inputSchemaJson } : {}),
587 ...(artifact.outputSchemaJson ? { outputSchemaJson: artifact.outputSchemaJson } : {}),
588 ...(refHintKeys.length > 0
589 ? { refHintKeys: refHintKeys.map((row) => row.refHintKey) }
590 : {}),
591 },
592 }],
593 },
594 baseUrl: source.endpoint,
595 namespace: source.namespace ?? namespaceFromSourceName(source.name),
596 sourceKey: source.id,
597 defaultHeaders: source.defaultHeaders ?? {},
598 credentialHeaders: auth.headers,
599 });
600
601 return yield* makeToolInvokerFromTools({
602 tools,
603 onElicitation: input.onElicitation,
604 }).invoke({
605 path: invocation.path,
606 args: invocation.args,
607 context: invocation.context,
608 });
609 }
610
611 return yield* Effect.fail(735 return yield* Effect.fail(
612 new Error(`Unsupported stored tool provider for ${invocation.path}`),736 new Error(`Unsupported stored tool provider for ${invocation.path}`),
613 );737 );
packages/control-plane/src/schema/index.ts+1−0
@@ -11,3 +11,4 @@ export * from "./models/source-auth-session"; · 10 unmodified lines
11export * from "./models/policy";11export * from "./models/policy";
12export * from "./models/local-installation";12export * from "./models/local-installation";
13export * from "./models/execution";13export * from "./models/execution";
14export * from "./models/source-inspection";
packages/control-plane/src/schema/models/source-inspection.ts+85−0
@@ -0,0 +1,85 @@
1import { OpenApiHttpMethodSchema } from "@executor-v3/codemode-openapi";
2import { Schema } from "effect";
3
4import { SourceSchema } from "./source";
5
6export const SourceInspectionPipelineKindSchema = Schema.Literal(
7 "openapi",
8 "persisted",
9);
10
11export const SourceInspectionToolSummarySchema = Schema.Struct({
12 path: Schema.String,
13 sourceKey: Schema.String,
14 title: Schema.optional(Schema.String),
15 description: Schema.optional(Schema.String),
16 providerKind: Schema.String,
17 toolId: Schema.String,
18 rawToolId: Schema.NullOr(Schema.String),
19 operationId: Schema.NullOr(Schema.String),
20 group: Schema.NullOr(Schema.String),
21 leaf: Schema.NullOr(Schema.String),
22 tags: Schema.Array(Schema.String),
23 method: Schema.NullOr(OpenApiHttpMethodSchema),
24 pathTemplate: Schema.NullOr(Schema.String),
25 inputType: Schema.optional(Schema.String),
26 outputType: Schema.optional(Schema.String),
27});
28
29export const SourceInspectionSchema = Schema.Struct({
30 source: SourceSchema,
31 namespace: Schema.String,
32 pipelineKind: SourceInspectionPipelineKindSchema,
33 toolCount: Schema.Number,
34 rawDocumentText: Schema.NullOr(Schema.String),
35 manifestJson: Schema.NullOr(Schema.String),
36 definitionsJson: Schema.NullOr(Schema.String),
37 tools: Schema.Array(SourceInspectionToolSummarySchema),
38});
39
40export const SourceInspectionToolDetailSchema = Schema.Struct({
41 summary: SourceInspectionToolSummarySchema,
42 definitionJson: Schema.NullOr(Schema.String),
43 documentationJson: Schema.NullOr(Schema.String),
44 providerDataJson: Schema.NullOr(Schema.String),
45 inputSchemaJson: Schema.NullOr(Schema.String),
46 outputSchemaJson: Schema.NullOr(Schema.String),
47 exampleInputJson: Schema.NullOr(Schema.String),
48 exampleOutputJson: Schema.NullOr(Schema.String),
49});
50
51export const SourceInspectionDiscoverPayloadSchema = Schema.Struct({
52 query: Schema.String,
53 limit: Schema.optional(Schema.Number),
54});
55
56export const SourceInspectionDiscoverResultItemSchema = Schema.Struct({
57 path: Schema.String,
58 score: Schema.Number,
59 description: Schema.optional(Schema.String),
60 inputType: Schema.optional(Schema.String),
61 outputType: Schema.optional(Schema.String),
62 reasons: Schema.Array(Schema.String),
63});
64
65export const SourceInspectionDiscoverResultSchema = Schema.Struct({
66 query: Schema.String,
67 queryTokens: Schema.Array(Schema.String),
68 bestPath: Schema.NullOr(Schema.String),
69 total: Schema.Number,
70 results: Schema.Array(SourceInspectionDiscoverResultItemSchema),
71});
72
73export type SourceInspectionPipelineKind =
74 typeof SourceInspectionPipelineKindSchema.Type;
75export type SourceInspectionToolSummary =
76 typeof SourceInspectionToolSummarySchema.Type;
77export type SourceInspection = typeof SourceInspectionSchema.Type;
78export type SourceInspectionToolDetail =
79 typeof SourceInspectionToolDetailSchema.Type;
80export type SourceInspectionDiscoverPayload =
81 typeof SourceInspectionDiscoverPayloadSchema.Type;
82export type SourceInspectionDiscoverResultItem =
83 typeof SourceInspectionDiscoverResultItemSchema.Type;
84export type SourceInspectionDiscoverResult =
85 typeof SourceInspectionDiscoverResultSchema.Type;
packages/control-plane/src/schema/models/source.ts+3−0
@@ -95,6 +95,7 @@ export const StoredSourceRecordSchema = Schema.transform( · 94 unmodified lines
95 authHeaderName: Schema.NullOr(Schema.String),95 authHeaderName: Schema.NullOr(Schema.String),
96 authPrefix: Schema.NullOr(Schema.String),96 authPrefix: Schema.NullOr(Schema.String),
97 sourceHash: Schema.NullOr(Schema.String),97 sourceHash: Schema.NullOr(Schema.String),
98 sourceDocumentText: Schema.NullOr(Schema.String),
98 lastError: Schema.NullOr(Schema.String),99 lastError: Schema.NullOr(Schema.String),
99 createdAt: TimestampMsSchema,100 createdAt: TimestampMsSchema,
100 updatedAt: TimestampMsSchema,101 updatedAt: TimestampMsSchema,
@@ -119,6 +120,7 @@ export const StoredSourceRecordSchema = Schema.transform( · 18 unmodified lines
119 authHeaderName: row.authHeaderName,120 authHeaderName: row.authHeaderName,
120 authPrefix: row.authPrefix,121 authPrefix: row.authPrefix,
121 sourceHash: row.sourceHash,122 sourceHash: row.sourceHash,
123 sourceDocumentText: row.sourceDocumentText,
122 lastError: row.lastError,124 lastError: row.lastError,
123 createdAt: row.createdAt,125 createdAt: row.createdAt,
124 updatedAt: row.updatedAt,126 updatedAt: row.updatedAt,
@@ -141,6 +143,7 @@ export const StoredSourceRecordSchema = Schema.transform( · 16 unmodified lines
141 authHeaderName: source.authHeaderName,143 authHeaderName: source.authHeaderName,
142 authPrefix: source.authPrefix,144 authPrefix: source.authPrefix,
143 sourceHash: source.sourceHash,145 sourceHash: source.sourceHash,
146 sourceDocumentText: source.sourceDocumentText,
144 lastError: source.lastError,147 lastError: source.lastError,
145 createdAt: source.createdAt,148 createdAt: source.createdAt,
146 updatedAt: source.updatedAt,149 updatedAt: source.updatedAt,
packages/control-plane/src/schema/models/tool-artifact.ts+9−0
@@ -56,6 +56,9 @@ export const StoredToolArtifactRecordSchema = Schema.Struct({ · 55 unmodified lines
56 ),56 ),
57 openApiPathTemplate: Schema.NullOr(Schema.String),57 openApiPathTemplate: Schema.NullOr(Schema.String),
58 openApiOperationHash: Schema.NullOr(Schema.String),58 openApiOperationHash: Schema.NullOr(Schema.String),
59 openApiRawToolId: Schema.NullOr(Schema.String),
60 openApiOperationId: Schema.NullOr(Schema.String),
61 openApiTagsJson: Schema.NullOr(Schema.String),
59 openApiRequestBodyRequired: Schema.NullOr(Schema.Boolean),62 openApiRequestBodyRequired: Schema.NullOr(Schema.Boolean),
60 createdAt: TimestampMsSchema,63 createdAt: TimestampMsSchema,
61 updatedAt: TimestampMsSchema,64 updatedAt: TimestampMsSchema,
@@ -84,6 +87,9 @@ export const ToolArtifactStorageSchema = Schema.transform( · 22 unmodified lines
84 openApiMethod: row.openApiMethod,87 openApiMethod: row.openApiMethod,
85 openApiPathTemplate: row.openApiPathTemplate,88 openApiPathTemplate: row.openApiPathTemplate,
86 openApiOperationHash: row.openApiOperationHash,89 openApiOperationHash: row.openApiOperationHash,
90 openApiRawToolId: row.openApiRawToolId,
91 openApiOperationId: row.openApiOperationId,
92 openApiTagsJson: row.openApiTagsJson,
87 openApiRequestBodyRequired: row.openApiRequestBodyRequired,93 openApiRequestBodyRequired: row.openApiRequestBodyRequired,
88 createdAt: row.createdAt,94 createdAt: row.createdAt,
89 updatedAt: row.updatedAt,95 updatedAt: row.updatedAt,
@@ -104,6 +110,9 @@ export const ToolArtifactStorageSchema = Schema.transform( · 14 unmodified lines
104 openApiMethod: record.openApiMethod,110 openApiMethod: record.openApiMethod,
105 openApiPathTemplate: record.openApiPathTemplate,111 openApiPathTemplate: record.openApiPathTemplate,
106 openApiOperationHash: record.openApiOperationHash,112 openApiOperationHash: record.openApiOperationHash,
113 openApiRawToolId: record.openApiRawToolId,
114 openApiOperationId: record.openApiOperationId,
115 openApiTagsJson: record.openApiTagsJson,
107 openApiRequestBodyRequired: record.openApiRequestBodyRequired,116 openApiRequestBodyRequired: record.openApiRequestBodyRequired,
108 createdAt: record.createdAt,117 createdAt: record.createdAt,
109 updatedAt: record.updatedAt,118 updatedAt: record.updatedAt,
packages/kitchen-sink/src/source-runtime.test.ts+8−4
@@ -67,8 +67,8 @@ const toDescriptor = ( · 66 unmodified lines
67 sourceKey: artifact.sourceId,67 sourceKey: artifact.sourceId,
68 description: artifact.description ?? artifact.title ?? undefined,68 description: artifact.description ?? artifact.title ?? undefined,
69 interaction: "auto" as const,69 interaction: "auto" as const,
70 inputHint: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,70 inputType: includeSchemas && artifact.inputSchemaJson ? "object" : undefined,
71 outputHint: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,71 outputType: includeSchemas && artifact.outputSchemaJson ? "output" : undefined,
72 inputSchemaJson: includeSchemas ? artifact.inputSchemaJson ?? undefined : undefined,72 inputSchemaJson: includeSchemas ? artifact.inputSchemaJson ?? undefined : undefined,
73 outputSchemaJson: includeSchemas ? artifact.outputSchemaJson ?? undefined : undefined,73 outputSchemaJson: includeSchemas ? artifact.outputSchemaJson ?? undefined : undefined,
74});74});
@@ -527,6 +527,9 @@ const openApiArtifact = (input: { · 452 unmodified lines
527 openApiMethod: input.method,527 openApiMethod: input.method,
528 openApiPathTemplate: input.pathTemplate,528 openApiPathTemplate: input.pathTemplate,
529 openApiOperationHash: input.operationHash ?? input.toolId,529 openApiOperationHash: input.operationHash ?? input.toolId,
530 openApiRawToolId: input.toolId,
531 openApiOperationId: input.toolId,
532 openApiTagsJson: null,
530 openApiRequestBodyRequired: null,533 openApiRequestBodyRequired: null,
531 createdAt: 0,534 createdAt: 0,
532 updatedAt: 0,535 updatedAt: 0,
@@ -805,13 +808,14 @@ describe("source runtime", () => { · 272 unmodified lines
805 score: expect.any(Number),808 score: expect.any(Number),
806 description: "GET /repos/{owner}/{repo}",809 description: "GET /repos/{owner}/{repo}",
807 interaction: "auto",810 interaction: "auto",
808 inputHint: "object { owner, repo }",811 inputType: "{ owner: string, repo: string }",
809 outputHint: "unknown",812 outputType: "unknown",
810 },813 },
811 ],814 ],
812 total: 1,815 total: 1,
813 },816 },
814 result: {817 result: {
818 ok: true,
815 status: 200,819 status: 200,
816 headers: expect.any(Object),820 headers: expect.any(Object),
817 body: {821 body: {
packages/react/package.json+24−0
@@ -0,0 +1,24 @@
1{
2 "name": "@executor-v3/react",
3 "private": true,
4 "type": "module",
5 "exports": {
6 ".": "./src/index.ts"
7 },
8 "scripts": {
9 "typecheck": "bunx tsc --noEmit -p tsconfig.json"
10 },
11 "dependencies": {
12 "@effect-atom/atom": "0.5.3",
13 "@effect-atom/atom-react": "0.5.0",
14 "@executor-v3/control-plane": "workspace:*",
15 "effect": "catalog:"
16 },
17 "peerDependencies": {
18 "react": ">=18 <20"
19 },
20 "devDependencies": {
21 "@types/react": "^19.2.10",
22 "bun-types": "catalog:"
23 }
24}
packages/react/src/index.ts+325−0
@@ -0,0 +1,325 @@
1import { Atom, Result } from "@effect-atom/atom";
2import { RegistryProvider, useAtomValue } from "@effect-atom/atom-react";
3import type {
4 LocalInstallation,
5 Source,
6 SourceInspection,
7 SourceInspectionDiscoverResult,
8 SourceInspectionToolDetail,
9} from "@executor-v3/control-plane/schema";
10import * as Cause from "effect/Cause";
11import * as Effect from "effect/Effect";
12import * as React from "react";
13
14const DEFAULT_EXECUTOR_API_BASE_URL = "http://127.0.0.1:8788";
15const ACCOUNT_HEADER = "x-executor-account-id";
16const PLACEHOLDER_WORKSPACE_ID = "ws_placeholder" as Source["workspaceId"];
17const PLACEHOLDER_ACCOUNT_ID = "acc_placeholder";
18const PLACEHOLDER_SOURCE_ID = "src_placeholder" as Source["id"];
19
20let apiBaseUrl =
21 typeof window !== "undefined" && typeof window.location?.origin === "string"
22 ? window.location.origin
23 : DEFAULT_EXECUTOR_API_BASE_URL;
24
25type AtomKeyPart = string | number | boolean | null | undefined;
26
27const encodeAtomKey = (parts: ReadonlyArray<AtomKeyPart>): string => JSON.stringify(parts);
28
29const decodeAtomKey = <T extends ReadonlyArray<AtomKeyPart>>(key: string): T => JSON.parse(key) as T;
30
31const causeMessage = (cause: Cause.Cause<unknown>): Error =>
32 new Error(Cause.pretty(cause));
33
34const requestJson = async <A>(input: {
35 path: string;
36 accountId?: string;
37 method?: "GET" | "POST";
38 payload?: unknown;
39}): Promise<A> => {
40 const response = await fetch(new URL(input.path, apiBaseUrl), {
41 method: input.method ?? "GET",
42 headers: {
43 ...(input.accountId ? { [ACCOUNT_HEADER]: input.accountId } : {}),
44 ...(input.payload !== undefined ? { "content-type": "application/json" } : {}),
45 },
46 ...(input.payload !== undefined ? { body: JSON.stringify(input.payload) } : {}),
47 });
48
49 if (!response.ok) {
50 const text = await response.text();
51 throw new Error(text || `${response.status} ${response.statusText}`);
52 }
53
54 return response.json() as Promise<A>;
55};
56
57const localInstallationAtom = Atom.make(
58 Effect.promise(() => requestJson<LocalInstallation>({ path: "/v1/local/installation" })),
59).pipe(Atom.keepAlive);
60
61const sourcesAtom = Atom.family((key: string) => {
62 const [enabled, workspaceId, accountId] = decodeAtomKey<
63 readonly [boolean, Source["workspaceId"], string]
64 >(key);
65
66 return Atom.make(
67 enabled
68 ? Effect.promise(() =>
69 requestJson<ReadonlyArray<Source>>({
70 path: `/v1/workspaces/${workspaceId}/sources`,
71 accountId,
72 }),
73 )
74 : Effect.never,
75 ).pipe(Atom.keepAlive);
76});
77
78const sourceInspectionAtom = Atom.family((key: string) => {
79 const [enabled, workspaceId, accountId, sourceId] = decodeAtomKey<
80 readonly [boolean, Source["workspaceId"], string, Source["id"]]
81 >(key);
82
83 return Atom.make(
84 enabled
85 ? Effect.promise(() =>
86 requestJson<SourceInspection>({
87 path: `/v1/workspaces/${workspaceId}/sources/${sourceId}/inspection`,
88 accountId,
89 }),
90 )
91 : Effect.never,
92 ).pipe(Atom.keepAlive);
93});
94
95const sourceInspectionToolAtom = Atom.family((key: string) => {
96 const [enabled, workspaceId, accountId, sourceId, toolPath] = decodeAtomKey<
97 readonly [boolean, Source["workspaceId"], string, Source["id"], string | null]
98 >(key);
99
100 return Atom.make(
101 enabled && toolPath
102 ? Effect.promise(() =>
103 requestJson<SourceInspectionToolDetail>({
104 path: `/v1/workspaces/${workspaceId}/sources/${sourceId}/tools/${encodeURIComponent(toolPath)}/inspection`,
105 accountId,
106 }),
107 )
108 : Effect.succeed<SourceInspectionToolDetail | null>(null),
109 ).pipe(Atom.keepAlive);
110});
111
112const sourceDiscoveryAtom = Atom.family((key: string) => {
113 const [enabled, workspaceId, accountId, sourceId, query, limit] = decodeAtomKey<
114 readonly [boolean, Source["workspaceId"], string, Source["id"], string, number | null]
115 >(key);
116
117 return Atom.make(
118 !enabled
119 ? Effect.never
120 : query.trim().length === 0
121 ? Effect.succeed<SourceInspectionDiscoverResult>({
122 query: "",
123 queryTokens: [],
124 bestPath: null,
125 total: 0,
126 results: [],
127 })
128 : Effect.promise(() =>
129 requestJson<SourceInspectionDiscoverResult>({
130 path: `/v1/workspaces/${workspaceId}/sources/${sourceId}/inspection/discover`,
131 accountId,
132 method: "POST",
133 payload: {
134 query,
135 ...(limit !== null ? { limit } : {}),
136 },
137 }),
138 ),
139 ).pipe(Atom.keepAlive);
140});
141
142export type Loadable<T> =
143 | { status: "loading" }
144 | { status: "error"; error: Error }
145 | { status: "ready"; data: T };
146
147type WorkspaceContext = {
148 installation: LocalInstallation;
149 workspaceId: Source["workspaceId"];
150 accountId: string;
151};
152
153const toLoadable = <T>(result: Result.Result<T, Error>): Loadable<T> => {
154 if (Result.isSuccess(result)) {
155 return {
156 status: "ready",
157 data: result.value,
158 };
159 }
160
161 if (Result.isFailure(result)) {
162 return {
163 status: "error",
164 error: causeMessage(result.cause),
165 };
166 }
167
168 return {
169 status: "loading",
170 };
171};
172
173const pendingLoadable = <T>(workspace: Loadable<WorkspaceContext>): Loadable<T> => {
174 if (workspace.status === "loading") {
175 return { status: "loading" };
176 }
177
178 if (workspace.status === "error") {
179 return { status: "error", error: workspace.error };
180 }
181
182 throw new Error("Expected workspace loadable to be pending or errored");
183};
184
185const useLoadableAtom = <T>(atom: Atom.Atom<Result.Result<T, Error>>): Loadable<T> => {
186 const result = useAtomValue(atom);
187 return React.useMemo(() => toLoadable(result), [result]);
188};
189
190const useWorkspaceContext = (): Loadable<WorkspaceContext> => {
191 const installation = useLoadableAtom(localInstallationAtom);
192
193 return React.useMemo(() => {
194 if (installation.status !== "ready") {
195 return installation;
196 }
197
198 return {
199 status: "ready",
200 data: {
201 installation: installation.data,
202 workspaceId: installation.data.workspaceId,
203 accountId: installation.data.accountId,
204 },
205 } satisfies Loadable<WorkspaceContext>;
206 }, [installation]);
207};
208
209const useWorkspaceRequestContext = () => {
210 const workspace = useWorkspaceContext();
211 const enabled = workspace.status === "ready";
212
213 const workspaceId = enabled
214 ? workspace.data.workspaceId
215 : PLACEHOLDER_WORKSPACE_ID;
216 const accountId = enabled
217 ? workspace.data.accountId
218 : PLACEHOLDER_ACCOUNT_ID;
219
220 return React.useMemo(
221 () => ({
222 workspace,
223 enabled,
224 workspaceId,
225 accountId,
226 }),
227 [accountId, enabled, workspace, workspaceId],
228 );
229};
230
231export const setExecutorApiBaseUrl = (baseUrl: string): void => {
232 apiBaseUrl = baseUrl;
233};
234
235export const ExecutorReactProvider = RegistryProvider;
236
237export const useLocalInstallation = (): Loadable<LocalInstallation> =>
238 useLoadableAtom(localInstallationAtom);
239
240export const useSources = (): Loadable<ReadonlyArray<Source>> => {
241 const workspace = useWorkspaceRequestContext();
242 const sources = useLoadableAtom(
243 sourcesAtom(
244 encodeAtomKey([workspace.enabled, workspace.workspaceId, workspace.accountId]),
245 ),
246 );
247
248 return workspace.enabled ? sources : pendingLoadable(workspace.workspace);
249};
250
251export const useSourceInspection = (sourceId: string): Loadable<SourceInspection> => {
252 const workspace = useWorkspaceRequestContext();
253 const requestedSourceId = workspace.enabled
254 ? (sourceId as Source["id"])
255 : PLACEHOLDER_SOURCE_ID;
256 const inspection = useLoadableAtom(
257 sourceInspectionAtom(
258 encodeAtomKey([
259 workspace.enabled,
260 workspace.workspaceId,
261 workspace.accountId,
262 requestedSourceId,
263 ]),
264 ),
265 );
266
267 return workspace.enabled ? inspection : pendingLoadable(workspace.workspace);
268};
269
270export const useSourceToolDetail = (
271 sourceId: string,
272 toolPath: string | null,
273): Loadable<SourceInspectionToolDetail | null> => {
274 const workspace = useWorkspaceRequestContext();
275 const requestedSourceId = workspace.enabled
276 ? (sourceId as Source["id"])
277 : PLACEHOLDER_SOURCE_ID;
278 const detail = useLoadableAtom(
279 sourceInspectionToolAtom(
280 encodeAtomKey([
281 workspace.enabled,
282 workspace.workspaceId,
283 workspace.accountId,
284 requestedSourceId,
285 toolPath,
286 ]),
287 ),
288 );
289
290 return workspace.enabled ? detail : pendingLoadable(workspace.workspace);
291};
292
293export const useSourceDiscovery = (input: {
294 sourceId: string;
295 query: string;
296 limit?: number;
297}): Loadable<SourceInspectionDiscoverResult> => {
298 const workspace = useWorkspaceRequestContext();
299 const requestedSourceId = workspace.enabled
300 ? (input.sourceId as Source["id"])
301 : PLACEHOLDER_SOURCE_ID;
302 const results = useLoadableAtom(
303 sourceDiscoveryAtom(
304 encodeAtomKey([
305 workspace.enabled,
306 workspace.workspaceId,
307 workspace.accountId,
308 requestedSourceId,
309 input.query,
310 input.limit ?? null,
311 ]),
312 ),
313 );
314
315 return workspace.enabled ? results : pendingLoadable(workspace.workspace);
316};
317
318
319export type {
320 LocalInstallation,
321 Source,
322 SourceInspection,
323 SourceInspectionDiscoverResult,
324 SourceInspectionToolDetail,
325};
packages/react/tsconfig.json+17−0
@@ -0,0 +1,17 @@
1{
2 "compilerOptions": {
3 "target": "ES2022",
4 "module": "ESNext",
5 "moduleResolution": "Bundler",
6 "strict": true,
7 "skipLibCheck": true,
8 "noEmit": true,
9 "lib": ["ES2022", "DOM"],
10 "jsx": "react-jsx",
11 "types": ["bun-types"]
12 },
13 "include": [
14 "src/**/*.ts",
15 "src/**/*.tsx"
16 ]
17}
packages/server/package.json+20−0
@@ -0,0 +1,20 @@
1{
2 "name": "@executor-v3/server",
3 "private": true,
4 "type": "module",
5 "exports": {
6 ".": "./src/index.ts"
7 },
8 "scripts": {
9 "typecheck": "bunx tsc --noEmit -p tsconfig.json"
10 },
11 "dependencies": {
12 "@effect/platform": "catalog:",
13 "@executor-v3/control-plane": "workspace:*",
14 "effect": "catalog:"
15 },
16 "devDependencies": {
17 "@types/node": "catalog:",
18 "bun-types": "catalog:"
19 }
20}
packages/server/src/config.tsrenamed from apps/executor/src/server/config.ts+7−1
@@ -4,6 +4,12 @@ import { join } from "node:path"; · 3 unmodified lines
4export const DEFAULT_SERVER_PORT = 8788;4export const DEFAULT_SERVER_PORT = 8788;
5export const DEFAULT_SERVER_HOST = "127.0.0.1";5export const DEFAULT_SERVER_HOST = "127.0.0.1";
6export const DEFAULT_SERVER_BASE_URL = `http://${DEFAULT_SERVER_HOST}:${DEFAULT_SERVER_PORT}`;6export const DEFAULT_SERVER_BASE_URL = `http://${DEFAULT_SERVER_HOST}:${DEFAULT_SERVER_PORT}`;
7export const DEFAULT_LOCAL_DATA_DIR = join(homedir(), ".local", "share", "executor-v3", "control-plane");7export const DEFAULT_LOCAL_DATA_DIR = join(
8 homedir(),
9 ".local",
10 "share",
11 "executor-v3",
12 "control-plane",
13);
8export const SERVER_START_TIMEOUT_MS = 5_000;14export const SERVER_START_TIMEOUT_MS = 5_000;
9export const SERVER_POLL_INTERVAL_MS = 100;15export const SERVER_POLL_INTERVAL_MS = 100;
packages/server/src/index.ts+237−0
@@ -0,0 +1,237 @@
1import { access, mkdir } from "node:fs/promises";
2import { dirname, extname, resolve } from "node:path";
3
4import { HttpApiBuilder, HttpServer } from "@effect/platform";
5import {
6 createControlPlaneApiLayer,
7 createSqlControlPlaneRuntime,
8 type ResolveExecutionEnvironment,
9 type ResolveSecretMaterial,
10 type SqlControlPlaneRuntime,
11} from "@executor-v3/control-plane";
12import * as Effect from "effect/Effect";
13import * as Layer from "effect/Layer";
14import type * as Scope from "effect/Scope";
15
16import {
17 DEFAULT_LOCAL_DATA_DIR,
18 DEFAULT_SERVER_HOST,
19 DEFAULT_SERVER_PORT,
20} from "./config";
21
22export {
23 DEFAULT_LOCAL_DATA_DIR,
24 DEFAULT_SERVER_BASE_URL,
25 DEFAULT_SERVER_HOST,
26 DEFAULT_SERVER_PORT,
27 SERVER_POLL_INTERVAL_MS,
28 SERVER_START_TIMEOUT_MS,
29} from "./config";
30
31type StaticUiOptions = {
32 readonly assetsDir?: string;
33 readonly devServerUrl?: string;
34};
35
36export type LocalExecutorServer = {
37 readonly runtime: SqlControlPlaneRuntime;
38 readonly port: number;
39 readonly host: string;
40 readonly baseUrl: string;
41};
42
43export type StartLocalExecutorServerOptions = {
44 readonly port?: number;
45 readonly host?: string;
46 readonly localDataDir?: string;
47 readonly executionResolver?: ResolveExecutionEnvironment;
48 readonly resolveSecretMaterial?: ResolveSecretMaterial;
49 readonly ui?: StaticUiOptions;
50};
51
52export type LocalExecutorRequestHandler = {
53 readonly runtime: SqlControlPlaneRuntime;
54 readonly handleApiRequest: (request: Request) => Promise<Response>;
55 readonly getBaseUrl: () => string | undefined;
56 readonly setBaseUrl: (baseUrl: string) => void;
57};
58
59const disposeRuntime = (runtime: SqlControlPlaneRuntime) =>
60 Effect.promise(() => runtime.close()).pipe(Effect.orDie);
61
62const createControlPlaneWebHandler = (runtime: SqlControlPlaneRuntime) =>
63 Effect.acquireRelease(
64 Effect.sync(() =>
65 HttpApiBuilder.toWebHandler(
66 Layer.merge(
67 createControlPlaneApiLayer(runtime.runtimeLayer),
68 HttpServer.layerContext,
69 ),
70 ),
71 ),
72 (handler) => Effect.promise(() => handler.dispose()).pipe(Effect.orDie),
73 );
74
75const safeFilePath = (assetsDir: string, pathname: string): string | null => {
76 const target = resolve(assetsDir, `.${pathname}`);
77 const root = resolve(assetsDir);
78 return target.startsWith(root) ? target : null;
79};
80
81const fileExists = async (path: string): Promise<boolean> => {
82 try {
83 await access(path);
84 return true;
85 } catch {
86 return false;
87 }
88};
89
90const wantsHtml = (request: Request): boolean =>
91 request.headers.get("accept")?.includes("text/html") ?? false;
92
93const serveUiAsset = async (request: Request, ui: StaticUiOptions): Promise<Response | null> => {
94 const url = new URL(request.url);
95
96 if (ui.devServerUrl) {
97 const proxyUrl = new URL(`${url.pathname}${url.search}`, ui.devServerUrl);
98 return fetch(new Request(proxyUrl.toString(), request));
99 }
100
101 if (!ui.assetsDir) {
102 return null;
103 }
104
105 const candidatePath = safeFilePath(ui.assetsDir, url.pathname);
106 if (candidatePath && await fileExists(candidatePath)) {
107 return new Response(Bun.file(candidatePath));
108 }
109
110 const shouldServeIndex =
111 url.pathname === "/"
112 || extname(url.pathname).length === 0
113 || wantsHtml(request);
114
115 if (!shouldServeIndex) {
116 return null;
117 }
118
119 const indexPath = resolve(ui.assetsDir, "index.html");
120 if (!(await fileExists(indexPath))) {
121 return null;
122 }
123
124 return new Response(Bun.file(indexPath), {
125 headers: {
126 "content-type": "text/html; charset=utf-8",
127 },
128 });
129};
130
131const isApiRequest = (request: Request): boolean => {
132 const pathname = new URL(request.url).pathname;
133 return pathname === "/v1" || pathname.startsWith("/v1/");
134};
135
136export const createLocalExecutorRequestHandler = (
137 options: StartLocalExecutorServerOptions = {},
138): Effect.Effect<LocalExecutorRequestHandler, Error, Scope.Scope> =>
139 Effect.gen(function* () {
140 const localDataDir = options.localDataDir ?? DEFAULT_LOCAL_DATA_DIR;
141
142 if (localDataDir !== ":memory:") {
143 yield* Effect.tryPromise({
144 try: () => mkdir(dirname(localDataDir), { recursive: true }),
145 catch: (cause) => (cause instanceof Error ? cause : new Error(String(cause))),
146 });
147 }
148
149 let baseUrlRef: string | undefined;
150
151 const runtime = yield* Effect.acquireRelease(
152 createSqlControlPlaneRuntime({
153 localDataDir,
154 executionResolver: options.executionResolver,
155 resolveSecretMaterial: options.resolveSecretMaterial,
156 getLocalServerBaseUrl: () => baseUrlRef,
157 }).pipe(
158 Effect.mapError((cause) =>
159 cause instanceof Error ? cause : new Error(String(cause)),
160 ),
161 ),
162 disposeRuntime,
163 );
164
165 const apiHandler = yield* createControlPlaneWebHandler(runtime);
166
167 return {
168 runtime,
169 handleApiRequest: (request) => apiHandler.handler(request),
170 getBaseUrl: () => baseUrlRef,
171 setBaseUrl: (baseUrl) => {
172 baseUrlRef = baseUrl;
173 },
174 } satisfies LocalExecutorRequestHandler;
175 });
176
177export const createLocalExecutorServer = (
178 options: StartLocalExecutorServerOptions = {},
179): Effect.Effect<LocalExecutorServer, Error, Scope.Scope> =>
180 Effect.gen(function* () {
181 const host = options.host ?? DEFAULT_SERVER_HOST;
182 const port = options.port ?? DEFAULT_SERVER_PORT;
183 const requestHandler = yield* createLocalExecutorRequestHandler(options);
184
185 const server = yield* Effect.acquireRelease(
186 Effect.sync(() =>
187 Bun.serve({
188 hostname: host,
189 port,
190 fetch: async (request) => {
191 if (isApiRequest(request)) {
192 return requestHandler.handleApiRequest(request);
193 }
194
195 const uiResponse = await serveUiAsset(request, options.ui ?? {});
196 if (uiResponse) {
197 return uiResponse;
198 }
199
200 return new Response("Not Found", { status: 404 });
201 },
202 }),
203 ),
204 (server) => Effect.sync(() => server.stop(true)),
205 );
206
207 const resolvedUrl = new URL(server.url);
208 requestHandler.setBaseUrl(resolvedUrl.origin);
209
210 return {
211 runtime: requestHandler.runtime,
212 host: resolvedUrl.hostname,
213 port: Number(resolvedUrl.port),
214 baseUrl: resolvedUrl.origin,
215 } satisfies LocalExecutorServer;
216 });
217
218export const runLocalExecutorServer = (
219 options: StartLocalExecutorServerOptions = {},
220): Effect.Effect<void, Error, never> =>
221 Effect.scoped(
222 Effect.gen(function* () {
223 const server = yield* createLocalExecutorServer(options);
224 console.error(`executor server listening on ${server.baseUrl}`);
225
226 yield* Effect.async<void, never>((resume) => {
227 const shutdown = () => resume(Effect.void);
228 process.once("SIGINT", shutdown);
229 process.once("SIGTERM", shutdown);
230
231 return Effect.sync(() => {
232 process.off("SIGINT", shutdown);
233 process.off("SIGTERM", shutdown);
234 });
235 });
236 }),
237 );
packages/server/tsconfig.json+15−0
@@ -0,0 +1,15 @@
1{
2 "compilerOptions": {
3 "target": "ES2022",
4 "module": "ESNext",
5 "moduleResolution": "Bundler",
6 "strict": true,
7 "skipLibCheck": true,
8 "noEmit": true,
9 "lib": ["ES2022", "DOM"],
10 "types": ["bun-types", "node"]
11 },
12 "include": [
13 "src/**/*.ts"
14 ]
15}
turbo.json+17−0
@@ -0,0 +1,17 @@
1{
2 "$schema": "https://turbo.build/schema.json",
3 "tasks": {
4 "build": {
5 "dependsOn": ["^build"],
6 "outputs": ["dist/**"]
7 },
8 "typecheck": {
9 "dependsOn": ["^typecheck"],
10 "outputs": []
11 },
12 "dev": {
13 "cache": false,
14 "persistent": true
15 }
16 }
17}