Unified · Split
| @@ -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 | }, |
| @@ -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"; | ||
| 36 | import { | 37 | import { |
| 37 | seedDemoMcpSourceInWorkspace, | 38 | seedDemoMcpSourceInWorkspace, |
| 38 | seedGithubOpenApiSourceInWorkspace, | 39 | seedGithubOpenApiSourceInWorkspace, |
| 39 | } from "./dev"; | 40 | } from "./dev"; |
| 40 | import { runLocalExecutorServer } from "../server"; | ||
| 41 | 41 | ||
| 42 | const toError = (cause: unknown): Error => | 42 | const toError = (cause: unknown): Error => |
| 43 | cause instanceof Error ? cause : new Error(String(cause)); | 43 | cause instanceof Error ? cause : new Error(String(cause)); |
| @@ -1,138 +0,0 @@ | |||
| 1 | import { mkdir } from "node:fs/promises"; | ||
| 2 | import { createServer } from "node:http"; | ||
| 3 | import { dirname } from "node:path"; | ||
| 4 | |||
| 5 | import { HttpApiBuilder, HttpServer } from "@effect/platform"; | ||
| 6 | import { NodeHttpServer } from "@effect/platform-node"; | ||
| 7 | import { | ||
| 8 | createControlPlaneApiLayer, | ||
| 9 | createSqlControlPlaneRuntime, | ||
| 10 | type ResolveExecutionEnvironment, | ||
| 11 | type ResolveSecretMaterial, | ||
| 12 | type SqlControlPlaneRuntime, | ||
| 13 | } from "@executor-v3/control-plane"; | ||
| 14 | import * as Context from "effect/Context"; | ||
| 15 | import * as Effect from "effect/Effect"; | ||
| 16 | import * as Layer from "effect/Layer"; | ||
| 17 | import type * as Scope from "effect/Scope"; | ||
| 18 | |||
| 19 | import { | ||
| 20 | DEFAULT_LOCAL_DATA_DIR, | ||
| 21 | DEFAULT_SERVER_HOST, | ||
| 22 | DEFAULT_SERVER_PORT, | ||
| 23 | } from "./config"; | ||
| 24 | |||
| 25 | type LocalExecutorServer = { | ||
| 26 | readonly runtime: SqlControlPlaneRuntime; | ||
| 27 | readonly port: number; | ||
| 28 | readonly host: string; | ||
| 29 | readonly baseUrl: string; | ||
| 30 | }; | ||
| 31 | |||
| 32 | type StartLocalExecutorServerOptions = { | ||
| 33 | readonly port?: number; | ||
| 34 | readonly host?: string; | ||
| 35 | readonly localDataDir?: string; | ||
| 36 | readonly executionResolver?: ResolveExecutionEnvironment; | ||
| 37 | readonly resolveSecretMaterial?: ResolveSecretMaterial; | ||
| 38 | }; | ||
| 39 | |||
| 40 | const disposeRuntime = (runtime: SqlControlPlaneRuntime) => | ||
| 41 | Effect.promise(() => runtime.close()).pipe(Effect.orDie); | ||
| 42 | |||
| 43 | const 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 | |||
| 61 | export 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 | |||
| 119 | export 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 | ); | ||
| @@ -31,7 +31,7 @@ import { · 30 unmodified lines | |||
| 31 | seedDemoMcpSourceInWorkspace, | 31 | seedDemoMcpSourceInWorkspace, |
| 32 | seedGithubOpenApiSourceInWorkspace, | 32 | seedGithubOpenApiSourceInWorkspace, |
| 33 | } from "../cli/dev"; | 33 | } from "../cli/dev"; |
| 34 | import { createLocalExecutorServer } from "."; | 34 | import { createLocalExecutorServer } from "@executor-v3/server"; |
| 35 | 35 | ||
| 36 | const executionResolver: ResolveExecutionEnvironment = () => | 36 | const executionResolver: ResolveExecutionEnvironment = () => |
| 37 | Effect.succeed({ | 37 | Effect.succeed({ |
| @@ -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 | } | ||
| @@ -0,0 +1,96 @@ | |||
| 1 | import { useEffect, useState, useRef, useCallback } from "react"; | ||
| 2 | import { codeToHtml, type BundledLanguage } from "shiki"; | ||
| 3 | import { cn } from "../lib/utils"; | ||
| 4 | import { IconCopy, IconCheck } from "./icons"; | ||
| 5 | |||
| 6 | // Lazily loaded, cached across renders | ||
| 7 | const highlightCache = new Map<string, string>(); | ||
| 8 | |||
| 9 | function cacheKey(code: string, lang: string) { | ||
| 10 | return `${lang}::${code.length}::${code.slice(0, 64)}`; | ||
| 11 | } | ||
| 12 | |||
| 13 | function 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 | |||
| 22 | export 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 | } | ||
| @@ -0,0 +1,44 @@ | |||
| 1 | import { useMemo } from "react"; | ||
| 2 | import { cn } from "../lib/utils"; | ||
| 3 | import { CodeBlock } from "./code-block"; | ||
| 4 | |||
| 5 | function prettyJson(raw: string): string { | ||
| 6 | try { | ||
| 7 | return JSON.stringify(JSON.parse(raw), null, 2); | ||
| 8 | } catch { | ||
| 9 | return raw; | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 13 | export 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 | } | ||
| @@ -0,0 +1,109 @@ | |||
| 1 | import type { SVGProps } from "react"; | ||
| 2 | import { cn } from "../lib/utils"; | ||
| 3 | |||
| 4 | type IconProps = SVGProps<SVGSVGElement> & { className?: string }; | ||
| 5 | |||
| 6 | export 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 | |||
| 17 | export 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 | |||
| 25 | export 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 | |||
| 34 | export 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 | |||
| 42 | export 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 | |||
| 50 | export 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 | |||
| 59 | export 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 | |||
| 68 | export 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 | |||
| 77 | export 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 | |||
| 85 | export 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 | |||
| 93 | export 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 | |||
| 102 | export 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 | } | ||
| @@ -0,0 +1,50 @@ | |||
| 1 | import type { ReactNode } from "react"; | ||
| 2 | import type { Loadable } from "@executor-v3/react"; | ||
| 3 | import { IconSpinner, IconEmpty } from "./icons"; | ||
| 4 | |||
| 5 | export 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 | |||
| 34 | export 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 | } | ||
| @@ -0,0 +1,232 @@ | |||
| 1 | import { useState, useMemo } from "react"; | ||
| 2 | import { Link, Outlet, useMatchRoute } from "@tanstack/react-router"; | ||
| 3 | import { useSources, type Source } from "@executor-v3/react"; | ||
| 4 | import { cn } from "../lib/utils"; | ||
| 5 | import { IconSources, IconChevron } from "./icons"; | ||
| 6 | import { LoadableBlock } from "./loadable"; | ||
| 7 | |||
| 8 | // ── Kind labels + ordering ─────────────────────────────────────────────── | ||
| 9 | |||
| 10 | const 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 | |||
| 17 | function 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 | |||
| 31 | const 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 | |||
| 41 | export 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 | |||
| 101 | function 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 | |||
| 129 | function 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 | |||
| 174 | function 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 | |||
| 218 | function 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 | } | ||
| @@ -0,0 +1,43 @@ | |||
| 1 | import type { HTMLAttributes } from "react"; | ||
| 2 | import { cva, type VariantProps } from "class-variance-authority"; | ||
| 3 | import { cn } from "../../lib/utils"; | ||
| 4 | |||
| 5 | const 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 | |||
| 27 | export type BadgeProps = HTMLAttributes<HTMLSpanElement> & VariantProps<typeof badgeVariants>; | ||
| 28 | |||
| 29 | export function Badge({ className, variant, ...props }: BadgeProps) { | ||
| 30 | return <span className={cn(badgeVariants({ variant }), className)} {...props} />; | ||
| 31 | } | ||
| 32 | |||
| 33 | export 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 | } | ||
| @@ -0,0 +1,34 @@ | |||
| 1 | import type { ButtonHTMLAttributes } from "react"; | ||
| 2 | import { cva, type VariantProps } from "class-variance-authority"; | ||
| 3 | import { cn } from "../../lib/utils"; | ||
| 4 | |||
| 5 | const 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 | |||
| 30 | type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>; | ||
| 31 | |||
| 32 | export function Button({ className, variant, size, ...props }: ButtonProps) { | ||
| 33 | return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />; | ||
| 34 | } | ||
| @@ -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 | */ | ||
| 8 | import * as Effect from "effect/Effect"; | ||
| 9 | import * as Scope from "effect/Scope"; | ||
| 10 | import { createLocalExecutorRequestHandler } from "@executor-v3/server"; | ||
| 11 | |||
| 12 | // Create a long-lived scope that stays open for the lifetime of the process. | ||
| 13 | const 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 | |||
| 24 | export default { | ||
| 25 | async fetch(request: Request) { | ||
| 26 | const handler = await handlerPromise; | ||
| 27 | return handler.handleApiRequest(request); | ||
| 28 | }, | ||
| 29 | }; | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | import { createRoot } from "react-dom/client"; | ||
| 2 | import { App } from "./main"; | ||
| 3 | |||
| 4 | const rootElement = document.getElementById("root"); | ||
| 5 | |||
| 6 | if (!rootElement) { | ||
| 7 | throw new Error("Missing #root element"); | ||
| 8 | } | ||
| 9 | |||
| 10 | createRoot(rootElement).render(<App />); | ||
| @@ -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 | } | ||
| @@ -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> | ||
| @@ -0,0 +1,6 @@ | |||
| 1 | import { clsx, type ClassValue } from "clsx"; | ||
| 2 | import { twMerge } from "tailwind-merge"; | ||
| 3 | |||
| 4 | export function cn(...inputs: ReadonlyArray<ClassValue>): string { | ||
| 5 | return twMerge(clsx(inputs)); | ||
| 6 | } | ||
| @@ -0,0 +1,100 @@ | |||
| 1 | import * as React from "react"; | ||
| 2 | import { | ||
| 3 | RouterProvider, | ||
| 4 | createRootRoute, | ||
| 5 | createRoute, | ||
| 6 | createRouter, | ||
| 7 | useNavigate, | ||
| 8 | } from "@tanstack/react-router"; | ||
| 9 | import { ExecutorReactProvider } from "@executor-v3/react"; | ||
| 10 | |||
| 11 | import "./globals.css"; | ||
| 12 | |||
| 13 | import { AppShell } from "./components/shell"; | ||
| 14 | import { HomePage } from "./views/home"; | ||
| 15 | import { SourceDetailPage } from "./views/source-detail"; | ||
| 16 | |||
| 17 | // --------------------------------------------------------------------------- | ||
| 18 | // Route search schema | ||
| 19 | // --------------------------------------------------------------------------- | ||
| 20 | |||
| 21 | type SourceRouteSearch = { | ||
| 22 | tab: "model" | "discover" | "manifest" | "definitions" | "raw"; | ||
| 23 | tool?: string; | ||
| 24 | query?: string; | ||
| 25 | }; | ||
| 26 | |||
| 27 | const sourceTabs = ["model", "discover", "manifest", "definitions", "raw"] as const; | ||
| 28 | |||
| 29 | // --------------------------------------------------------------------------- | ||
| 30 | // Routes | ||
| 31 | // --------------------------------------------------------------------------- | ||
| 32 | |||
| 33 | const rootRoute = createRootRoute({ | ||
| 34 | component: AppShell, | ||
| 35 | }); | ||
| 36 | |||
| 37 | const homeRoute = createRoute({ | ||
| 38 | getParentRoute: () => rootRoute, | ||
| 39 | path: "/", | ||
| 40 | component: HomePage, | ||
| 41 | }); | ||
| 42 | |||
| 43 | const 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 | |||
| 57 | function 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 | |||
| 75 | const routeTree = rootRoute.addChildren([homeRoute, sourceRoute]); | ||
| 76 | |||
| 77 | const router = createRouter({ | ||
| 78 | routeTree, | ||
| 79 | defaultPreload: "intent", | ||
| 80 | }); | ||
| 81 | |||
| 82 | declare module "@tanstack/react-router" { | ||
| 83 | interface Register { | ||
| 84 | router: typeof router; | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | // --------------------------------------------------------------------------- | ||
| 89 | // App | ||
| 90 | // --------------------------------------------------------------------------- | ||
| 91 | |||
| 92 | export function App() { | ||
| 93 | return ( | ||
| 94 | <React.StrictMode> | ||
| 95 | <ExecutorReactProvider> | ||
| 96 | <RouterProvider router={router} /> | ||
| 97 | </ExecutorReactProvider> | ||
| 98 | </React.StrictMode> | ||
| 99 | ); | ||
| 100 | } | ||
| @@ -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 | */ | ||
| 7 | import { runLocalExecutorServer } from "@executor-v3/server"; | ||
| 8 | |||
| 9 | await runLocalExecutorServer({ | ||
| 10 | ui: { | ||
| 11 | assetsDir: new URL("../dist", import.meta.url).pathname, | ||
| 12 | }, | ||
| 13 | }); | ||
| @@ -0,0 +1,181 @@ | |||
| 1 | import { Link } from "@tanstack/react-router"; | ||
| 2 | import { useSources, type Source } from "@executor-v3/react"; | ||
| 3 | import { LoadableBlock } from "../components/loadable"; | ||
| 4 | import { Badge } from "../components/ui/badge"; | ||
| 5 | import { IconSources, IconDiscover } from "../components/icons"; | ||
| 6 | import { CodeBlock } from "../components/code-block"; | ||
| 7 | |||
| 8 | export 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 | |||
| 124 | function 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 | → | ||
| 151 | </span> | ||
| 152 | </Link> | ||
| 153 | ); | ||
| 154 | } | ||
| 155 | |||
| 156 | function 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 | |||
| 172 | function 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 | } | ||
| @@ -0,0 +1,604 @@ | |||
| 1 | import { useState, useEffect, useCallback, useRef } from "react"; | ||
| 2 | import { useNavigate } from "@tanstack/react-router"; | ||
| 3 | import { | ||
| 4 | useSourceInspection, | ||
| 5 | useSourceToolDetail, | ||
| 6 | useSourceDiscovery, | ||
| 7 | type Loadable, | ||
| 8 | type SourceInspection, | ||
| 9 | type SourceInspectionToolDetail, | ||
| 10 | type SourceInspectionDiscoverResult, | ||
| 11 | } from "@executor-v3/react"; | ||
| 12 | import { cn } from "../lib/utils"; | ||
| 13 | import { Badge, MethodBadge } from "../components/ui/badge"; | ||
| 14 | import { Button } from "../components/ui/button"; | ||
| 15 | import { LoadableBlock, EmptyState } from "../components/loadable"; | ||
| 16 | import { DocumentPanel } from "../components/document-panel"; | ||
| 17 | import { | ||
| 18 | IconSearch, | ||
| 19 | IconChevron, | ||
| 20 | IconTool, | ||
| 21 | IconCopy, | ||
| 22 | IconCheck, | ||
| 23 | IconClose, | ||
| 24 | IconEmpty, | ||
| 25 | } from "../components/icons"; | ||
| 26 | |||
| 27 | // --------------------------------------------------------------------------- | ||
| 28 | // Types | ||
| 29 | // --------------------------------------------------------------------------- | ||
| 30 | |||
| 31 | type SourceRouteSearch = { | ||
| 32 | tab: "model" | "discover" | "manifest" | "definitions" | "raw"; | ||
| 33 | tool?: string; | ||
| 34 | query?: string; | ||
| 35 | }; | ||
| 36 | |||
| 37 | const 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 | |||
| 49 | export 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 | |||
| 170 | function 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 | |||
| 292 | function 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 | |||
| 331 | function 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 | |||
| 399 | function 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>→</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 → | ||
| 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 | |||
| 518 | function 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 | |||
| 534 | function 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 | |||
| 552 | function 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 | } | ||
| @@ -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 | } | ||
| @@ -0,0 +1,29 @@ | |||
| 1 | import { defineConfig } from "vite"; | ||
| 2 | import react from "@vitejs/plugin-react"; | ||
| 3 | import tailwindcss from "@tailwindcss/vite"; | ||
| 4 | import devServer from "@hono/vite-dev-server"; | ||
| 5 | |||
| 6 | export 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 | }); | ||
| @@ -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=="], |
| @@ -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 | } |
| @@ -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 }) { |
| @@ -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 | ...(includeSchemas | 233 | ...(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 | }; |
| @@ -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 }) => |
| @@ -1,5 +1,6 @@ | |||
| 1 | export * from "./types"; | 1 | export * from "./types"; |
| 2 | export * from "./json-schema"; | 2 | export * from "./json-schema"; |
| 3 | export * from "./schema-types"; | ||
| 3 | export * from "./tool-map"; | 4 | export * from "./tool-map"; |
| 4 | export * from "./discovery"; | 5 | export * from "./discovery"; |
| 5 | export * from "./system-tools"; | 6 | export * from "./system-tools"; |
| @@ -0,0 +1,125 @@ | |||
| 1 | type JsonSchemaRecord = Record<string, unknown>; | ||
| 2 | |||
| 3 | const asRecord = (value: unknown): JsonSchemaRecord => | ||
| 4 | typeof value === "object" && value !== null && !Array.isArray(value) | ||
| 5 | ? (value as JsonSchemaRecord) | ||
| 6 | : {}; | ||
| 7 | |||
| 8 | const asStringArray = (value: unknown): Array<string> => | ||
| 9 | Array.isArray(value) | ||
| 10 | ? value.filter((item): item is string => typeof item === "string") | ||
| 11 | : []; | ||
| 12 | |||
| 13 | const truncate = (value: string, maxLength: number): string => | ||
| 14 | value.length <= maxLength | ||
| 15 | ? value | ||
| 16 | : `${value.slice(0, Math.max(0, maxLength - 4))} ...`; | ||
| 17 | |||
| 18 | const propertyLabel = ( | ||
| 19 | name: string, | ||
| 20 | schema: JsonSchemaRecord, | ||
| 21 | optional: boolean, | ||
| 22 | maxLength: number, | ||
| 23 | ): string => | ||
| 24 | `${name}${optional ? "?" : ""}: ${schemaToTypeSignature(schema, maxLength)}`; | ||
| 25 | |||
| 26 | const 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 | |||
| 47 | export 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 | |||
| 111 | export 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 | }; | ||
| @@ -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 | ||
| 91 | const discoverOutputSchema = Schema.standardSchemaV1( | 93 | const discoverOutputSchema = Schema.standardSchemaV1( |
| @@ -2,6 +2,7 @@ import type { StandardSchemaV1 } from "@standard-schema/spec"; · 1 unmodified line | |||
| 2 | import * as Data from "effect/Data"; | 2 | import * as Data from "effect/Data"; |
| 3 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as JSONSchema from "effect/JSONSchema"; | 4 | import * as JSONSchema from "effect/JSONSchema"; |
| 5 | import { typeSignatureFromSchemaJson } from "./schema-types"; | ||
| 5 | 6 | ||
| 6 | import type { | 7 | import type { |
| 7 | ElicitationRequest, | 8 | ElicitationRequest, |
| @@ -355,50 +356,11 @@ const stringifySchema = (value: unknown): string | undefined => { · 347 unmodified lines | |||
| 355 | } | 356 | } |
| 356 | }; | 357 | }; |
| 357 | 358 | ||
| 358 | const inferHintFromSchemaJson = ( | 359 | const 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 | ||
| 403 | export function createToolsFromRecord(input: { | 365 | export 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 | } |
| @@ -77,12 +77,15 @@ export type OnElicitation = ( · 76 unmodified lines | |||
| 77 | export type ToolMetadata = { | 77 | export 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 | ||
| 88 | export type ToolExecutionContext = { | 91 | export 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 | ||
| 137 | export type ToolNamespace = { | 143 | export type ToolNamespace = { |
| @@ -0,0 +1 @@ | |||
| 1 | target/ | ||
| @@ -0,0 +1,445 @@ | |||
| 1 | # This file is automatically @generated by Cargo. | ||
| 2 | # It is not intended for manual editing. | ||
| 3 | version = 4 | ||
| 4 | |||
| 5 | [[package]] | ||
| 6 | name = "anstream" | ||
| 7 | version = "0.6.21" | ||
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" | ||
| 10 | dependencies = [ | ||
| 11 | "anstyle", | ||
| 12 | "anstyle-parse", | ||
| 13 | "anstyle-query", | ||
| 14 | "anstyle-wincon", | ||
| 15 | "colorchoice", | ||
| 16 | "is_terminal_polyfill", | ||
| 17 | "utf8parse", | ||
| 18 | ] | ||
| 19 | |||
| 20 | [[package]] | ||
| 21 | name = "anstyle" | ||
| 22 | version = "1.0.13" | ||
| 23 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 24 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" | ||
| 25 | |||
| 26 | [[package]] | ||
| 27 | name = "anstyle-parse" | ||
| 28 | version = "0.2.7" | ||
| 29 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 30 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" | ||
| 31 | dependencies = [ | ||
| 32 | "utf8parse", | ||
| 33 | ] | ||
| 34 | |||
| 35 | [[package]] | ||
| 36 | name = "anstyle-query" | ||
| 37 | version = "1.1.5" | ||
| 38 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 39 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" | ||
| 40 | dependencies = [ | ||
| 41 | "windows-sys", | ||
| 42 | ] | ||
| 43 | |||
| 44 | [[package]] | ||
| 45 | name = "anstyle-wincon" | ||
| 46 | version = "3.0.11" | ||
| 47 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 48 | checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" | ||
| 49 | dependencies = [ | ||
| 50 | "anstyle", | ||
| 51 | "once_cell_polyfill", | ||
| 52 | "windows-sys", | ||
| 53 | ] | ||
| 54 | |||
| 55 | [[package]] | ||
| 56 | name = "block-buffer" | ||
| 57 | version = "0.10.4" | ||
| 58 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 59 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" | ||
| 60 | dependencies = [ | ||
| 61 | "generic-array", | ||
| 62 | ] | ||
| 63 | |||
| 64 | [[package]] | ||
| 65 | name = "bumpalo" | ||
| 66 | version = "3.20.2" | ||
| 67 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 68 | checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" | ||
| 69 | |||
| 70 | [[package]] | ||
| 71 | name = "cfg-if" | ||
| 72 | version = "1.0.4" | ||
| 73 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 74 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | ||
| 75 | |||
| 76 | [[package]] | ||
| 77 | name = "clap" | ||
| 78 | version = "4.5.60" | ||
| 79 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 80 | checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" | ||
| 81 | dependencies = [ | ||
| 82 | "clap_builder", | ||
| 83 | "clap_derive", | ||
| 84 | ] | ||
| 85 | |||
| 86 | [[package]] | ||
| 87 | name = "clap_builder" | ||
| 88 | version = "4.5.60" | ||
| 89 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 90 | checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" | ||
| 91 | dependencies = [ | ||
| 92 | "anstream", | ||
| 93 | "anstyle", | ||
| 94 | "clap_lex", | ||
| 95 | "strsim", | ||
| 96 | ] | ||
| 97 | |||
| 98 | [[package]] | ||
| 99 | name = "clap_derive" | ||
| 100 | version = "4.5.55" | ||
| 101 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 102 | checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" | ||
| 103 | dependencies = [ | ||
| 104 | "heck", | ||
| 105 | "proc-macro2", | ||
| 106 | "quote", | ||
| 107 | "syn", | ||
| 108 | ] | ||
| 109 | |||
| 110 | [[package]] | ||
| 111 | name = "clap_lex" | ||
| 112 | version = "1.0.0" | ||
| 113 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 114 | checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" | ||
| 115 | |||
| 116 | [[package]] | ||
| 117 | name = "colorchoice" | ||
| 118 | version = "1.0.4" | ||
| 119 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 120 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" | ||
| 121 | |||
| 122 | [[package]] | ||
| 123 | name = "cpufeatures" | ||
| 124 | version = "0.2.17" | ||
| 125 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 126 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" | ||
| 127 | dependencies = [ | ||
| 128 | "libc", | ||
| 129 | ] | ||
| 130 | |||
| 131 | [[package]] | ||
| 132 | name = "crypto-common" | ||
| 133 | version = "0.1.7" | ||
| 134 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 135 | checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" | ||
| 136 | dependencies = [ | ||
| 137 | "generic-array", | ||
| 138 | "typenum", | ||
| 139 | ] | ||
| 140 | |||
| 141 | [[package]] | ||
| 142 | name = "digest" | ||
| 143 | version = "0.10.7" | ||
| 144 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 145 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" | ||
| 146 | dependencies = [ | ||
| 147 | "block-buffer", | ||
| 148 | "crypto-common", | ||
| 149 | ] | ||
| 150 | |||
| 151 | [[package]] | ||
| 152 | name = "equivalent" | ||
| 153 | version = "1.0.2" | ||
| 154 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 155 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | ||
| 156 | |||
| 157 | [[package]] | ||
| 158 | name = "generic-array" | ||
| 159 | version = "0.14.7" | ||
| 160 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 161 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" | ||
| 162 | dependencies = [ | ||
| 163 | "typenum", | ||
| 164 | "version_check", | ||
| 165 | ] | ||
| 166 | |||
| 167 | [[package]] | ||
| 168 | name = "hashbrown" | ||
| 169 | version = "0.16.1" | ||
| 170 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 171 | checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" | ||
| 172 | |||
| 173 | [[package]] | ||
| 174 | name = "heck" | ||
| 175 | version = "0.5.0" | ||
| 176 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 177 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||
| 178 | |||
| 179 | [[package]] | ||
| 180 | name = "indexmap" | ||
| 181 | version = "2.13.0" | ||
| 182 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 183 | checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" | ||
| 184 | dependencies = [ | ||
| 185 | "equivalent", | ||
| 186 | "hashbrown", | ||
| 187 | ] | ||
| 188 | |||
| 189 | [[package]] | ||
| 190 | name = "is_terminal_polyfill" | ||
| 191 | version = "1.70.2" | ||
| 192 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 193 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" | ||
| 194 | |||
| 195 | [[package]] | ||
| 196 | name = "itoa" | ||
| 197 | version = "1.0.17" | ||
| 198 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 199 | checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" | ||
| 200 | |||
| 201 | [[package]] | ||
| 202 | name = "libc" | ||
| 203 | version = "0.2.182" | ||
| 204 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 205 | checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" | ||
| 206 | |||
| 207 | [[package]] | ||
| 208 | name = "memchr" | ||
| 209 | version = "2.8.0" | ||
| 210 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 211 | checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" | ||
| 212 | |||
| 213 | [[package]] | ||
| 214 | name = "once_cell" | ||
| 215 | version = "1.21.3" | ||
| 216 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 217 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" | ||
| 218 | |||
| 219 | [[package]] | ||
| 220 | name = "once_cell_polyfill" | ||
| 221 | version = "1.70.2" | ||
| 222 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 223 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" | ||
| 224 | |||
| 225 | [[package]] | ||
| 226 | name = "openapi-extractor-rs" | ||
| 227 | version = "0.1.0" | ||
| 228 | dependencies = [ | ||
| 229 | "clap", | ||
| 230 | "serde", | ||
| 231 | "serde_json", | ||
| 232 | "serde_yaml", | ||
| 233 | "sha2", | ||
| 234 | "wasm-bindgen", | ||
| 235 | ] | ||
| 236 | |||
| 237 | [[package]] | ||
| 238 | name = "proc-macro2" | ||
| 239 | version = "1.0.106" | ||
| 240 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 241 | checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" | ||
| 242 | dependencies = [ | ||
| 243 | "unicode-ident", | ||
| 244 | ] | ||
| 245 | |||
| 246 | [[package]] | ||
| 247 | name = "quote" | ||
| 248 | version = "1.0.44" | ||
| 249 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 250 | checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" | ||
| 251 | dependencies = [ | ||
| 252 | "proc-macro2", | ||
| 253 | ] | ||
| 254 | |||
| 255 | [[package]] | ||
| 256 | name = "rustversion" | ||
| 257 | version = "1.0.22" | ||
| 258 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 259 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" | ||
| 260 | |||
| 261 | [[package]] | ||
| 262 | name = "ryu" | ||
| 263 | version = "1.0.23" | ||
| 264 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 265 | checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" | ||
| 266 | |||
| 267 | [[package]] | ||
| 268 | name = "serde" | ||
| 269 | version = "1.0.228" | ||
| 270 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 271 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" | ||
| 272 | dependencies = [ | ||
| 273 | "serde_core", | ||
| 274 | "serde_derive", | ||
| 275 | ] | ||
| 276 | |||
| 277 | [[package]] | ||
| 278 | name = "serde_core" | ||
| 279 | version = "1.0.228" | ||
| 280 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 281 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" | ||
| 282 | dependencies = [ | ||
| 283 | "serde_derive", | ||
| 284 | ] | ||
| 285 | |||
| 286 | [[package]] | ||
| 287 | name = "serde_derive" | ||
| 288 | version = "1.0.228" | ||
| 289 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 290 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" | ||
| 291 | dependencies = [ | ||
| 292 | "proc-macro2", | ||
| 293 | "quote", | ||
| 294 | "syn", | ||
| 295 | ] | ||
| 296 | |||
| 297 | [[package]] | ||
| 298 | name = "serde_json" | ||
| 299 | version = "1.0.149" | ||
| 300 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 301 | checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" | ||
| 302 | dependencies = [ | ||
| 303 | "itoa", | ||
| 304 | "memchr", | ||
| 305 | "serde", | ||
| 306 | "serde_core", | ||
| 307 | "zmij", | ||
| 308 | ] | ||
| 309 | |||
| 310 | [[package]] | ||
| 311 | name = "serde_yaml" | ||
| 312 | version = "0.9.34+deprecated" | ||
| 313 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 314 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" | ||
| 315 | dependencies = [ | ||
| 316 | "indexmap", | ||
| 317 | "itoa", | ||
| 318 | "ryu", | ||
| 319 | "serde", | ||
| 320 | "unsafe-libyaml", | ||
| 321 | ] | ||
| 322 | |||
| 323 | [[package]] | ||
| 324 | name = "sha2" | ||
| 325 | version = "0.10.9" | ||
| 326 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 327 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" | ||
| 328 | dependencies = [ | ||
| 329 | "cfg-if", | ||
| 330 | "cpufeatures", | ||
| 331 | "digest", | ||
| 332 | ] | ||
| 333 | |||
| 334 | [[package]] | ||
| 335 | name = "strsim" | ||
| 336 | version = "0.11.1" | ||
| 337 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 338 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" | ||
| 339 | |||
| 340 | [[package]] | ||
| 341 | name = "syn" | ||
| 342 | version = "2.0.117" | ||
| 343 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 344 | checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" | ||
| 345 | dependencies = [ | ||
| 346 | "proc-macro2", | ||
| 347 | "quote", | ||
| 348 | "unicode-ident", | ||
| 349 | ] | ||
| 350 | |||
| 351 | [[package]] | ||
| 352 | name = "typenum" | ||
| 353 | version = "1.19.0" | ||
| 354 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 355 | checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" | ||
| 356 | |||
| 357 | [[package]] | ||
| 358 | name = "unicode-ident" | ||
| 359 | version = "1.0.24" | ||
| 360 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 361 | checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" | ||
| 362 | |||
| 363 | [[package]] | ||
| 364 | name = "unsafe-libyaml" | ||
| 365 | version = "0.2.11" | ||
| 366 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 367 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" | ||
| 368 | |||
| 369 | [[package]] | ||
| 370 | name = "utf8parse" | ||
| 371 | version = "0.2.2" | ||
| 372 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 373 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" | ||
| 374 | |||
| 375 | [[package]] | ||
| 376 | name = "version_check" | ||
| 377 | version = "0.9.5" | ||
| 378 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 379 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | ||
| 380 | |||
| 381 | [[package]] | ||
| 382 | name = "wasm-bindgen" | ||
| 383 | version = "0.2.114" | ||
| 384 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 385 | checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" | ||
| 386 | dependencies = [ | ||
| 387 | "cfg-if", | ||
| 388 | "once_cell", | ||
| 389 | "rustversion", | ||
| 390 | "wasm-bindgen-macro", | ||
| 391 | "wasm-bindgen-shared", | ||
| 392 | ] | ||
| 393 | |||
| 394 | [[package]] | ||
| 395 | name = "wasm-bindgen-macro" | ||
| 396 | version = "0.2.114" | ||
| 397 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 398 | checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" | ||
| 399 | dependencies = [ | ||
| 400 | "quote", | ||
| 401 | "wasm-bindgen-macro-support", | ||
| 402 | ] | ||
| 403 | |||
| 404 | [[package]] | ||
| 405 | name = "wasm-bindgen-macro-support" | ||
| 406 | version = "0.2.114" | ||
| 407 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 408 | checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" | ||
| 409 | dependencies = [ | ||
| 410 | "bumpalo", | ||
| 411 | "proc-macro2", | ||
| 412 | "quote", | ||
| 413 | "syn", | ||
| 414 | "wasm-bindgen-shared", | ||
| 415 | ] | ||
| 416 | |||
| 417 | [[package]] | ||
| 418 | name = "wasm-bindgen-shared" | ||
| 419 | version = "0.2.114" | ||
| 420 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 421 | checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" | ||
| 422 | dependencies = [ | ||
| 423 | "unicode-ident", | ||
| 424 | ] | ||
| 425 | |||
| 426 | [[package]] | ||
| 427 | name = "windows-link" | ||
| 428 | version = "0.2.1" | ||
| 429 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 430 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" | ||
| 431 | |||
| 432 | [[package]] | ||
| 433 | name = "windows-sys" | ||
| 434 | version = "0.61.2" | ||
| 435 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 436 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" | ||
| 437 | dependencies = [ | ||
| 438 | "windows-link", | ||
| 439 | ] | ||
| 440 | |||
| 441 | [[package]] | ||
| 442 | name = "zmij" | ||
| 443 | version = "1.0.21" | ||
| 444 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 445 | checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | [package] | ||
| 2 | name = "openapi-extractor-rs" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2024" | ||
| 5 | |||
| 6 | [lib] | ||
| 7 | crate-type = ["cdylib", "rlib"] | ||
| 8 | |||
| 9 | [dependencies] | ||
| 10 | clap = { version = "4.5.40", features = ["derive"] } | ||
| 11 | serde = { version = "1.0.228", features = ["derive"] } | ||
| 12 | serde_json = "1.0.145" | ||
| 13 | serde_yaml = "0.9.34" | ||
| 14 | sha2 = "0.10.9" | ||
| 15 | |||
| 16 | [target.'cfg(target_arch = "wasm32")'.dependencies] | ||
| 17 | wasm-bindgen = "0.2.114" | ||
| @@ -0,0 +1,20 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | set -euo pipefail | ||
| 3 | |||
| 4 | ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" | ||
| 5 | CRATE_DIR="$ROOT_DIR/openapi-extractor-rs" | ||
| 6 | OUT_DIR="$ROOT_DIR/src/openapi-extractor-wasm" | ||
| 7 | |||
| 8 | cd "$CRATE_DIR" | ||
| 9 | |||
| 10 | rustup target add wasm32-unknown-unknown >/dev/null | ||
| 11 | |||
| 12 | cargo build --release --target wasm32-unknown-unknown | ||
| 13 | |||
| 14 | wasm-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 | |||
| 20 | rm -rf "$CRATE_DIR/target" | ||
| @@ -0,0 +1,1018 @@ | |||
| 1 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}; | ||
| 2 | |||
| 3 | use serde::Serialize; | ||
| 4 | use serde_json::{Map, Value, json}; | ||
| 5 | use sha2::{Digest, Sha256}; | ||
| 6 | |||
| 7 | #[cfg(target_arch = "wasm32")] | ||
| 8 | use wasm_bindgen::prelude::*; | ||
| 9 | |||
| 10 | const OPEN_API_HTTP_METHODS: [&str; 8] = [ | ||
| 11 | "get", | ||
| 12 | "put", | ||
| 13 | "post", | ||
| 14 | "delete", | ||
| 15 | "patch", | ||
| 16 | "head", | ||
| 17 | "options", | ||
| 18 | "trace", | ||
| 19 | ]; | ||
| 20 | |||
| 21 | const OPEN_API_PARAMETER_LOCATIONS: [&str; 4] = ["path", "query", "header", "cookie"]; | ||
| 22 | |||
| 23 | #[derive(Serialize, Clone)] | ||
| 24 | struct OpenApiToolParameter { | ||
| 25 | name: String, | ||
| 26 | location: String, | ||
| 27 | required: bool, | ||
| 28 | } | ||
| 29 | |||
| 30 | #[derive(Serialize, Clone)] | ||
| 31 | #[serde(rename_all = "camelCase")] | ||
| 32 | struct OpenApiToolRequestBody { | ||
| 33 | required: bool, | ||
| 34 | content_types: Vec<String>, | ||
| 35 | } | ||
| 36 | |||
| 37 | #[derive(Serialize, Clone)] | ||
| 38 | #[serde(rename_all = "camelCase")] | ||
| 39 | struct 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")] | ||
| 48 | struct 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")] | ||
| 59 | struct 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")] | ||
| 69 | struct 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")] | ||
| 81 | struct 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")] | ||
| 90 | struct 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")] | ||
| 101 | struct 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")] | ||
| 115 | struct 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")] | ||
| 134 | struct 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 | |||
| 142 | pub 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] | ||
| 159 | pub 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 | |||
| 167 | fn 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 | |||
| 180 | fn as_object(value: &Value) -> Option<&Map<String, Value>> { | ||
| 181 | value.as_object() | ||
| 182 | } | ||
| 183 | |||
| 184 | fn 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 | |||
| 193 | fn 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 | |||
| 208 | fn 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 | |||
| 250 | fn 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 | |||
| 259 | fn 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 | |||
| 263 | fn 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 | |||
| 284 | fn 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 | |||
| 289 | fn 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 | |||
| 302 | fn 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 | |||
| 312 | fn 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 | |||
| 327 | fn 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 | |||
| 349 | fn 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 | |||
| 364 | fn 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 | |||
| 402 | fn 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 | |||
| 406 | fn 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 | |||
| 440 | fn 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 | |||
| 444 | fn 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 | |||
| 452 | fn 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 | |||
| 478 | fn 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 | |||
| 515 | fn 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 | |||
| 523 | fn 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 | |||
| 547 | fn 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 | |||
| 573 | fn 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 | |||
| 583 | fn 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 | |||
| 588 | fn 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 | |||
| 593 | fn 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 | |||
| 611 | fn 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 | |||
| 664 | fn 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 | |||
| 705 | fn 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 | |||
| 740 | fn 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 | |||
| 760 | fn 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 | |||
| 784 | fn 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 | |||
| 809 | fn 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 | |||
| 845 | fn 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 | |||
| 873 | fn build_operation_id(operation: &Map<String, Value>) -> Option<String> { | ||
| 874 | as_trimmed_string(operation.get("operationId")) | ||
| 875 | } | ||
| 876 | |||
| 877 | fn 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 | |||
| 893 | fn 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 | |||
| 901 | fn 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 | |||
| 913 | fn 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 | |||
| 917 | fn 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 | |||
| 933 | fn 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 | } | ||
| @@ -0,0 +1,58 @@ | |||
| 1 | use std::fs; | ||
| 2 | use std::io::{self, Read, Write}; | ||
| 3 | |||
| 4 | use clap::Parser; | ||
| 5 | |||
| 6 | #[derive(Parser, Debug)] | ||
| 7 | #[command(name = "openapi-extractor-rs")] | ||
| 8 | struct 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 | |||
| 19 | fn main() { | ||
| 20 | if let Err(error) = run() { | ||
| 21 | eprintln!("{error}"); | ||
| 22 | std::process::exit(1); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | fn 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 | |||
| 48 | fn 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 | } | ||
| @@ -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" |
| @@ -6,11 +6,19 @@ export { · 5 unmodified lines | |||
| 6 | OpenApiExtractionError, | 6 | OpenApiExtractionError, |
| 7 | extractOpenApiManifest, | 7 | extractOpenApiManifest, |
| 8 | } from "./openapi-extraction"; | 8 | } from "./openapi-extraction"; |
| 9 | export { | ||
| 10 | compileOpenApiToolDefinitions, | ||
| 11 | openApiProviderDataJsonFromDefinition, | ||
| 12 | OpenApiToolDefinitionSchema, | ||
| 13 | type OpenApiToolDefinition, | ||
| 14 | } from "./openapi-definitions"; | ||
| 15 | export { buildOpenApiToolPresentation, type OpenApiToolPresentation } from "./openapi-tool-presentation"; | ||
| 9 | export { | 16 | export { |
| 10 | OpenApiToolInvocationError, | 17 | OpenApiToolInvocationError, |
| 11 | createOpenApiToolsFromManifest, | 18 | createOpenApiToolsFromManifest, |
| 12 | createOpenApiToolsFromSpec, | 19 | createOpenApiToolsFromSpec, |
| 13 | } from "./openapi-tools"; | 20 | } from "./openapi-tools"; |
| 21 | export { resolveSchemaJsonWithRefHints, resolveTypingSchemasWithRefHints } from "./openapi-schema-refs"; | ||
| 14 | export { | 22 | export { |
| 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"; |
| 51 | export type { | ||
| 52 | OpenApiExample, | ||
| 53 | OpenApiParameterDocumentation, | ||
| 54 | OpenApiRequestBodyDocumentation, | ||
| 55 | OpenApiResponseDocumentation, | ||
| 56 | OpenApiToolDocumentation, | ||
| 57 | } from "./openapi-types"; | ||
| @@ -0,0 +1,254 @@ | |||
| 1 | import { Schema } from "effect"; | ||
| 2 | |||
| 3 | import { | ||
| 4 | DiscoveryTypingPayloadSchema, | ||
| 5 | OpenApiHttpMethodSchema, | ||
| 6 | OpenApiInvocationPayloadSchema, | ||
| 7 | OpenApiToolDocumentationSchema, | ||
| 8 | type OpenApiExtractedTool, | ||
| 9 | type OpenApiToolManifest, | ||
| 10 | } from "./openapi-types"; | ||
| 11 | |||
| 12 | const VERSION_SEGMENT_REGEX = /^v\d+(?:[._-]\d+)?$/i; | ||
| 13 | const IGNORED_PATH_SEGMENTS = new Set(["api"]); | ||
| 14 | |||
| 15 | const 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 | |||
| 24 | const normalizeWord = (value: string): string => value.toLowerCase(); | ||
| 25 | |||
| 26 | const 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 | |||
| 36 | const toPascalCase = (value: string): string => { | ||
| 37 | const camel = toCamelCase(value); | ||
| 38 | return `${camel[0]?.toUpperCase() ?? ""}${camel.slice(1)}`; | ||
| 39 | }; | ||
| 40 | |||
| 41 | const 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 | |||
| 50 | const pathSegmentsFromTemplate = (pathTemplate: string): Array<string> => | ||
| 51 | pathTemplate | ||
| 52 | .split("/") | ||
| 53 | .map((segment) => segment.trim()) | ||
| 54 | .filter((segment) => segment.length > 0); | ||
| 55 | |||
| 56 | const isPathParameterSegment = (segment: string): boolean => | ||
| 57 | segment.startsWith("{") && segment.endsWith("}"); | ||
| 58 | |||
| 59 | const deriveVersionSegment = (pathTemplate: string): string | undefined => | ||
| 60 | pathSegmentsFromTemplate(pathTemplate) | ||
| 61 | .map((segment) => segment.toLowerCase()) | ||
| 62 | .find((segment) => VERSION_SEGMENT_REGEX.test(segment)); | ||
| 63 | |||
| 64 | const 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 | |||
| 83 | const splitOperationIdSegments = (value: string): Array<string> => | ||
| 84 | value | ||
| 85 | .split(/[/.]+/) | ||
| 86 | .map((segment) => segment.trim()) | ||
| 87 | .filter((segment) => segment.length > 0); | ||
| 88 | |||
| 89 | const 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 | |||
| 102 | const 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 | |||
| 117 | const 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 | |||
| 126 | const defaultDescription = (tool: OpenApiExtractedTool): string => | ||
| 127 | tool.description ?? `${tool.method.toUpperCase()} ${tool.path}`; | ||
| 128 | |||
| 129 | export 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 | |||
| 147 | export type OpenApiToolDefinition = typeof OpenApiToolDefinitionSchema.Type; | ||
| 148 | |||
| 149 | type MutableOpenApiToolDefinition = { | ||
| 150 | -readonly [Key in keyof OpenApiToolDefinition]: OpenApiToolDefinition[Key]; | ||
| 151 | }; | ||
| 152 | |||
| 153 | const 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 | |||
| 206 | export 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 | |||
| 238 | export 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 | }); | ||
| @@ -0,0 +1,118 @@ | |||
| 1 | import { describe, expect, it } from "@effect/vitest"; | ||
| 2 | import * as Effect from "effect/Effect"; | ||
| 3 | |||
| 4 | import { extractOpenApiManifest } from "./openapi-extraction"; | ||
| 5 | import { createOpenApiToolsFromManifest } from "./openapi-tools"; | ||
| 6 | import type { OpenApiJsonObject } from "./openapi-types"; | ||
| 7 | |||
| 8 | const VERCEL_OPENAPI_SPEC_URL = "https://openapi.vercel.sh/"; | ||
| 9 | |||
| 10 | const 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 | |||
| 23 | type ParsedInputSchema = { | ||
| 24 | type?: string; | ||
| 25 | properties?: Record<string, unknown>; | ||
| 26 | required?: Array<string>; | ||
| 27 | }; | ||
| 28 | |||
| 29 | const parseInputSchema = (inputSchemaJson: string | undefined): ParsedInputSchema | null => | ||
| 30 | inputSchemaJson ? (JSON.parse(inputSchemaJson) as ParsedInputSchema) : null; | ||
| 31 | |||
| 32 | const 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 | |||
| 75 | describe("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 | }); | ||
Binary file not shown.
| @@ -0,0 +1,135 @@ | |||
| 1 | type JsonObject = Record<string, unknown>; | ||
| 2 | |||
| 3 | type DiscoveryTypingLike = { | ||
| 4 | inputSchemaJson?: string; | ||
| 5 | outputSchemaJson?: string; | ||
| 6 | }; | ||
| 7 | |||
| 8 | const isJsonObject = (value: unknown): value is JsonObject => | ||
| 9 | typeof value === "object" && value !== null && !Array.isArray(value); | ||
| 10 | |||
| 11 | const parseJson = (value: string): unknown | null => { | ||
| 12 | try { | ||
| 13 | return JSON.parse(value) as unknown; | ||
| 14 | } catch { | ||
| 15 | return null; | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | |||
| 19 | const 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 | |||
| 89 | export 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 | |||
| 120 | export 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 | }); | ||
| @@ -0,0 +1,106 @@ | |||
| 1 | import { typeSignatureFromSchemaJson } from "@executor-v3/codemode-core"; | ||
| 2 | |||
| 3 | import { | ||
| 4 | resolveTypingSchemasWithRefHints, | ||
| 5 | } from "./openapi-schema-refs"; | ||
| 6 | import type { | ||
| 7 | OpenApiExample, | ||
| 8 | OpenApiToolDocumentation, | ||
| 9 | OpenApiToolManifest, | ||
| 10 | } from "./openapi-types"; | ||
| 11 | import type { OpenApiToolDefinition } from "./openapi-definitions"; | ||
| 12 | |||
| 13 | const parseJson = (value: string): unknown | undefined => { | ||
| 14 | try { | ||
| 15 | return JSON.parse(value) as unknown; | ||
| 16 | } catch { | ||
| 17 | return undefined; | ||
| 18 | } | ||
| 19 | }; | ||
| 20 | |||
| 21 | const firstExample = ( | ||
| 22 | examples: ReadonlyArray<OpenApiExample> | undefined, | ||
| 23 | ): OpenApiExample | undefined => examples?.[0]; | ||
| 24 | |||
| 25 | const 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 | |||
| 57 | const buildExampleOutputJson = ( | ||
| 58 | documentation: OpenApiToolDocumentation | undefined, | ||
| 59 | ): string | undefined => firstExample(documentation?.response?.examples)?.valueJson; | ||
| 60 | |||
| 61 | export 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 | |||
| 71 | export 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 | }; | ||
| @@ -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 | }); |
| @@ -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 | ||
| 17 | import { | ||
| 18 | compileOpenApiToolDefinitions, | ||
| 19 | type OpenApiToolDefinition, | ||
| 20 | } from "./openapi-definitions"; | ||
| 21 | import { buildOpenApiToolPresentation } from "./openapi-tool-presentation"; | ||
| 17 | import { | 22 | import { |
| 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 | ||
| 43 | const BLOCKED_RESPONSE_HEADER_NAMES = new Set([ | ||
| 44 | "authorization", | ||
| 45 | "authentication-info", | ||
| 46 | "cookie", | ||
| 47 | "proxy-authenticate", | ||
| 48 | "proxy-authorization", | ||
| 49 | "set-cookie", | ||
| 50 | "set-cookie2", | ||
| 51 | "www-authenticate", | ||
| 52 | "x-api-key", | ||
| 53 | "x-auth-token", | ||
| 54 | "x-csrf-token", | ||
| 55 | ]); | ||
| 56 | |||
| 57 | const NOISY_RESPONSE_HEADER_NAMES = new Set([ | ||
| 58 | "alt-svc", | ||
| 59 | "cf-ray", | ||
| 60 | "server", | ||
| 61 | "traceparent", | ||
| 62 | "tracestate", | ||
| 63 | "via", | ||
| 64 | "x-cache", | ||
| 65 | "x-cache-hits", | ||
| 66 | "x-powered-by", | ||
| 67 | "x-request-id", | ||
| 68 | "x-runtime", | ||
| 69 | "x-served-by", | ||
| 70 | "x-trace-id", | ||
| 71 | ]); | ||
| 72 | |||
| 73 | const NOISY_RESPONSE_HEADER_PREFIXES = [ | ||
| 74 | "cf-", | ||
| 75 | "trace-", | ||
| 76 | "x-amz-cf-", | ||
| 77 | "x-b3-", | ||
| 78 | "x-cloud-trace-", | ||
| 79 | ]; | ||
| 80 | |||
| 38 | const isRecord = (value: unknown): value is Record<string, unknown> => | 81 | const 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 | ||
| 247 | const shouldDropResponseHeader = (headerName: string): boolean => | ||
| 248 | BLOCKED_RESPONSE_HEADER_NAMES.has(headerName) | ||
| 249 | || NOISY_RESPONSE_HEADER_NAMES.has(headerName) | ||
| 250 | || NOISY_RESPONSE_HEADER_PREFIXES.some((prefix) => headerName.startsWith(prefix)); | ||
| 251 | |||
| 252 | const sanitizeResponseHeaders = ( | ||
| 253 | headers: Readonly<Record<string, string>>, | ||
| 254 | ): Record<string, string> => { | ||
| 255 | const sanitized: Record<string, string> = {}; | ||
| 256 | |||
| 257 | for (const [rawName, rawValue] of Object.entries(headers)) { | ||
| 258 | const name = rawName.trim().toLowerCase(); | ||
| 259 | if (name.length === 0 || shouldDropResponseHeader(name)) { | ||
| 260 | continue; | ||
| 261 | } | ||
| 262 | |||
| 263 | sanitized[name] = rawValue.length > 4000 ? `${rawValue.slice(0, 4000)}...` : rawValue; | ||
| 264 | } | ||
| 265 | |||
| 266 | return sanitized; | ||
| 267 | }; | ||
| 268 | |||
| 204 | const decodeHttpClientResponseBody = ( | 269 | const 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 _R | 273 | infer _Requirements |
| 209 | > | 274 | > |
| 210 | ? A | 275 | ? 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 | 310 | export type CreateOpenApiToolsFromManifestInput = { | |
| 246 | type 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 | ||
| 432 | const createToolPath = (namespace: string | undefined, definition: OpenApiToolDefinition): string => | ||
| 433 | namespace ? `${namespace}.${definition.toolId}` : definition.toolId; | ||
| 434 | |||
| 368 | export const createOpenApiToolsFromManifest = ( | 435 | export 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 = namespace | 448 | 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 | OpenApiExtractionError | 534 | 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 | ); |
| @@ -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. | ||
| 17 | export type OpenApiSpecInput = string | OpenAPISpec | OpenApiJsonObject; | 16 | export type OpenApiSpecInput = string | OpenAPISpec | OpenApiJsonObject; |
| 18 | 17 | ||
| 19 | export const OPEN_API_HTTP_METHODS = [ | 18 | export 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 | ||
| 66 | export const OpenApiExampleSchema = Schema.Struct({ | ||
| 67 | valueJson: Schema.String, | ||
| 68 | mediaType: Schema.optional(Schema.String), | ||
| 69 | label: Schema.optional(Schema.String), | ||
| 70 | }); | ||
| 71 | |||
| 72 | export 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 | |||
| 80 | export const OpenApiRequestBodyDocumentationSchema = Schema.Struct({ | ||
| 81 | description: Schema.optional(Schema.String), | ||
| 82 | examples: Schema.optional(Schema.Array(OpenApiExampleSchema)), | ||
| 83 | }); | ||
| 84 | |||
| 85 | export 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 | |||
| 92 | export 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 | |||
| 67 | export const OpenApiExtractedToolSchema = Schema.Struct({ | 100 | export 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 | ||
| 78 | export const OpenApiToolManifestSchema = Schema.Struct({ | 114 | export 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 | |||
| 93 | export type OpenApiToolRequestBody = typeof OpenApiToolRequestBodySchema.Type; | 129 | export type OpenApiToolRequestBody = typeof OpenApiToolRequestBodySchema.Type; |
| 94 | export type OpenApiInvocationPayload = typeof OpenApiInvocationPayloadSchema.Type; | 130 | export type OpenApiInvocationPayload = typeof OpenApiInvocationPayloadSchema.Type; |
| 95 | export type DiscoveryTypingPayload = typeof DiscoveryTypingPayloadSchema.Type; | 131 | export type DiscoveryTypingPayload = typeof DiscoveryTypingPayloadSchema.Type; |
| 132 | export type OpenApiExample = typeof OpenApiExampleSchema.Type; | ||
| 133 | export type OpenApiParameterDocumentation = typeof OpenApiParameterDocumentationSchema.Type; | ||
| 134 | export type OpenApiRequestBodyDocumentation = typeof OpenApiRequestBodyDocumentationSchema.Type; | ||
| 135 | export type OpenApiResponseDocumentation = typeof OpenApiResponseDocumentationSchema.Type; | ||
| 136 | export type OpenApiToolDocumentation = typeof OpenApiToolDocumentationSchema.Type; | ||
| 96 | export type OpenApiExtractedTool = typeof OpenApiExtractedToolSchema.Type; | 137 | export type OpenApiExtractedTool = typeof OpenApiExtractedToolSchema.Type; |
| 97 | export type OpenApiToolManifest = typeof OpenApiToolManifestSchema.Type; | 138 | export type OpenApiToolManifest = typeof OpenApiToolManifestSchema.Type; |
| @@ -0,0 +1,37 @@ | |||
| 1 | ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_raw_tool_id" text;--> statement-breakpoint | ||
| 2 | ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_operation_id" text;--> statement-breakpoint | ||
| 3 | ALTER TABLE "tool_artifacts" ADD COLUMN "openapi_tags_json" text;--> statement-breakpoint | ||
| 4 | UPDATE "tool_artifacts" | ||
| 5 | SET | ||
| 6 | "openapi_raw_tool_id" = "tool_id", | ||
| 7 | "openapi_operation_id" = "tool_id", | ||
| 8 | "openapi_tags_json" = '[]' | ||
| 9 | WHERE "provider_kind" = 'openapi';--> statement-breakpoint | ||
| 10 | ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_mcp_shape_check";--> statement-breakpoint | ||
| 11 | ALTER TABLE "tool_artifacts" DROP CONSTRAINT "tool_artifacts_openapi_shape_check";--> statement-breakpoint | ||
| 12 | ALTER TABLE "tool_artifacts" | ||
| 13 | ADD 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 | ||
| 26 | ALTER TABLE "tool_artifacts" | ||
| 27 | ADD 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 | ); | ||
| @@ -0,0 +1 @@ | |||
| 1 | ALTER TABLE "sources" ADD COLUMN "source_document_text" text; | ||
| @@ -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", |
| @@ -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 | ||
| 73 | const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema); | 77 | const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema); |
| 74 | const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema); | 78 | const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema); |
| 79 | const toolPathParam = HttpApiSchema.param("toolPath", Schema.String); | ||
| 75 | 80 | ||
| 76 | const CredentialPageUrlParamsSchema = Schema.Struct({ | 81 | const 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") {} |
| @@ -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"; |
| 20 | import { | ||
| 21 | discoverSourceInspectionTools, | ||
| 22 | getSourceInspection, | ||
| 23 | getSourceInspectionToolDetail, | ||
| 24 | } from "../../runtime/source-inspection"; | ||
| 20 | import { | 25 | import { |
| 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))( |
| @@ -6,8 +6,8 @@ import { · 5 unmodified lines | |||
| 6 | } from "@effect/platform"; | 6 | } from "@effect/platform"; |
| 7 | import * as Effect from "effect/Effect"; | 7 | import * as Effect from "effect/Effect"; |
| 8 | 8 | ||
| 9 | import { ControlPlaneApi } from "./api"; | 9 | import { ControlPlaneApi } from "./api/api"; |
| 10 | import { ControlPlaneAuthHeaders } from "./runtime"; | 10 | import { ControlPlaneAuthHeaders } from "./runtime/actor-resolver"; |
| 11 | 11 | ||
| 12 | export const createControlPlaneClient = (input: { | 12 | export const createControlPlaneClient = (input: { |
| 13 | baseUrl: string; | 13 | baseUrl: string; |
| @@ -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, |
| @@ -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 | ||
| @@ -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 null | 240 | and ${table.openApiMethod} is null |
| 237 | and ${table.openApiPathTemplate} is null | 241 | and ${table.openApiPathTemplate} is null |
| 238 | and ${table.openApiOperationHash} is null | 242 | 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 null | 246 | 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 null | 255 | and ${table.openApiPathTemplate} is not null |
| 249 | and ${table.openApiOperationHash} is not null | 256 | 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 | ], |
| @@ -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, |
| @@ -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", |
| @@ -57,6 +57,7 @@ export * from "./schema-type-signature"; · 56 unmodified lines | |||
| 57 | export * from "./source-auth-service"; | 57 | export * from "./source-auth-service"; |
| 58 | export * from "./store"; | 58 | export * from "./store"; |
| 59 | export * from "./workspace-execution-environment"; | 59 | export * from "./workspace-execution-environment"; |
| 60 | export * from "./source-inspection"; | ||
| 60 | 61 | ||
| 61 | export type RuntimeControlPlaneOptions = { | 62 | export type RuntimeControlPlaneOptions = { |
| 62 | actorResolver?: ControlPlaneActorResolverShape; | 63 | actorResolver?: ControlPlaneActorResolverShape; |
| @@ -114,7 +115,7 @@ const createRuntimeLayerFromContext = ( · 51 unmodified lines | |||
| 114 | | RuntimeExecutionResolverService | 115 | | RuntimeExecutionResolverService |
| 115 | >; | 116 | >; |
| 116 | 117 | ||
| 117 | return Layer.succeedContext(runtimeContext); | 118 | return Layer.succeedContext(runtimeContext) as RuntimeControlPlaneLayer; |
| 118 | }; | 119 | }; |
| 119 | 120 | ||
| 120 | export const createRuntimeControlPlaneLayer = ( | 121 | export const createRuntimeControlPlaneLayer = ( |
| @@ -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 | ||
| 442 | const upsertSecretMaterial = (rows: SqlControlPlaneRows, input: { | 449 | const upsertSecretMaterial = (rows: SqlControlPlaneRows, input: { |
| @@ -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, |
| @@ -0,0 +1,520 @@ | |||
| 1 | import { typeSignatureFromSchemaJson } from "@executor-v3/codemode-core"; | ||
| 2 | import { | ||
| 3 | buildOpenApiToolPresentation, | ||
| 4 | compileOpenApiToolDefinitions, | ||
| 5 | extractOpenApiManifest, | ||
| 6 | type OpenApiToolDefinition, | ||
| 7 | type OpenApiToolManifest, | ||
| 8 | } from "@executor-v3/codemode-openapi"; | ||
| 9 | import type { | ||
| 10 | Source, | ||
| 11 | SourceId, | ||
| 12 | SourceInspection, | ||
| 13 | SourceInspectionDiscoverPayload, | ||
| 14 | SourceInspectionDiscoverResult, | ||
| 15 | SourceInspectionDiscoverResultItem, | ||
| 16 | SourceInspectionToolDetail, | ||
| 17 | SourceInspectionToolSummary, | ||
| 18 | StoredSourceRecord, | ||
| 19 | WorkspaceId, | ||
| 20 | } from "#schema"; | ||
| 21 | import { | ||
| 22 | ControlPlaneNotFoundError, | ||
| 23 | ControlPlaneStorageError, | ||
| 24 | } from "#api"; | ||
| 25 | import * as Effect from "effect/Effect"; | ||
| 26 | import * as Option from "effect/Option"; | ||
| 27 | |||
| 28 | import { operationErrors } from "./operation-errors"; | ||
| 29 | import { projectSourceFromStorage } from "./source-definitions"; | ||
| 30 | import { ControlPlaneStore, type ControlPlaneStoreShape } from "./store"; | ||
| 31 | import { namespaceFromSourceName } from "./tool-artifacts"; | ||
| 32 | |||
| 33 | const sourceInspectOps = { | ||
| 34 | bundle: operationErrors("sources.inspect.bundle"), | ||
| 35 | tool: operationErrors("sources.inspect.tool"), | ||
| 36 | discover: operationErrors("sources.inspect.discover"), | ||
| 37 | } as const; | ||
| 38 | |||
| 39 | const asPrettyJson = (value: unknown): string => JSON.stringify(value, null, 2); | ||
| 40 | |||
| 41 | const tokenize = (value: string): Array<string> => | ||
| 42 | value | ||
| 43 | .trim() | ||
| 44 | .toLowerCase() | ||
| 45 | .split(/[^a-z0-9]+/) | ||
| 46 | .filter(Boolean); | ||
| 47 | |||
| 48 | const 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 | |||
| 65 | type InspectionToolRecord = { | ||
| 66 | summary: SourceInspectionToolSummary; | ||
| 67 | detail: SourceInspectionToolDetail; | ||
| 68 | searchText: string; | ||
| 69 | }; | ||
| 70 | |||
| 71 | type 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 | |||
| 81 | const 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 | |||
| 128 | const 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 | |||
| 180 | const 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 | |||
| 227 | const 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 | |||
| 271 | const 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 | |||
| 311 | const 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 | |||
| 338 | const 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 | |||
| 407 | const 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 | |||
| 425 | export 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 | |||
| 451 | export 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 | |||
| 482 | export 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 | ); | ||
| @@ -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 | ); |
| @@ -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"; |
| 11 | import { | 11 | import { extractOpenApiManifest } from "@executor-v3/codemode-openapi"; |
| 12 | extractOpenApiManifest, | ||
| 13 | type OpenApiExtractedTool, | ||
| 14 | } from "@executor-v3/codemode-openapi"; | ||
| 15 | import type { SqlControlPlaneRows } from "#persistence"; | 12 | import type { SqlControlPlaneRows } from "#persistence"; |
| 16 | import { | 13 | import { |
| 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"; |
| 24 | import * as Effect from "effect/Effect"; | 21 | import * as Effect from "effect/Effect"; |
| 22 | import * as Option from "effect/Option"; | ||
| 25 | 23 | ||
| 26 | export type ResolveSourceSecretMaterial = ( | 24 | export 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 | ||
| 108 | const 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 | ||
| 174 | const shouldIndexSource = (source: Source): boolean => | 110 | const shouldIndexSource = (source: Source): boolean => |
| 175 | source.enabled | 111 | 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 | ), |
| @@ -25,6 +25,7 @@ import * as Effect from "effect/Effect"; · 24 unmodified lines | |||
| 25 | import * as Exit from "effect/Exit"; | 25 | import * as Exit from "effect/Exit"; |
| 26 | import * as Layer from "effect/Layer"; | 26 | import * as Layer from "effect/Layer"; |
| 27 | import * as Scope from "effect/Scope"; | 27 | import * as Scope from "effect/Scope"; |
| 28 | import * as Option from "effect/Option"; | ||
| 28 | import { Schema } from "effect"; | 29 | import { Schema } from "effect"; |
| 29 | import { z } from "zod/v4"; | 30 | import { 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 | ||
| 417 | const persistConnectedGithubSearchArtifacts = (input: { | 422 | const 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 | ||
| 528 | const makeResolver = (persistence: SqlControlPlanePersistence) => | 531 | const 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?.inputSchemaJson | 846 | const inputSchema = described?.inputSchemaJson |
| 840 | ? JSON.parse(described.inputSchemaJson) as Record<string, unknown> | 847 | ? JSON.parse(described.inputSchemaJson) as Record<string, unknown> |
| @@ -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"; |
| 12 | import { | 13 | import { |
| 13 | createSdkMcpConnector, | 14 | createSdkMcpConnector, |
| 14 | createMcpToolsFromManifest, | 15 | createMcpToolsFromManifest, |
| 15 | } from "@executor-v3/codemode-mcp"; | 16 | } from "@executor-v3/codemode-mcp"; |
| 16 | import { | 17 | import { |
| 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"; |
| 19 | import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc"; | 25 | import { makeInProcessExecutor } from "@executor-v3/runtime-local-inproc"; |
| 20 | import { | 26 | import { |
| @@ -35,7 +41,7 @@ import type { · 14 unmodified lines | |||
| 35 | ResolveExecutionEnvironment, | 41 | ResolveExecutionEnvironment, |
| 36 | } from "./execution-state"; | 42 | } from "./execution-state"; |
| 37 | import { createExecutorToolMap } from "./executor-tools"; | 43 | import { createExecutorToolMap } from "./executor-tools"; |
| 38 | import { projectSourceFromStorage } from "./source-definitions"; | 44 | import { projectSourceFromStorage, projectSourcesFromStorage } from "./source-definitions"; |
| 39 | import { | 45 | import { |
| 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 | |||
| 61 | const LOW_SIGNAL_QUERY_TOKENS = new Set([ | 68 | const 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 | ||
| 210 | const toDescriptor = (input: { | 217 | const catalogNamespaceFromPath = (path: string): string => { |
| 218 | const [first, second] = path.split("."); | ||
| 219 | return second ? `${first}.${second}` : first; | ||
| 220 | }; | ||
| 221 | |||
| 222 | const 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 | ||
| 245 | type OpenApiWorkspaceTool = { | ||
| 246 | path: ToolPath; | ||
| 247 | source: Source; | ||
| 248 | manifest: OpenApiToolManifest; | ||
| 249 | definition: OpenApiToolDefinition; | ||
| 250 | descriptor: ToolDescriptor; | ||
| 251 | searchNamespace: string; | ||
| 252 | searchText: string; | ||
| 253 | }; | ||
| 254 | |||
| 255 | const 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 | |||
| 226 | const loadSourceById = (input: { | 364 | const 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 = includeSchemas | 489 | 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 = includeSchemas | 538 | 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.toolArtifacts | 670 | 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 | ); |
| @@ -11,3 +11,4 @@ export * from "./models/source-auth-session"; · 10 unmodified lines | |||
| 11 | export * from "./models/policy"; | 11 | export * from "./models/policy"; |
| 12 | export * from "./models/local-installation"; | 12 | export * from "./models/local-installation"; |
| 13 | export * from "./models/execution"; | 13 | export * from "./models/execution"; |
| 14 | export * from "./models/source-inspection"; | ||
| @@ -0,0 +1,85 @@ | |||
| 1 | import { OpenApiHttpMethodSchema } from "@executor-v3/codemode-openapi"; | ||
| 2 | import { Schema } from "effect"; | ||
| 3 | |||
| 4 | import { SourceSchema } from "./source"; | ||
| 5 | |||
| 6 | export const SourceInspectionPipelineKindSchema = Schema.Literal( | ||
| 7 | "openapi", | ||
| 8 | "persisted", | ||
| 9 | ); | ||
| 10 | |||
| 11 | export 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 | |||
| 29 | export 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 | |||
| 40 | export 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 | |||
| 51 | export const SourceInspectionDiscoverPayloadSchema = Schema.Struct({ | ||
| 52 | query: Schema.String, | ||
| 53 | limit: Schema.optional(Schema.Number), | ||
| 54 | }); | ||
| 55 | |||
| 56 | export 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 | |||
| 65 | export 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 | |||
| 73 | export type SourceInspectionPipelineKind = | ||
| 74 | typeof SourceInspectionPipelineKindSchema.Type; | ||
| 75 | export type SourceInspectionToolSummary = | ||
| 76 | typeof SourceInspectionToolSummarySchema.Type; | ||
| 77 | export type SourceInspection = typeof SourceInspectionSchema.Type; | ||
| 78 | export type SourceInspectionToolDetail = | ||
| 79 | typeof SourceInspectionToolDetailSchema.Type; | ||
| 80 | export type SourceInspectionDiscoverPayload = | ||
| 81 | typeof SourceInspectionDiscoverPayloadSchema.Type; | ||
| 82 | export type SourceInspectionDiscoverResultItem = | ||
| 83 | typeof SourceInspectionDiscoverResultItemSchema.Type; | ||
| 84 | export type SourceInspectionDiscoverResult = | ||
| 85 | typeof SourceInspectionDiscoverResultSchema.Type; | ||
| @@ -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, |
| @@ -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, |
| @@ -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: { |
| @@ -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 | } | ||
| @@ -0,0 +1,325 @@ | |||
| 1 | import { Atom, Result } from "@effect-atom/atom"; | ||
| 2 | import { RegistryProvider, useAtomValue } from "@effect-atom/atom-react"; | ||
| 3 | import type { | ||
| 4 | LocalInstallation, | ||
| 5 | Source, | ||
| 6 | SourceInspection, | ||
| 7 | SourceInspectionDiscoverResult, | ||
| 8 | SourceInspectionToolDetail, | ||
| 9 | } from "@executor-v3/control-plane/schema"; | ||
| 10 | import * as Cause from "effect/Cause"; | ||
| 11 | import * as Effect from "effect/Effect"; | ||
| 12 | import * as React from "react"; | ||
| 13 | |||
| 14 | const DEFAULT_EXECUTOR_API_BASE_URL = "http://127.0.0.1:8788"; | ||
| 15 | const ACCOUNT_HEADER = "x-executor-account-id"; | ||
| 16 | const PLACEHOLDER_WORKSPACE_ID = "ws_placeholder" as Source["workspaceId"]; | ||
| 17 | const PLACEHOLDER_ACCOUNT_ID = "acc_placeholder"; | ||
| 18 | const PLACEHOLDER_SOURCE_ID = "src_placeholder" as Source["id"]; | ||
| 19 | |||
| 20 | let apiBaseUrl = | ||
| 21 | typeof window !== "undefined" && typeof window.location?.origin === "string" | ||
| 22 | ? window.location.origin | ||
| 23 | : DEFAULT_EXECUTOR_API_BASE_URL; | ||
| 24 | |||
| 25 | type AtomKeyPart = string | number | boolean | null | undefined; | ||
| 26 | |||
| 27 | const encodeAtomKey = (parts: ReadonlyArray<AtomKeyPart>): string => JSON.stringify(parts); | ||
| 28 | |||
| 29 | const decodeAtomKey = <T extends ReadonlyArray<AtomKeyPart>>(key: string): T => JSON.parse(key) as T; | ||
| 30 | |||
| 31 | const causeMessage = (cause: Cause.Cause<unknown>): Error => | ||
| 32 | new Error(Cause.pretty(cause)); | ||
| 33 | |||
| 34 | const 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 | |||
| 57 | const localInstallationAtom = Atom.make( | ||
| 58 | Effect.promise(() => requestJson<LocalInstallation>({ path: "/v1/local/installation" })), | ||
| 59 | ).pipe(Atom.keepAlive); | ||
| 60 | |||
| 61 | const 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 | |||
| 78 | const 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 | |||
| 95 | const 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 | |||
| 112 | const 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 | |||
| 142 | export type Loadable<T> = | ||
| 143 | | { status: "loading" } | ||
| 144 | | { status: "error"; error: Error } | ||
| 145 | | { status: "ready"; data: T }; | ||
| 146 | |||
| 147 | type WorkspaceContext = { | ||
| 148 | installation: LocalInstallation; | ||
| 149 | workspaceId: Source["workspaceId"]; | ||
| 150 | accountId: string; | ||
| 151 | }; | ||
| 152 | |||
| 153 | const 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 | |||
| 173 | const 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 | |||
| 185 | const 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 | |||
| 190 | const 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 | |||
| 209 | const 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 | |||
| 231 | export const setExecutorApiBaseUrl = (baseUrl: string): void => { | ||
| 232 | apiBaseUrl = baseUrl; | ||
| 233 | }; | ||
| 234 | |||
| 235 | export const ExecutorReactProvider = RegistryProvider; | ||
| 236 | |||
| 237 | export const useLocalInstallation = (): Loadable<LocalInstallation> => | ||
| 238 | useLoadableAtom(localInstallationAtom); | ||
| 239 | |||
| 240 | export 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 | |||
| 251 | export 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 | |||
| 270 | export 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 | |||
| 293 | export 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 | |||
| 319 | export type { | ||
| 320 | LocalInstallation, | ||
| 321 | Source, | ||
| 322 | SourceInspection, | ||
| 323 | SourceInspectionDiscoverResult, | ||
| 324 | SourceInspectionToolDetail, | ||
| 325 | }; | ||
| @@ -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 | } | ||
| @@ -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 | } | ||
| @@ -4,6 +4,12 @@ import { join } from "node:path"; · 3 unmodified lines | |||
| 4 | export const DEFAULT_SERVER_PORT = 8788; | 4 | export const DEFAULT_SERVER_PORT = 8788; |
| 5 | export const DEFAULT_SERVER_HOST = "127.0.0.1"; | 5 | export const DEFAULT_SERVER_HOST = "127.0.0.1"; |
| 6 | export const DEFAULT_SERVER_BASE_URL = `http://${DEFAULT_SERVER_HOST}:${DEFAULT_SERVER_PORT}`; | 6 | export const DEFAULT_SERVER_BASE_URL = `http://${DEFAULT_SERVER_HOST}:${DEFAULT_SERVER_PORT}`; |
| 7 | export const DEFAULT_LOCAL_DATA_DIR = join(homedir(), ".local", "share", "executor-v3", "control-plane"); | 7 | export const DEFAULT_LOCAL_DATA_DIR = join( |
| 8 | homedir(), | ||
| 9 | ".local", | ||
| 10 | "share", | ||
| 11 | "executor-v3", | ||
| 12 | "control-plane", | ||
| 13 | ); | ||
| 8 | export const SERVER_START_TIMEOUT_MS = 5_000; | 14 | export const SERVER_START_TIMEOUT_MS = 5_000; |
| 9 | export const SERVER_POLL_INTERVAL_MS = 100; | 15 | export const SERVER_POLL_INTERVAL_MS = 100; |
| @@ -0,0 +1,237 @@ | |||
| 1 | import { access, mkdir } from "node:fs/promises"; | ||
| 2 | import { dirname, extname, resolve } from "node:path"; | ||
| 3 | |||
| 4 | import { HttpApiBuilder, HttpServer } from "@effect/platform"; | ||
| 5 | import { | ||
| 6 | createControlPlaneApiLayer, | ||
| 7 | createSqlControlPlaneRuntime, | ||
| 8 | type ResolveExecutionEnvironment, | ||
| 9 | type ResolveSecretMaterial, | ||
| 10 | type SqlControlPlaneRuntime, | ||
| 11 | } from "@executor-v3/control-plane"; | ||
| 12 | import * as Effect from "effect/Effect"; | ||
| 13 | import * as Layer from "effect/Layer"; | ||
| 14 | import type * as Scope from "effect/Scope"; | ||
| 15 | |||
| 16 | import { | ||
| 17 | DEFAULT_LOCAL_DATA_DIR, | ||
| 18 | DEFAULT_SERVER_HOST, | ||
| 19 | DEFAULT_SERVER_PORT, | ||
| 20 | } from "./config"; | ||
| 21 | |||
| 22 | export { | ||
| 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 | |||
| 31 | type StaticUiOptions = { | ||
| 32 | readonly assetsDir?: string; | ||
| 33 | readonly devServerUrl?: string; | ||
| 34 | }; | ||
| 35 | |||
| 36 | export type LocalExecutorServer = { | ||
| 37 | readonly runtime: SqlControlPlaneRuntime; | ||
| 38 | readonly port: number; | ||
| 39 | readonly host: string; | ||
| 40 | readonly baseUrl: string; | ||
| 41 | }; | ||
| 42 | |||
| 43 | export 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 | |||
| 52 | export 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 | |||
| 59 | const disposeRuntime = (runtime: SqlControlPlaneRuntime) => | ||
| 60 | Effect.promise(() => runtime.close()).pipe(Effect.orDie); | ||
| 61 | |||
| 62 | const 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 | |||
| 75 | const 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 | |||
| 81 | const fileExists = async (path: string): Promise<boolean> => { | ||
| 82 | try { | ||
| 83 | await access(path); | ||
| 84 | return true; | ||
| 85 | } catch { | ||
| 86 | return false; | ||
| 87 | } | ||
| 88 | }; | ||
| 89 | |||
| 90 | const wantsHtml = (request: Request): boolean => | ||
| 91 | request.headers.get("accept")?.includes("text/html") ?? false; | ||
| 92 | |||
| 93 | const 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 | |||
| 131 | const isApiRequest = (request: Request): boolean => { | ||
| 132 | const pathname = new URL(request.url).pathname; | ||
| 133 | return pathname === "/v1" || pathname.startsWith("/v1/"); | ||
| 134 | }; | ||
| 135 | |||
| 136 | export 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 | |||
| 177 | export 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 | |||
| 218 | export 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 | ); | ||
| @@ -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 | } | ||
| @@ -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 | } | ||