- Move @openassistant/reacord to assistant/packages/reacord/ (only actively used legacy package) - Delete entire assistant/legacy/openassistant/ directory (~515MB of superseded code) - Delete unused assistant/packages/agent-executor-adapter/ (9-line wrapper never imported) - Update workspace entries in root and assistant package.json
Unified · Split
| @@ -2,5 +2,4 @@ · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | Assistant-side code lives here. |
| 4 | 4 | |
| 5 | −- `packages/agent-executor-adapter`: lightweight adapter for talking to the executor API. | |
| 6 | 5 | - `legacy/openassistant`: previous prototype code moved out of the executor-focused workspace. |
| @@ -1,344 +0,0 @@ | ||
| 1 | −# OpenAssistant — Architecture Plan | |
| 2 | − | |
| 3 | −## What This Is | |
| 4 | − | |
| 5 | −An AI assistant that takes natural language prompts and executes them as typed TypeScript code against registered tools. Unlike CLI-based agents (OpenClaw, ClawdBot), the agent generates TypeScript code rather than shell commands, giving precise control over what gets approved, what runs, and what the agent can access. | |
| 6 | − | |
| 7 | −## Example Prompts | |
| 8 | − | |
| 9 | −| Prompt | Task Type | Duration | | |
| 10 | −|---|---|---| | |
| 11 | −| "On AnswerOverflow/AnswerOverflow, close all issues older than 30 days" | One-shot | Seconds/minutes | | |
| 12 | −| "Every day at 9am check if my site has hit 10M visitors with PostHog and if it has let me know" | Recurring + conditional exit | Days/weeks | | |
| 13 | −| "Tell me how many people have subscribed every day at 5pm" | Recurring, no exit | Forever | | |
| 14 | −| "When I get an email, check if it's spam and if it is delete it" | Event-triggered | Forever | | |
| 15 | − | |
| 16 | −## Architecture | |
| 17 | − | |
| 18 | −``` | |
| 19 | − Clients (pull events, push decisions) | |
| 20 | − ────────────────────────────────────── | |
| 21 | − Discord bot Web UI CLI Slack | |
| 22 | − │ │ │ │ | |
| 23 | − └────────────┴────────┴───────┘ | |
| 24 | − │ | |
| 25 | − Event stream (SSE / WS) | |
| 26 | − + REST for decisions | |
| 27 | − │ | |
| 28 | − Server (gateway) | |
| 29 | − ────────────────────────────────────── | |
| 30 | − Bun.serve() | |
| 31 | − ├── SSE event stream per task | |
| 32 | − ├── REST API (/api/tasks, /api/approvals, /api/hooks) | |
| 33 | − ├── Workflow .well-known routes (durability) | |
| 34 | − ├── Agent loop (Claude + codemode runner) | |
| 35 | − ├── Tool plugins (hand-written + MCP + OpenAPI) | |
| 36 | − └── Persistence (bun:sqlite for MVP) | |
| 37 | −``` | |
| 38 | − | |
| 39 | −### Key Principle: Server Emits Events, Clients Subscribe | |
| 40 | − | |
| 41 | −The server never POSTs to clients. Instead: | |
| 42 | −1. Every task produces a stream of `TaskEvent`s (persisted, resumable) | |
| 43 | −2. Clients subscribe via SSE/WebSocket/polling | |
| 44 | −3. When a tool needs approval, the server emits an `approval_request` event | |
| 45 | −4. Any client renders approval UI and POSTs the decision to `POST /api/approvals/:callId` | |
| 46 | −5. The server picks up the decision and continues execution | |
| 47 | − | |
| 48 | −This is client-agnostic. Discord, web, CLI — they all implement the same protocol. | |
| 49 | − | |
| 50 | −## Execution Sandbox | |
| 51 | − | |
| 52 | −LLM-generated code runs in a `node:vm` sandbox with `Object.create(null)` as the context base: | |
| 53 | − | |
| 54 | −- **Allowed:** `tools` (injected), `JSON`, `Math`, `Date`, `console` (no-op) | |
| 55 | −- **Blocked:** `fetch`, `process`, `Bun`, `require`, `import()`, all Node/Bun APIs | |
| 56 | −- **Constructor chain escape:** Safe — returns the sandbox global which has nothing dangerous | |
| 57 | −- **Prototype pollution:** Stays in sandbox, does not leak to host | |
| 58 | −- **Execution timeout:** `vm.runInContext` `timeout` option | |
| 59 | −- **Memory limits:** Not controllable with `node:vm` (would need rquickjs, deferred to post-MVP) | |
| 60 | − | |
| 61 | −Tool functions are wrapped so `toString()` doesn't leak source code. | |
| 62 | − | |
| 63 | −## Durability — Vercel Workflow | |
| 64 | − | |
| 65 | −[Vercel Workflow DevKit](https://github.com/vercel/workflow) handles: | |
| 66 | −- **Durable scheduling:** `sleep("1 day")` suspends the workflow, consumes zero compute, survives restarts | |
| 67 | −- **Durable event waiting:** `createHook()` + `for await` loop for webhook-triggered tasks | |
| 68 | −- **Crash recovery:** Workflow state is persisted; on restart, replays from the event log | |
| 69 | −- **Observability:** Every workflow run, every step, every retry is traced | |
| 70 | − | |
| 71 | −### Three Workflow Templates (compile-time) | |
| 72 | − | |
| 73 | −All workflows are generic. The `prompt` argument (interpreted by Claude at runtime) determines behavior. | |
| 74 | − | |
| 75 | −```ts | |
| 76 | −// workflows/one-shot.ts — "close all stale issues" | |
| 77 | −export async function oneShotWorkflow(prompt, requesterId, channelId, taskId) { | |
| 78 | − "use workflow"; | |
| 79 | − const result = await runAgentTurn(prompt, requesterId, channelId, taskId); | |
| 80 | − return result; | |
| 81 | −} | |
| 82 | − | |
| 83 | −// workflows/recurring.ts — "every day at 9am check X" | |
| 84 | −export async function recurringWorkflow(prompt, requesterId, channelId, taskId, intervalMs) { | |
| 85 | − "use workflow"; | |
| 86 | − while (true) { | |
| 87 | − await sleep(intervalMs); | |
| 88 | − await runAgentTurn(prompt, requesterId, channelId, taskId); | |
| 89 | − } | |
| 90 | −} | |
| 91 | − | |
| 92 | −// workflows/event-triggered.ts — "when I get an email, do X" | |
| 93 | −export async function eventTriggeredWorkflow(prompt, requesterId, channelId, taskId, hookToken) { | |
| 94 | − "use workflow"; | |
| 95 | − const hook = createHook<Record<string, unknown>>({ token: hookToken }); | |
| 96 | − for await (const payload of hook) { | |
| 97 | − const enrichedPrompt = `${prompt}\n\nEvent data:\n${JSON.stringify(payload, null, 2)}`; | |
| 98 | − await runAgentTurn(enrichedPrompt, requesterId, channelId, taskId); | |
| 99 | − } | |
| 100 | −} | |
| 101 | −``` | |
| 102 | − | |
| 103 | −### The Shared Step | |
| 104 | − | |
| 105 | −```ts | |
| 106 | −async function runAgentTurn(prompt, requesterId, channelId, taskId) { | |
| 107 | − "use step"; | |
| 108 | − // Full runtime access here — this is where the agent loop runs | |
| 109 | − // Creates runner, calls Claude, executes code, collects receipts | |
| 110 | − // Emits TaskEvents to the stream (including approval_request) | |
| 111 | − // Polls for approval decisions from clients | |
| 112 | −} | |
| 113 | −``` | |
| 114 | − | |
| 115 | −## Tool System | |
| 116 | − | |
| 117 | −### defineTool API | |
| 118 | − | |
| 119 | −```ts | |
| 120 | −import { z } from "zod"; | |
| 121 | − | |
| 122 | −defineTool({ | |
| 123 | − description: "Close a GitHub issue", | |
| 124 | − approval: "required", | |
| 125 | − args: z.object({ | |
| 126 | − owner: z.string(), | |
| 127 | − repo: z.string(), | |
| 128 | − issueNumber: z.number(), | |
| 129 | − }), | |
| 130 | − returns: z.object({ | |
| 131 | − number: z.number(), | |
| 132 | − title: z.string(), | |
| 133 | − state: z.string(), | |
| 134 | − }), | |
| 135 | − run: async (input) => { ... }, | |
| 136 | − formatApproval: (input) => ({ | |
| 137 | − title: `Close ${input.owner}/${input.repo}#${input.issueNumber}`, | |
| 138 | − }), | |
| 139 | −}); | |
| 140 | −``` | |
| 141 | − | |
| 142 | −- `args` (Zod schema) → validates input at invocation + generates TypeScript declarations for typechecker + generates LLM prompt guidance | |
| 143 | −- `returns` (Zod schema) → generates TypeScript return type | |
| 144 | −- `description` → LLM prompt guidance | |
| 145 | −- `approval: "auto" | "required"` → determines approval flow | |
| 146 | −- `run` → plain `async` function (not Effect in the public API) | |
| 147 | −- `formatApproval` → optional, provides rich approval presentation | |
| 148 | − | |
| 149 | −### Three Tool Sources | |
| 150 | − | |
| 151 | −All produce the same `ToolTree` + TypeScript declarations. The runner doesn't know the difference. | |
| 152 | − | |
| 153 | −**1. Hand-written plugins** | |
| 154 | −```ts | |
| 155 | −export const githubTools = { | |
| 156 | − github: { | |
| 157 | − issues: { | |
| 158 | − close: defineTool({ ... }), | |
| 159 | − } | |
| 160 | − } | |
| 161 | −}; | |
| 162 | −``` | |
| 163 | − | |
| 164 | −**2. MCP servers** (auto-generated at startup) | |
| 165 | −```ts | |
| 166 | −// Config: { type: "mcp", name: "answeroverflow", url: "https://www.answeroverflow.com/mcp" } | |
| 167 | −// → Introspects tools/list | |
| 168 | −// → Generates: tools.answeroverflow.search_answeroverflow({ query }) | |
| 169 | −// → Each tool wraps client.callTool(name, input) | |
| 170 | −``` | |
| 171 | − | |
| 172 | −**3. OpenAPI specs** (auto-generated at startup) | |
| 173 | −```ts | |
| 174 | −// Config: { type: "openapi", name: "fastspring", spec: "https://...", auth: { ... } } | |
| 175 | −// → Parses spec, groups by tag | |
| 176 | −// → GET → approval: "auto", POST/PUT/DELETE → approval: "required" | |
| 177 | −// → Generates: tools.fastspring.accounts.get({ account_id }) | |
| 178 | −``` | |
| 179 | − | |
| 180 | −### Tool Source Config | |
| 181 | − | |
| 182 | −```ts | |
| 183 | −const toolSources = [ | |
| 184 | − createGitHubPlugin(), | |
| 185 | − { type: "mcp", name: "answeroverflow", url: "https://www.answeroverflow.com/mcp" }, | |
| 186 | − { type: "openapi", name: "fastspring", spec: "https://...", auth: { type: "basic", credentials: "FASTSPRING_CREDENTIALS" } }, | |
| 187 | −]; | |
| 188 | −``` | |
| 189 | − | |
| 190 | −## Approval Flow | |
| 191 | − | |
| 192 | −### One-shot tasks (user is active in conversation) | |
| 193 | − | |
| 194 | −1. Agent generates code that calls `tools.github.issues.close(...)` | |
| 195 | −2. Runner hits the approval gate for `approval: "required"` tools | |
| 196 | −3. Runner emits `{ type: "approval_request", id, toolPath, preview }` to the task event stream | |
| 197 | −4. Runner polls `getApprovalDecision(callId)` in a loop | |
| 198 | −5. Client (Discord/web/CLI) sees the event, renders approval UI | |
| 199 | −6. User clicks Approve → client POSTs to `POST /api/approvals/:callId` | |
| 200 | −7. Decision is written to DB | |
| 201 | −8. Runner's poll picks it up, execution continues | |
| 202 | − | |
| 203 | −### Background tasks (recurring/event-triggered, no active conversation) | |
| 204 | − | |
| 205 | −Same flow, but the client sends a proactive DM/notification with approval buttons. The user approves asynchronously. | |
| 206 | − | |
| 207 | −### Batch approval (future enhancement) | |
| 208 | − | |
| 209 | −For tasks that need many approvals (close 15 issues), a batch approval mode: show all proposed actions upfront, user approves once. | |
| 210 | − | |
| 211 | −## Task Event Stream | |
| 212 | − | |
| 213 | −Every task run produces events. Clients subscribe and render them however they want. | |
| 214 | − | |
| 215 | −```ts | |
| 216 | −type TaskEvent = | |
| 217 | − | { type: "status"; message: string } | |
| 218 | − | { type: "code_generated"; code: string } | |
| 219 | − | { type: "approval_request"; id: string; toolPath: string; input: unknown; preview: ApprovalPresentation } | |
| 220 | − | { type: "approval_resolved"; id: string; decision: "approved" | "denied" } | |
| 221 | − | { type: "tool_result"; id: string; toolPath: string; status: "succeeded" | "failed" | "denied"; preview?: string } | |
| 222 | − | { type: "agent_message"; text: string } | |
| 223 | − | { type: "error"; error: string } | |
| 224 | − | { type: "completed" } | |
| 225 | −``` | |
| 226 | − | |
| 227 | −## Task Management | |
| 228 | − | |
| 229 | −The agent has task management tools (so the user speaks natural language): | |
| 230 | − | |
| 231 | −``` | |
| 232 | −tools.tasks.list() → active tasks for this user | |
| 233 | −tools.tasks.createRecurring(...) → starts a recurring workflow | |
| 234 | −tools.tasks.createEventTrigger(...) → starts an event-triggered workflow | |
| 235 | −tools.tasks.cancel({ taskId }) → cancels a workflow run | |
| 236 | −tools.tasks.history({ taskId }) → recent events from a task's stream | |
| 237 | −``` | |
| 238 | − | |
| 239 | −## Persistence | |
| 240 | − | |
| 241 | −**bun:sqlite** for MVP. Single file, zero ops. Used for: | |
| 242 | −- Workflow world (adapter needed, or use Workflow's local filesystem world) | |
| 243 | −- Approval decisions (polling table) | |
| 244 | −- Task registry (what tasks exist, who owns them) | |
| 245 | −- Auth credentials (env vars for MVP, DB profiles later) | |
| 246 | − | |
| 247 | −## Agent Loop | |
| 248 | − | |
| 249 | −1. Build system prompt from tool descriptions + type declarations + task context | |
| 250 | −2. Call Claude with the prompt and the `run_code` tool | |
| 251 | −3. Claude responds with `run_code({ code: "..." })` | |
| 252 | −4. Typecheck the code against generated TypeScript declarations | |
| 253 | −5. If typecheck fails: feed error back to Claude, retry (up to 3 times) | |
| 254 | −6. Execute code in `node:vm` sandbox | |
| 255 | −7. Collect receipts from all tool calls | |
| 256 | −8. Feed receipts back to Claude | |
| 257 | −9. Claude may call `run_code` again, or produce final text response | |
| 258 | −10. Emit `agent_message` event to the stream | |
| 259 | − | |
| 260 | −## Effect Usage | |
| 261 | − | |
| 262 | −Effect is used throughout the codebase, properly: | |
| 263 | −- **Services + Layers** for dependency injection (database, config, clients) | |
| 264 | −- **Tagged errors** for typed error handling | |
| 265 | −- **Tracing spans** for observability | |
| 266 | −- **Fibers** where concurrency is needed | |
| 267 | − | |
| 268 | −The public `defineTool` API uses plain `async` functions — Effect is an internal implementation detail, not exposed to tool authors or the sandbox. | |
| 269 | − | |
| 270 | −## Server Routes | |
| 271 | − | |
| 272 | −``` | |
| 273 | −POST /.well-known/workflow/v1/flow Workflow flow handler | |
| 274 | −POST /.well-known/workflow/v1/step Workflow step handler | |
| 275 | −* /.well-known/workflow/v1/webhook/:token Workflow webhook handler | |
| 276 | − | |
| 277 | −POST /api/tasks Create a new task | |
| 278 | −GET /api/tasks List tasks for a user | |
| 279 | −GET /api/tasks/:id Get task status | |
| 280 | −GET /api/tasks/:id/events SSE stream of TaskEvents | |
| 281 | −POST /api/tasks/:id/cancel Cancel a task | |
| 282 | − | |
| 283 | −POST /api/approvals/:callId Resolve an approval decision | |
| 284 | − | |
| 285 | −POST /api/hooks/:token Webhook ingestion (external services POST here) | |
| 286 | −``` | |
| 287 | − | |
| 288 | −## Monorepo Layout | |
| 289 | − | |
| 290 | −``` | |
| 291 | −apps/ | |
| 292 | − server/ Bun.serve() — the brain | |
| 293 | − index.ts Routes: REST + Workflow + SSE + webhooks | |
| 294 | − workflows/ | |
| 295 | − one-shot.ts "use workflow" | |
| 296 | − recurring.ts "use workflow" | |
| 297 | − event-triggered.ts "use workflow" | |
| 298 | − steps.ts "use step" — shared (runAgentTurn, notifyUser) | |
| 299 | − | |
| 300 | − bot/ Discord client | |
| 301 | − index.ts discord.js, subscribes to event streams | |
| 302 | − views/ React components via Reacord | |
| 303 | − | |
| 304 | −packages/ | |
| 305 | − core/ | |
| 306 | − tools.ts defineTool, ToolTree, ToolCallReceipt types | |
| 307 | − runner.ts node:vm sandbox execution | |
| 308 | − typechecker.ts TypeScript type checking (auto-gen from Zod) | |
| 309 | − agent.ts Agent loop (Claude + run_code + retries) | |
| 310 | − events.ts TaskEvent types | |
| 311 | − approval.ts Approval types + logic | |
| 312 | − | |
| 313 | − tool-gen/ | |
| 314 | − mcp.ts MCP server introspection → ToolTree | |
| 315 | − openapi.ts OpenAPI spec parsing → ToolTree | |
| 316 | − type-gen.ts Zod schema → TypeScript declaration strings | |
| 317 | − | |
| 318 | − reacord/ (existing) React reconciler for Discord | |
| 319 | −``` | |
| 320 | − | |
| 321 | −## Build Order | |
| 322 | − | |
| 323 | −1. **packages/core** — defineTool, ToolTree, runner (node:vm), typechecker, agent loop, TaskEvent types. Tested with real Claude calls. | |
| 324 | −2. **packages/tool-gen** — MCP + OpenAPI generation. Uses existing parsers (@modelcontextprotocol/sdk, swagger-parser). | |
| 325 | −3. **apps/server** — Bun.serve + Workflow integration + REST API + SSE streams. | |
| 326 | −4. **apps/bot** — Discord client consuming event streams. | |
| 327 | − | |
| 328 | −## Testing | |
| 329 | − | |
| 330 | −Real tests, no mocks. Claude calls use Claude Max credentials. | |
| 331 | − | |
| 332 | −- **Unit tests:** Runner executes code in sandbox, collects receipts. Tool definitions validate with Zod. Typechecker catches bad code. | |
| 333 | −- **Integration tests:** Agent loop generates and executes code for simple tasks. Tool-gen produces correct ToolTree from MCP/OpenAPI specs. | |
| 334 | −- **E2E tests:** Full workflow: create task → subscribe to events → approve → get results. | |
| 335 | − | |
| 336 | −## What's Deferred (post-MVP) | |
| 337 | − | |
| 338 | −- Hard memory limits (rquickjs) | |
| 339 | −- Credential broker / ephemeral tokens | |
| 340 | −- Batch approval UX | |
| 341 | −- Ops web UI | |
| 342 | −- CLI client | |
| 343 | −- Plugin discovery from convention directories | |
| 344 | −- Auto-compaction for long conversation contexts | |
| @@ -1,35 +0,0 @@ | ||
| 1 | −# OpenAssistant | |
| 2 | − | |
| 3 | −## Install | |
| 4 | − | |
| 5 | −```bash | |
| 6 | −bun install | |
| 7 | −``` | |
| 8 | − | |
| 9 | −## Local MVP (remote executor mode) | |
| 10 | − | |
| 11 | −Start the executor (private code runner): | |
| 12 | − | |
| 13 | −```bash | |
| 14 | −bun run --filter '@openassistant/executor' dev | |
| 15 | −``` | |
| 16 | − | |
| 17 | −Start the server (agent + approvals + tool broker): | |
| 18 | − | |
| 19 | −```bash | |
| 20 | −OPENASSISTANT_EXECUTOR_URL=http://localhost:3001 \ | |
| 21 | −OPENASSISTANT_CALLBACK_BASE_URL=http://localhost:3000 \ | |
| 22 | −bun run --filter '@openassistant/server' dev | |
| 23 | −``` | |
| 24 | − | |
| 25 | −Or run all services together: | |
| 26 | − | |
| 27 | −```bash | |
| 28 | −bun run dev | |
| 29 | −``` | |
| 30 | − | |
| 31 | −## Notes | |
| 32 | − | |
| 33 | −- Untrusted generated code executes in the executor service. | |
| 34 | −- Tool calls are proxied back to the server via `/internal/runs/:runId/invoke`. | |
| 35 | −- Approval flow and secret-backed tool execution stay on the server side. | |
| @@ -1,25 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "name": "@openassistant/bot", | |
| 3 | − "private": true, | |
| 4 | − "type": "module", | |
| 5 | − "scripts": { | |
| 6 | − "with-env": "bun run --env-file=../../.env --", | |
| 7 | − "dev": "bun run with-env bun --hot src/index.ts", | |
| 8 | − "start": "bun run with-env bun src/index.ts", | |
| 9 | − "typecheck": "tsgo --noEmit", | |
| 10 | − "test": "bun test src/" | |
| 11 | − }, | |
| 12 | − "dependencies": { | |
| 13 | − "@openassistant/core": "workspace:*", | |
| 14 | − "@openassistant/server": "workspace:*", | |
| 15 | − "@openassistant/reacord": "workspace:*", | |
| 16 | − "@elysiajs/eden": "latest", | |
| 17 | − "discord.js": "^14.17.3", | |
| 18 | − "effect": "latest", | |
| 19 | − "react": "^19.0.0" | |
| 20 | − }, | |
| 21 | − "devDependencies": { | |
| 22 | − "@types/bun": "latest", | |
| 23 | − "@types/react": "^19.0.0" | |
| 24 | − } | |
| 25 | −} | |
| @@ -1,64 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * /ask command handler | |
| 3 | − * | |
| 4 | − * Defers the interaction, creates a task on the server, | |
| 5 | − * and mounts a self-updating <TaskMessage /> that subscribes | |
| 6 | − * to the server's SSE stream via Eden Treaty. | |
| 7 | − */ | |
| 8 | − | |
| 9 | −import type { ChatInputCommandInteraction, CommandInteraction } from "discord.js"; | |
| 10 | −import type { Client as ApiClient } from "@openassistant/server/client"; | |
| 11 | −import { unwrap } from "@openassistant/server/client"; | |
| 12 | −import type { ReacordInstance } from "@openassistant/reacord"; | |
| 13 | −import { Effect, Runtime } from "effect"; | |
| 14 | −import { TaskMessage } from "../views/task-message"; | |
| 15 | − | |
| 16 | −// --------------------------------------------------------------------------- | |
| 17 | −// Types | |
| 18 | −// --------------------------------------------------------------------------- | |
| 19 | − | |
| 20 | −interface AskCommandDeps { | |
| 21 | − readonly api: ApiClient; | |
| 22 | − readonly reacord: { | |
| 23 | − reply: (interaction: CommandInteraction, content: React.ReactNode) => Effect.Effect<ReacordInstance>; | |
| 24 | − }; | |
| 25 | −} | |
| 26 | − | |
| 27 | −// --------------------------------------------------------------------------- | |
| 28 | −// Command handler | |
| 29 | −// --------------------------------------------------------------------------- | |
| 30 | − | |
| 31 | −export async function handleAskCommand( | |
| 32 | − interaction: ChatInputCommandInteraction, | |
| 33 | − deps: AskCommandDeps, | |
| 34 | −): Promise<void> { | |
| 35 | − const prompt = interaction.options.getString("prompt", true); | |
| 36 | − const requesterId = interaction.user.id; | |
| 37 | − | |
| 38 | − // Defer immediately — Discord interactions expire after 3 seconds. | |
| 39 | − await interaction.deferReply(); | |
| 40 | − | |
| 41 | − // Create task on the server | |
| 42 | − let taskId: string; | |
| 43 | − let executionMode: "local" | "remote" = "local"; | |
| 44 | − try { | |
| 45 | − const data = await unwrap( | |
| 46 | − deps.api.api.tasks.post({ prompt, requesterId }), | |
| 47 | − ); | |
| 48 | − taskId = data.taskId; | |
| 49 | − executionMode = data.executionMode; | |
| 50 | − } catch (error) { | |
| 51 | − await interaction.editReply({ | |
| 52 | − content: `\u274c Failed to create task: ${error instanceof Error ? error.message : String(error)}`, | |
| 53 | − }); | |
| 54 | − return; | |
| 55 | − } | |
| 56 | − | |
| 57 | − // Mount the component — it handles its own SSE subscription and state | |
| 58 | − await Runtime.runPromise(Runtime.defaultRuntime)( | |
| 59 | − deps.reacord.reply( | |
| 60 | − interaction, | |
| 61 | − <TaskMessage taskId={taskId} prompt={prompt} api={deps.api} executionMode={executionMode} />, | |
| 62 | − ), | |
| 63 | − ); | |
| 64 | −} | |
| @@ -1,122 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * OpenAssistant Discord Bot | |
| 3 | − * | |
| 4 | − * Connects to Discord, registers slash commands, and renders | |
| 5 | − * task results using Reacord (React for Discord). | |
| 6 | − * | |
| 7 | − * Communicates with the server via Eden Treaty (type-safe HTTP client). | |
| 8 | − */ | |
| 9 | − | |
| 10 | −import { | |
| 11 | − Client, | |
| 12 | − GatewayIntentBits, | |
| 13 | − REST, | |
| 14 | − Routes, | |
| 15 | − SlashCommandBuilder, | |
| 16 | − type ChatInputCommandInteraction, | |
| 17 | −} from "discord.js"; | |
| 18 | −import { makeReacord } from "@openassistant/reacord"; | |
| 19 | −import { createClient } from "@openassistant/server/client"; | |
| 20 | −import { handleAskCommand } from "./commands/ask"; | |
| 21 | − | |
| 22 | −// --------------------------------------------------------------------------- | |
| 23 | −// Config | |
| 24 | −// --------------------------------------------------------------------------- | |
| 25 | − | |
| 26 | −const DISCORD_TOKEN = process.env["DISCORD_BOT_TOKEN"]; | |
| 27 | −if (!DISCORD_TOKEN) { | |
| 28 | − throw new Error("DISCORD_BOT_TOKEN is required"); | |
| 29 | −} | |
| 30 | − | |
| 31 | −const SERVER_URL = process.env["OPENASSISTANT_SERVER_URL"] ?? "http://localhost:3000"; | |
| 32 | − | |
| 33 | −// --------------------------------------------------------------------------- | |
| 34 | −// Discord client | |
| 35 | −// --------------------------------------------------------------------------- | |
| 36 | − | |
| 37 | −const client = new Client({ | |
| 38 | − intents: [ | |
| 39 | − GatewayIntentBits.Guilds, | |
| 40 | − GatewayIntentBits.GuildMessages, | |
| 41 | − ], | |
| 42 | −}); | |
| 43 | − | |
| 44 | −// --------------------------------------------------------------------------- | |
| 45 | −// Reacord | |
| 46 | −// --------------------------------------------------------------------------- | |
| 47 | − | |
| 48 | −const reacord = makeReacord(client, { maxInstances: 50 }); | |
| 49 | − | |
| 50 | −// --------------------------------------------------------------------------- | |
| 51 | −// Eden Treaty API client | |
| 52 | −// --------------------------------------------------------------------------- | |
| 53 | − | |
| 54 | −const api = createClient(SERVER_URL); | |
| 55 | − | |
| 56 | −// --------------------------------------------------------------------------- | |
| 57 | −// Slash command definitions | |
| 58 | −// --------------------------------------------------------------------------- | |
| 59 | − | |
| 60 | −const commands = [ | |
| 61 | − new SlashCommandBuilder() | |
| 62 | − .setName("ask") | |
| 63 | − .setDescription("Ask the AI assistant to do something") | |
| 64 | − .addStringOption((option) => | |
| 65 | − option | |
| 66 | − .setName("prompt") | |
| 67 | − .setDescription("What do you want the assistant to do?") | |
| 68 | − .setRequired(true), | |
| 69 | − ), | |
| 70 | − new SlashCommandBuilder() | |
| 71 | − .setName("clear") | |
| 72 | − .setDescription("Push chat history out of view"), | |
| 73 | −]; | |
| 74 | − | |
| 75 | −// --------------------------------------------------------------------------- | |
| 76 | −// Register commands on startup | |
| 77 | −// --------------------------------------------------------------------------- | |
| 78 | − | |
| 79 | −async function registerCommands() { | |
| 80 | − const rest = new REST().setToken(DISCORD_TOKEN!); | |
| 81 | − | |
| 82 | − try { | |
| 83 | − console.log("Registering slash commands..."); | |
| 84 | − await rest.put(Routes.applicationCommands(client.user!.id), { | |
| 85 | − body: commands.map((c) => c.toJSON()), | |
| 86 | − }); | |
| 87 | − console.log("Slash commands registered."); | |
| 88 | − } catch (error) { | |
| 89 | − console.error("Failed to register slash commands:", error); | |
| 90 | − } | |
| 91 | −} | |
| 92 | − | |
| 93 | −// --------------------------------------------------------------------------- | |
| 94 | −// Command handler | |
| 95 | −// --------------------------------------------------------------------------- | |
| 96 | − | |
| 97 | −client.on("interactionCreate", async (interaction) => { | |
| 98 | − if (!interaction.isChatInputCommand()) return; | |
| 99 | − | |
| 100 | − switch (interaction.commandName) { | |
| 101 | − case "ask": | |
| 102 | − await handleAskCommand(interaction, { api, reacord }); | |
| 103 | − break; | |
| 104 | − case "clear": | |
| 105 | − await interaction.reply({ content: `_${"\n".repeat(50)}_` }); | |
| 106 | − break; | |
| 107 | − default: | |
| 108 | − await interaction.reply({ content: `Unknown command: ${interaction.commandName}` }); | |
| 109 | − } | |
| 110 | −}); | |
| 111 | − | |
| 112 | −// --------------------------------------------------------------------------- | |
| 113 | −// Start | |
| 114 | −// --------------------------------------------------------------------------- | |
| 115 | − | |
| 116 | −client.once("clientReady", async () => { | |
| 117 | − console.log(`Logged in as ${client.user?.tag}`); | |
| 118 | − await registerCommands(); | |
| 119 | − console.log(`Connected to server at ${SERVER_URL}`); | |
| 120 | −}); | |
| 121 | − | |
| 122 | −client.login(DISCORD_TOKEN); | |
| @@ -1,522 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * TaskMessage — self-contained live-updating Discord message for a task. | |
| 3 | − * | |
| 4 | − * Subscribes to the server's SSE stream via Eden Treaty on mount, | |
| 5 | − * reduces TaskEvents into local state, and re-renders reactively. | |
| 6 | − * | |
| 7 | − * The command handler just mounts this component and walks away: | |
| 8 | − * <TaskMessage taskId={id} prompt={prompt} api={api} /> | |
| 9 | − */ | |
| 10 | − | |
| 11 | −import { useState, useEffect } from "react"; | |
| 12 | −import { | |
| 13 | − Container, | |
| 14 | − TextDisplay, | |
| 15 | − Separator, | |
| 16 | − ActionRow, | |
| 17 | − Button, | |
| 18 | − ModalButton, | |
| 19 | − Loading, | |
| 20 | − useInstance, | |
| 21 | − type ModalField, | |
| 22 | − type ModalFieldValues, | |
| 23 | −} from "@openassistant/reacord"; | |
| 24 | −import type { Client as ApiClient } from "@openassistant/server/client"; | |
| 25 | −import { unwrap } from "@openassistant/server/client"; | |
| 26 | −import type { TaskEvent } from "@openassistant/core/events"; | |
| 27 | −import type { ApprovalPresentation } from "@openassistant/core/tools"; | |
| 28 | − | |
| 29 | −// --------------------------------------------------------------------------- | |
| 30 | −// State | |
| 31 | −// --------------------------------------------------------------------------- | |
| 32 | − | |
| 33 | −interface PendingApproval { | |
| 34 | − readonly id: string; | |
| 35 | − readonly toolPath: string; | |
| 36 | − readonly input: unknown; | |
| 37 | − readonly preview: ApprovalPresentation; | |
| 38 | −} | |
| 39 | − | |
| 40 | −interface TaskState { | |
| 41 | − readonly status: "running" | "completed" | "failed" | "cancelled"; | |
| 42 | − readonly statusMessage: string; | |
| 43 | − readonly lastCode: string | null; | |
| 44 | − readonly toolResults: string[]; | |
| 45 | − readonly pendingApprovals: PendingApproval[]; | |
| 46 | − readonly agentMessage: string | null; | |
| 47 | − readonly error: string | null; | |
| 48 | − /** Whether an approval rule has been created for this task. */ | |
| 49 | − readonly hasRule: boolean; | |
| 50 | −} | |
| 51 | − | |
| 52 | −const INITIAL_STATE: TaskState = { | |
| 53 | − status: "running", | |
| 54 | − statusMessage: "Thinking...", | |
| 55 | − lastCode: null, | |
| 56 | − toolResults: [], | |
| 57 | − pendingApprovals: [], | |
| 58 | − agentMessage: null, | |
| 59 | − error: null, | |
| 60 | − hasRule: false, | |
| 61 | −}; | |
| 62 | − | |
| 63 | −function reduceEvent(state: TaskState, event: TaskEvent): TaskState { | |
| 64 | − switch (event.type) { | |
| 65 | − case "status": | |
| 66 | − return { ...state, statusMessage: event.message }; | |
| 67 | − | |
| 68 | − case "code_generated": | |
| 69 | − return { ...state, lastCode: event.code, statusMessage: "Running code..." }; | |
| 70 | − | |
| 71 | − case "approval_request": | |
| 72 | − // Deduplicate — if we already have this approval (e.g. from SSE replay), skip it | |
| 73 | − if (state.pendingApprovals.some((a) => a.id === event.id)) return state; | |
| 74 | − return { | |
| 75 | − ...state, | |
| 76 | − pendingApprovals: [ | |
| 77 | − ...state.pendingApprovals, | |
| 78 | − { id: event.id, toolPath: event.toolPath, input: event.input, preview: event.preview }, | |
| 79 | − ], | |
| 80 | − statusMessage: "Waiting for approval...", | |
| 81 | − }; | |
| 82 | − | |
| 83 | − case "approval_resolved": | |
| 84 | − return { | |
| 85 | − ...state, | |
| 86 | − pendingApprovals: state.pendingApprovals.filter((a) => a.id !== event.id), | |
| 87 | − statusMessage: event.decision === "approved" ? "Approved, continuing..." : "Denied.", | |
| 88 | − }; | |
| 89 | − | |
| 90 | − case "tool_result": { | |
| 91 | − const r = event.receipt; | |
| 92 | − const icon = r.status === "succeeded" ? "\u2705" : r.status === "denied" ? "\u26d4" : "\u274c"; | |
| 93 | − const shortName = formatToolName(r.toolPath); | |
| 94 | − const output = formatToolOutput(r.toolPath, r.outputPreview); | |
| 95 | − const line = `${icon} ${shortName}${output}`; | |
| 96 | − return { ...state, toolResults: [...state.toolResults, line] }; | |
| 97 | − } | |
| 98 | − | |
| 99 | − case "agent_message": | |
| 100 | − return { ...state, agentMessage: event.text, statusMessage: "Done" }; | |
| 101 | − | |
| 102 | − case "error": | |
| 103 | − return { ...state, status: "failed", error: event.error, statusMessage: "Failed" }; | |
| 104 | − | |
| 105 | − case "completed": | |
| 106 | − return { ...state, status: "completed", statusMessage: "Completed" }; | |
| 107 | − } | |
| 108 | −} | |
| 109 | − | |
| 110 | −// --------------------------------------------------------------------------- | |
| 111 | −// Component | |
| 112 | −// --------------------------------------------------------------------------- | |
| 113 | − | |
| 114 | −export interface TaskMessageProps { | |
| 115 | − readonly taskId: string; | |
| 116 | − readonly prompt: string; | |
| 117 | − readonly api: ApiClient; | |
| 118 | − readonly executionMode: "local" | "remote"; | |
| 119 | −} | |
| 120 | − | |
| 121 | −export function TaskMessage({ taskId, prompt, api, executionMode }: TaskMessageProps) { | |
| 122 | − const instance = useInstance(); | |
| 123 | − const [state, setState] = useState<TaskState>(INITIAL_STATE); | |
| 124 | − | |
| 125 | − // Subscribe to SSE stream on mount | |
| 126 | − useEffect(() => { | |
| 127 | − let cancelled = false; | |
| 128 | − | |
| 129 | − const subscribe = async () => { | |
| 130 | − try { | |
| 131 | − const { data: stream, error } = await api.api.tasks({ id: taskId }).events.get(); | |
| 132 | − | |
| 133 | − if (error || !stream) { | |
| 134 | − if (!cancelled) { | |
| 135 | − setState((s) => ({ ...s, status: "failed", error: "Failed to connect to event stream", statusMessage: "Failed" })); | |
| 136 | − } | |
| 137 | − return; | |
| 138 | − } | |
| 139 | − | |
| 140 | − for await (const sse of stream as AsyncIterable<{ event: string; data: TaskEvent }>) { | |
| 141 | − if (cancelled) break; | |
| 142 | − setState((s) => reduceEvent(s, sse.data)); | |
| 143 | − } | |
| 144 | − } catch (err) { | |
| 145 | − if (!cancelled) { | |
| 146 | − setState((s) => ({ | |
| 147 | − ...s, | |
| 148 | − status: "failed", | |
| 149 | − error: err instanceof Error ? err.message : String(err), | |
| 150 | − statusMessage: "Failed", | |
| 151 | − })); | |
| 152 | − } | |
| 153 | − } | |
| 154 | − }; | |
| 155 | − | |
| 156 | − subscribe(); | |
| 157 | − | |
| 158 | − return () => { | |
| 159 | − cancelled = true; | |
| 160 | − }; | |
| 161 | − }, [taskId]); | |
| 162 | − | |
| 163 | − // Deactivate buttons once done | |
| 164 | − useEffect(() => { | |
| 165 | − if (state.status !== "running") { | |
| 166 | − const timer = setTimeout(() => instance.deactivate(), 5000); | |
| 167 | − return () => clearTimeout(timer); | |
| 168 | − } | |
| 169 | − }, [state.status]); | |
| 170 | − | |
| 171 | − const isDone = state.status !== "running"; | |
| 172 | − const accentColor = isDone | |
| 173 | − ? state.status === "completed" ? 0x57f287 : 0xed4245 | |
| 174 | − : 0x5865f2; | |
| 175 | − | |
| 176 | − return ( | |
| 177 | − <Container accentColor={accentColor}> | |
| 178 | − <TextDisplay>{`${statusEmoji(state.status)} **${state.statusMessage}**`}</TextDisplay> | |
| 179 | − <TextDisplay>{`Runner: ${executionMode === "remote" ? "cloud executor" : "in-process local"}`}</TextDisplay> | |
| 180 | − <TextDisplay>{`> ${prompt.length > 200 ? prompt.slice(0, 200) + "..." : prompt}`}</TextDisplay> | |
| 181 | − | |
| 182 | − {state.lastCode && ( | |
| 183 | − <> | |
| 184 | − <Separator /> | |
| 185 | − <TextDisplay>{`\`\`\`ts\n${state.lastCode.length > 800 ? state.lastCode.slice(0, 800) + "\n// ..." : state.lastCode}\n\`\`\``}</TextDisplay> | |
| 186 | − </> | |
| 187 | − )} | |
| 188 | − | |
| 189 | − {state.toolResults.length > 0 && ( | |
| 190 | − <> | |
| 191 | − <Separator /> | |
| 192 | − <TextDisplay>{collapseToolResults(state.toolResults, 10).join("\n")}</TextDisplay> | |
| 193 | − </> | |
| 194 | − )} | |
| 195 | − | |
| 196 | − {state.pendingApprovals.map((approval) => ( | |
| 197 | − <ApprovalButtons | |
| 198 | − key={approval.id} | |
| 199 | − approval={approval} | |
| 200 | − api={api} | |
| 201 | − taskId={taskId} | |
| 202 | − hasRule={state.hasRule} | |
| 203 | − onRuleCreated={() => setState((s) => ({ ...s, hasRule: true }))} | |
| 204 | − /> | |
| 205 | − ))} | |
| 206 | − | |
| 207 | − {state.error && ( | |
| 208 | − <> | |
| 209 | − <Separator /> | |
| 210 | − <TextDisplay>{`\u274c **Error:** ${state.error.slice(0, 500)}`}</TextDisplay> | |
| 211 | − </> | |
| 212 | − )} | |
| 213 | − | |
| 214 | − {state.agentMessage && ( | |
| 215 | − <> | |
| 216 | − <Separator /> | |
| 217 | − <TextDisplay> | |
| 218 | − {state.agentMessage.length > 1800 | |
| 219 | − ? state.agentMessage.slice(0, 1800) + "..." | |
| 220 | − : state.agentMessage} | |
| 221 | − </TextDisplay> | |
| 222 | − </> | |
| 223 | − )} | |
| 224 | − | |
| 225 | − {state.status === "running" && state.pendingApprovals.length === 0 && ( | |
| 226 | − <Loading /> | |
| 227 | − )} | |
| 228 | − </Container> | |
| 229 | − ); | |
| 230 | −} | |
| 231 | − | |
| 232 | −// --------------------------------------------------------------------------- | |
| 233 | −// Approval buttons | |
| 234 | −// --------------------------------------------------------------------------- | |
| 235 | − | |
| 236 | −function ApprovalButtons({ | |
| 237 | − approval, | |
| 238 | − api, | |
| 239 | − taskId, | |
| 240 | − hasRule, | |
| 241 | − onRuleCreated, | |
| 242 | −}: { | |
| 243 | − approval: PendingApproval; | |
| 244 | − api: ApiClient; | |
| 245 | − taskId: string; | |
| 246 | − hasRule: boolean; | |
| 247 | − onRuleCreated: () => void; | |
| 248 | −}) { | |
| 249 | − const [resolved, setResolved] = useState(false); | |
| 250 | − | |
| 251 | − const handle = async (decision: "approved" | "denied") => { | |
| 252 | − setResolved(true); | |
| 253 | − try { | |
| 254 | − await unwrap(api.api.approvals({ callId: approval.id }).post({ decision })); | |
| 255 | − } catch (err) { | |
| 256 | − console.error(`[approval ${approval.id}]`, err); | |
| 257 | − } | |
| 258 | − }; | |
| 259 | − | |
| 260 | − // Extract field names from the input for the rule builder | |
| 261 | − const inputFields = extractFieldNames(approval.input); | |
| 262 | − | |
| 263 | − const ruleFields: ModalField[] = [ | |
| 264 | − { | |
| 265 | − type: "stringSelect", | |
| 266 | − id: "field", | |
| 267 | − label: "Field", | |
| 268 | − description: "Which input field to check", | |
| 269 | − placeholder: "Select a field...", | |
| 270 | − required: true, | |
| 271 | − options: inputFields.map((f) => ({ | |
| 272 | − label: f.name, | |
| 273 | − value: f.path, | |
| 274 | − description: f.preview ? `Current: ${f.preview}` : undefined, | |
| 275 | − })), | |
| 276 | − }, | |
| 277 | − { | |
| 278 | − type: "stringSelect", | |
| 279 | − id: "operator", | |
| 280 | − label: "Condition", | |
| 281 | − description: "How to compare the field value", | |
| 282 | − required: true, | |
| 283 | − options: [ | |
| 284 | − { label: "equals", value: "equals" }, | |
| 285 | − { label: "does not equal", value: "not_equals" }, | |
| 286 | − { label: "includes", value: "includes" }, | |
| 287 | − { label: "does not include", value: "not_includes" }, | |
| 288 | − ], | |
| 289 | − }, | |
| 290 | − { | |
| 291 | − type: "textInput", | |
| 292 | − id: "value", | |
| 293 | − label: "Value", | |
| 294 | − description: "Value to compare against", | |
| 295 | − placeholder: "e.g. rhys.dev", | |
| 296 | − style: "short", | |
| 297 | − required: true, | |
| 298 | − }, | |
| 299 | − { | |
| 300 | − type: "stringSelect", | |
| 301 | − id: "decision", | |
| 302 | − label: "Action", | |
| 303 | − description: "What to do when the rule matches", | |
| 304 | − required: true, | |
| 305 | − options: [ | |
| 306 | − { label: "Approve matching", value: "approved" }, | |
| 307 | − { label: "Deny matching", value: "denied" }, | |
| 308 | − ], | |
| 309 | − }, | |
| 310 | − ]; | |
| 311 | − | |
| 312 | − const handleRuleSubmit = async (values: ModalFieldValues) => { | |
| 313 | − const field = values.getStringSelect("field")?.[0]; | |
| 314 | − const operator = values.getStringSelect("operator")?.[0]; | |
| 315 | − const value = values.getTextInput("value"); | |
| 316 | − const decision = values.getStringSelect("decision")?.[0]; | |
| 317 | − | |
| 318 | − if (!field || !operator || value === undefined || !decision) return; | |
| 319 | − | |
| 320 | − try { | |
| 321 | − await unwrap( | |
| 322 | − api.api.tasks({ id: taskId })["approval-rules"].post({ | |
| 323 | − toolPath: approval.toolPath, | |
| 324 | − field, | |
| 325 | − operator: operator as "equals" | "not_equals" | "includes" | "not_includes", | |
| 326 | − value, | |
| 327 | − decision: decision as "approved" | "denied", | |
| 328 | − }), | |
| 329 | − ); | |
| 330 | − onRuleCreated(); | |
| 331 | − } catch (err) { | |
| 332 | − console.error(`[rule creation]`, err); | |
| 333 | − } | |
| 334 | − }; | |
| 335 | − | |
| 336 | − return ( | |
| 337 | − <> | |
| 338 | − <Separator /> | |
| 339 | − <TextDisplay> | |
| 340 | − {formatApprovalDisplay(approval)} | |
| 341 | − </TextDisplay> | |
| 342 | − {!resolved && ( | |
| 343 | − <ActionRow> | |
| 344 | − <Button label="Approve" style="success" onClick={() => handle("approved")} /> | |
| 345 | − <Button label="Deny" style="danger" onClick={() => handle("denied")} /> | |
| 346 | − {!hasRule && inputFields.length > 0 && ( | |
| 347 | − <ModalButton | |
| 348 | − label="Create Rule" | |
| 349 | − style="secondary" | |
| 350 | − modalTitle="Auto-approve/deny rule" | |
| 351 | − fields={ruleFields} | |
| 352 | − onSubmit={handleRuleSubmit} | |
| 353 | − /> | |
| 354 | − )} | |
| 355 | − </ActionRow> | |
| 356 | − )} | |
| 357 | − </> | |
| 358 | − ); | |
| 359 | −} | |
| 360 | − | |
| 361 | −// --------------------------------------------------------------------------- | |
| 362 | −// Field extraction for rule builder | |
| 363 | −// --------------------------------------------------------------------------- | |
| 364 | − | |
| 365 | −interface FieldInfo { | |
| 366 | − readonly name: string; | |
| 367 | − readonly path: string; | |
| 368 | − readonly preview?: string; | |
| 369 | −} | |
| 370 | − | |
| 371 | −function extractFieldNames(input: unknown, prefix = ""): FieldInfo[] { | |
| 372 | − if (!input || typeof input !== "object") return []; | |
| 373 | − const fields: FieldInfo[] = []; | |
| 374 | − | |
| 375 | − for (const [key, value] of Object.entries(input as Record<string, unknown>)) { | |
| 376 | − const path = prefix ? `${prefix}.${key}` : key; | |
| 377 | − if (value !== null && typeof value === "object" && !Array.isArray(value)) { | |
| 378 | − // Recurse into nested objects | |
| 379 | − fields.push(...extractFieldNames(value, path)); | |
| 380 | − } else { | |
| 381 | − const preview = value === undefined ? undefined : String(value).slice(0, 50); | |
| 382 | − fields.push({ name: path, path, preview }); | |
| 383 | − } | |
| 384 | − } | |
| 385 | − | |
| 386 | − return fields.slice(0, 25); // Discord select max 25 options | |
| 387 | −} | |
| 388 | − | |
| 389 | −// --------------------------------------------------------------------------- | |
| 390 | −// Helpers | |
| 391 | −// --------------------------------------------------------------------------- | |
| 392 | − | |
| 393 | −function statusEmoji(status: TaskState["status"]): string { | |
| 394 | − switch (status) { | |
| 395 | − case "running": return "\u23f3"; | |
| 396 | − case "completed": return "\u2705"; | |
| 397 | − case "failed": return "\u274c"; | |
| 398 | − case "cancelled": return "\u26d4"; | |
| 399 | − } | |
| 400 | −} | |
| 401 | − | |
| 402 | −/** | |
| 403 | − * Shorten a tool path for display. | |
| 404 | − * "github.issues.issues_list_for_repo" → "issues_list_for_repo" | |
| 405 | − * "discover" → "discover" | |
| 406 | − */ | |
| 407 | −function formatToolName(toolPath: string): string { | |
| 408 | − const parts = toolPath.split("."); | |
| 409 | − return `\`${parts[parts.length - 1]}\``; | |
| 410 | −} | |
| 411 | − | |
| 412 | −/** | |
| 413 | − * Format tool output for display. Special-cases discover results | |
| 414 | − * and truncates raw JSON. | |
| 415 | − */ | |
| 416 | −function formatToolOutput(toolPath: string, outputPreview: string | undefined): string { | |
| 417 | − if (!outputPreview) return ""; | |
| 418 | − | |
| 419 | − // Special-case discover results | |
| 420 | − if (toolPath === "discover" || toolPath.endsWith(".discover")) { | |
| 421 | − try { | |
| 422 | − const parsed = JSON.parse(outputPreview.endsWith("...") ? outputPreview.slice(0, -3) + "}" : outputPreview); | |
| 423 | − if (parsed.total !== undefined) { | |
| 424 | − return ` \u2192 found ${parsed.total} tools`; | |
| 425 | − } | |
| 426 | − } catch { /* fall through */ } | |
| 427 | − } | |
| 428 | − | |
| 429 | − // For other results, show a short preview | |
| 430 | − const clean = outputPreview.length > 80 ? outputPreview.slice(0, 80) + "..." : outputPreview; | |
| 431 | − return ` \u2192 ${clean}`; | |
| 432 | −} | |
| 433 | − | |
| 434 | −/** | |
| 435 | − * Collapse consecutive identical tool results into counts. | |
| 436 | − * ["✅ `delete` → ...", "✅ `delete` → ...", "✅ `delete` → ..."] | |
| 437 | − * becomes ["✅ `delete` ×3"] | |
| 438 | − */ | |
| 439 | −function collapseToolResults(results: string[], maxLines: number): string[] { | |
| 440 | − if (results.length === 0) return []; | |
| 441 | − | |
| 442 | − const collapsed: string[] = []; | |
| 443 | − let lastBase = ""; | |
| 444 | − let count = 0; | |
| 445 | − | |
| 446 | − for (const line of results) { | |
| 447 | − // Extract the tool name part (icon + name) | |
| 448 | − const base = line.replace(/ \u2192 .*$/, ""); | |
| 449 | − if (base === lastBase) { | |
| 450 | − count++; | |
| 451 | − } else { | |
| 452 | − if (lastBase && count > 0) { | |
| 453 | − collapsed.push(count === 1 ? results[collapsed.length] || lastBase : `${lastBase} \u00d7${count}`); | |
| 454 | − } | |
| 455 | − lastBase = base; | |
| 456 | − count = 1; | |
| 457 | − } | |
| 458 | − } | |
| 459 | − // Push the last group | |
| 460 | − if (lastBase && count > 0) { | |
| 461 | − if (count === 1) { | |
| 462 | − collapsed.push(results[results.length - 1]!); | |
| 463 | − } else { | |
| 464 | − collapsed.push(`${lastBase} \u00d7${count}`); | |
| 465 | − } | |
| 466 | − } | |
| 467 | − | |
| 468 | − return collapsed.slice(-maxLines); | |
| 469 | −} | |
| 470 | − | |
| 471 | −/** | |
| 472 | − * Format an approval request for Discord display. | |
| 473 | − * Uses structured preview fields for a clean, scannable layout. | |
| 474 | − * | |
| 475 | − * Example output: | |
| 476 | − * 🛡️ **DELETE** `removeProjectDomain` | |
| 477 | − * > **Target:** personal-site | |
| 478 | − * > **domain:** something-123-domain.vercel.app | |
| 479 | − */ | |
| 480 | −function formatApprovalDisplay(approval: PendingApproval): string { | |
| 481 | − const { preview, input } = approval; | |
| 482 | − | |
| 483 | − // Action verb from structured data, falling back to title | |
| 484 | − const actionLabel = preview.action | |
| 485 | − ? preview.action.toUpperCase() | |
| 486 | − : preview.title; | |
| 487 | − | |
| 488 | − // Short operation name | |
| 489 | − const opName = formatToolName(approval.toolPath); | |
| 490 | − | |
| 491 | − const lines: string[] = []; | |
| 492 | − | |
| 493 | − // Header: 🛡️ **DELETE** `removeProjectDomain` | |
| 494 | − const destructiveWarning = preview.isDestructive ? " ⚠️" : ""; | |
| 495 | − lines.push(`\u{1f6e1}\ufe0f **${actionLabel}**${destructiveWarning} ${opName}`); | |
| 496 | − | |
| 497 | − // Target (resource IDs) | |
| 498 | − if (preview.resourceIds && preview.resourceIds.length > 0) { | |
| 499 | − lines.push(`> **Target:** ${preview.resourceIds.join(", ")}`); | |
| 500 | − } | |
| 501 | − | |
| 502 | − // Key fields from input (excluding IDs already shown in Target) | |
| 503 | − const shownIds = new Set(preview.resourceIds ?? []); | |
| 504 | − if (input && typeof input === "object") { | |
| 505 | − const idKeys = new Set(["id", "ids", "name", "slug", "key", "idOrName", "projectId"]); | |
| 506 | − for (const [key, value] of Object.entries(input as Record<string, unknown>)) { | |
| 507 | − // Skip ID fields already shown as Target | |
| 508 | − if (idKeys.has(key) && typeof value === "string" && shownIds.has(value)) continue; | |
| 509 | − if (value === undefined || value === null) continue; | |
| 510 | − if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { | |
| 511 | − lines.push(`> **${key}:** ${String(value)}`); | |
| 512 | − } | |
| 513 | − } | |
| 514 | − } | |
| 515 | − | |
| 516 | − // Link if present | |
| 517 | − if (preview.link) { | |
| 518 | − lines.push(`> ${preview.link}`); | |
| 519 | − } | |
| 520 | − | |
| 521 | − return lines.join("\n"); | |
| 522 | −} | |
| @@ -1,13 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "extends": "../../tsconfig.base.json", | |
| 3 | − "compilerOptions": { | |
| 4 | − "exactOptionalPropertyTypes": false, | |
| 5 | − "jsx": "react-jsx", | |
| 6 | − "module": "ESNext", | |
| 7 | − "moduleResolution": "Bundler", | |
| 8 | − "rootDir": "src", | |
| 9 | − "noUnusedLocals": false, | |
| 10 | − "noUnusedParameters": false | |
| 11 | − }, | |
| 12 | − "include": ["src/**/*"] | |
| 13 | −} | |
| @@ -1,18 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "name": "@openassistant/executor", | |
| 3 | − "private": true, | |
| 4 | − "type": "module", | |
| 5 | − "scripts": { | |
| 6 | − "dev": "bun --hot src/index.ts", | |
| 7 | − "start": "bun src/index.ts", | |
| 8 | − "typecheck": "tsgo --noEmit", | |
| 9 | − "test": "bun test src/" | |
| 10 | − }, | |
| 11 | − "dependencies": { | |
| 12 | − "@openassistant/core": "workspace:*", | |
| 13 | − "zod": "^4.3.6" | |
| 14 | − }, | |
| 15 | − "devDependencies": { | |
| 16 | − "@types/bun": "latest" | |
| 17 | − } | |
| 18 | −} | |
| @@ -1,150 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Executor service — runs untrusted generated TS/JS code in an isolated process. | |
| 3 | − * | |
| 4 | − * This service only executes code. Any tools.* invocation is proxied back to the | |
| 5 | − * control plane via an authenticated callback. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import { | |
| 9 | − createRunner, | |
| 10 | − defineTool, | |
| 11 | − ToolDeniedError, | |
| 12 | − type ToolTree, | |
| 13 | −} from "@openassistant/core"; | |
| 14 | −import type { | |
| 15 | − ExecutorInvokeResponse, | |
| 16 | − ExecutorRunRequest, | |
| 17 | − ExecutorToolManifestEntry, | |
| 18 | −} from "@openassistant/core/remote-runner"; | |
| 19 | −import { z } from "zod"; | |
| 20 | − | |
| 21 | −const ExecuteSchema: z.ZodType<ExecutorRunRequest> = z.object({ | |
| 22 | − runId: z.string().min(1), | |
| 23 | − code: z.string(), | |
| 24 | − timeoutMs: z.number().int().positive(), | |
| 25 | − callbackBaseUrl: z.string().url(), | |
| 26 | − callbackToken: z.string().min(1), | |
| 27 | − tools: z.array(z.object({ | |
| 28 | − toolPath: z.string().min(1), | |
| 29 | − approval: z.union([z.literal("auto"), z.literal("required")]), | |
| 30 | − })), | |
| 31 | −}); | |
| 32 | − | |
| 33 | −const InvokeResponseSchema: z.ZodType<ExecutorInvokeResponse> = z.object({ | |
| 34 | − ok: z.boolean(), | |
| 35 | − value: z.unknown().optional(), | |
| 36 | − error: z.string().optional(), | |
| 37 | − denied: z.boolean().optional(), | |
| 38 | −}); | |
| 39 | − | |
| 40 | −function json(status: number, value: unknown): Response { | |
| 41 | − return new Response(JSON.stringify(value), { | |
| 42 | − status, | |
| 43 | − headers: { | |
| 44 | − "content-type": "application/json", | |
| 45 | − }, | |
| 46 | − }); | |
| 47 | −} | |
| 48 | − | |
| 49 | −function setToolPath(tree: Record<string, unknown>, entry: ExecutorToolManifestEntry, request: ExecutorRunRequest): void { | |
| 50 | − const parts = entry.toolPath.split(".").filter(Boolean); | |
| 51 | − if (parts.length === 0) return; | |
| 52 | − | |
| 53 | − let current = tree; | |
| 54 | − for (let i = 0; i < parts.length - 1; i++) { | |
| 55 | − const segment = parts[i]!; | |
| 56 | − const next = current[segment]; | |
| 57 | − if (!next || typeof next !== "object") { | |
| 58 | − current[segment] = {}; | |
| 59 | − } | |
| 60 | − current = current[segment] as Record<string, unknown>; | |
| 61 | − } | |
| 62 | − | |
| 63 | − const leaf = parts[parts.length - 1]!; | |
| 64 | − current[leaf] = defineTool({ | |
| 65 | − description: `Remote callback tool: ${entry.toolPath}`, | |
| 66 | − approval: "auto", | |
| 67 | − args: z.any(), | |
| 68 | − returns: z.any(), | |
| 69 | − run: async (input: unknown) => { | |
| 70 | − const callbackUrl = `${request.callbackBaseUrl.replace(/\/$/, "")}/internal/runs/${encodeURIComponent(request.runId)}/invoke`; | |
| 71 | − const response = await fetch(callbackUrl, { | |
| 72 | − method: "POST", | |
| 73 | − headers: { | |
| 74 | − "content-type": "application/json", | |
| 75 | − authorization: `Bearer ${request.callbackToken}`, | |
| 76 | − }, | |
| 77 | − body: JSON.stringify({ | |
| 78 | − toolPath: entry.toolPath, | |
| 79 | − input, | |
| 80 | − }), | |
| 81 | − }); | |
| 82 | − | |
| 83 | − if (!response.ok) { | |
| 84 | − throw new Error(`Tool callback failed: HTTP ${response.status}`); | |
| 85 | − } | |
| 86 | − | |
| 87 | − const payload = await response.json(); | |
| 88 | − const parsed = InvokeResponseSchema.safeParse(payload); | |
| 89 | − if (!parsed.success) { | |
| 90 | − throw new Error(`Tool callback returned invalid payload: ${parsed.error.message}`); | |
| 91 | − } | |
| 92 | − | |
| 93 | − if (parsed.data.denied) { | |
| 94 | − throw new ToolDeniedError(parsed.data.error ?? "Tool call denied"); | |
| 95 | − } | |
| 96 | − | |
| 97 | − if (!parsed.data.ok) { | |
| 98 | − throw new Error(parsed.data.error ?? "Tool callback failed"); | |
| 99 | − } | |
| 100 | − | |
| 101 | − return parsed.data.value; | |
| 102 | − }, | |
| 103 | − }); | |
| 104 | −} | |
| 105 | − | |
| 106 | −function buildToolTree(request: ExecutorRunRequest): ToolTree { | |
| 107 | − const root: Record<string, unknown> = {}; | |
| 108 | − for (const entry of request.tools) { | |
| 109 | − setToolPath(root, entry, request); | |
| 110 | − } | |
| 111 | − return root as ToolTree; | |
| 112 | −} | |
| 113 | − | |
| 114 | −const PORT = Number(process.env["PORT"] ?? 3001); | |
| 115 | − | |
| 116 | −Bun.serve({ | |
| 117 | − port: PORT, | |
| 118 | − async fetch(request) { | |
| 119 | − const url = new URL(request.url); | |
| 120 | − | |
| 121 | − if (request.method === "GET" && url.pathname === "/health") { | |
| 122 | − return json(200, { ok: true }); | |
| 123 | − } | |
| 124 | − | |
| 125 | − if (request.method === "POST" && url.pathname === "/internal/execute") { | |
| 126 | − const body = await request.json().catch(() => undefined); | |
| 127 | − const parsed = ExecuteSchema.safeParse(body); | |
| 128 | − if (!parsed.success) { | |
| 129 | − return json(400, { | |
| 130 | − ok: false, | |
| 131 | − error: `Invalid execute payload: ${parsed.error.message}`, | |
| 132 | − }); | |
| 133 | − } | |
| 134 | − | |
| 135 | − const toolTree = buildToolTree(parsed.data); | |
| 136 | − const runner = createRunner({ | |
| 137 | − tools: toolTree, | |
| 138 | − requestApproval: async () => "approved", | |
| 139 | − timeoutMs: parsed.data.timeoutMs, | |
| 140 | − }); | |
| 141 | − | |
| 142 | − const result = await runner.run(parsed.data.code); | |
| 143 | − return json(200, result); | |
| 144 | − } | |
| 145 | − | |
| 146 | − return json(404, { ok: false, error: "Not found" }); | |
| 147 | − }, | |
| 148 | −}); | |
| 149 | − | |
| 150 | −console.log(`OpenAssistant executor running at http://localhost:${PORT}`); | |
| @@ -1,5 +0,0 @@ | ||
| 1 | −import { expect, test } from "bun:test"; | |
| 2 | − | |
| 3 | −test("executor package is wired", () => { | |
| 4 | − expect(true).toBe(true); | |
| 5 | −}); | |
| @@ -1,14 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "extends": "../../tsconfig.base.json", | |
| 3 | − "compilerOptions": { | |
| 4 | − "rootDir": "src", | |
| 5 | − "outDir": "dist", | |
| 6 | − "composite": true, | |
| 7 | − "noUnusedLocals": false, | |
| 8 | − "noUnusedParameters": false | |
| 9 | − }, | |
| 10 | − "include": ["src/**/*.ts"], | |
| 11 | − "references": [ | |
| 12 | − { "path": "../../packages/core" } | |
| 13 | − ] | |
| 14 | −} | |
| @@ -1,27 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "name": "@openassistant/server", | |
| 3 | − "private": true, | |
| 4 | − "type": "module", | |
| 5 | − "exports": { | |
| 6 | − ".": "./src/index.ts", | |
| 7 | − "./client": "./src/client.ts", | |
| 8 | − "./routes": "./src/routes.ts" | |
| 9 | − }, | |
| 10 | − "scripts": { | |
| 11 | − "with-env": "bun run --env-file=../../.env --", | |
| 12 | − "dev": "bun run with-env bun --hot src/index.ts", | |
| 13 | − "start": "bun run with-env bun src/index.ts", | |
| 14 | − "typecheck": "tsgo --noEmit", | |
| 15 | − "test": "bun test src/" | |
| 16 | − }, | |
| 17 | − "dependencies": { | |
| 18 | − "@openassistant/core": "workspace:*", | |
| 19 | − "@openassistant/tool-gen": "workspace:*", | |
| 20 | − "@elysiajs/eden": "latest", | |
| 21 | − "elysia": "latest", | |
| 22 | − "zod": "^4.3.6" | |
| 23 | − }, | |
| 24 | − "devDependencies": { | |
| 25 | − "@types/bun": "latest" | |
| 26 | − } | |
| 27 | −} | |
| @@ -1,150 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Client tests — proves end-to-end type safety with Eden Treaty. | |
| 3 | − * | |
| 4 | − * Uses Elysia's direct app testing (no HTTP server) + Eden Treaty. | |
| 5 | − */ | |
| 6 | − | |
| 7 | −import { test, expect, describe } from "bun:test"; | |
| 8 | −import { treaty } from "@elysiajs/eden"; | |
| 9 | −import { createApp } from "./routes.js"; | |
| 10 | −import { ApiError, unwrap } from "./client.js"; | |
| 11 | −import { defineTool } from "@openassistant/core/tools"; | |
| 12 | −import type { ToolTree } from "@openassistant/core/tools"; | |
| 13 | −import type { LanguageModel, GenerateResult } from "@openassistant/core/agent"; | |
| 14 | −import { z } from "zod"; | |
| 15 | − | |
| 16 | −// --------------------------------------------------------------------------- | |
| 17 | −// Fixtures | |
| 18 | −// --------------------------------------------------------------------------- | |
| 19 | − | |
| 20 | −const echoTool = defineTool({ | |
| 21 | − description: "Echo", | |
| 22 | − approval: "auto", | |
| 23 | − args: z.object({ message: z.string() }), | |
| 24 | − returns: z.object({ echoed: z.string() }), | |
| 25 | − run: async (input) => ({ echoed: input.message }), | |
| 26 | −}); | |
| 27 | − | |
| 28 | −const testTools: ToolTree = { echo: echoTool }; | |
| 29 | − | |
| 30 | −function createSimpleModel(): LanguageModel { | |
| 31 | − return { | |
| 32 | − async generate(): Promise<GenerateResult> { | |
| 33 | − return { text: "All done." }; | |
| 34 | − }, | |
| 35 | − }; | |
| 36 | −} | |
| 37 | − | |
| 38 | −function createTestApp() { | |
| 39 | − return createApp({ | |
| 40 | − tools: testTools, | |
| 41 | − model: createSimpleModel(), | |
| 42 | − }); | |
| 43 | −} | |
| 44 | − | |
| 45 | −// --------------------------------------------------------------------------- | |
| 46 | −// Tests | |
| 47 | −// --------------------------------------------------------------------------- | |
| 48 | − | |
| 49 | −describe("Eden Treaty client", () => { | |
| 50 | − test("unwrap extracts data from successful call", async () => { | |
| 51 | − const app = createTestApp(); | |
| 52 | − const client = treaty(app); | |
| 53 | − | |
| 54 | − const data = await unwrap( | |
| 55 | − client.api.tasks.post({ | |
| 56 | − prompt: "Hello", | |
| 57 | − requesterId: "user_1", | |
| 58 | − }), | |
| 59 | − ); | |
| 60 | − | |
| 61 | − expect(data.taskId).toMatch(/^task_/); | |
| 62 | − expect(data.status).toBe("running"); | |
| 63 | − }); | |
| 64 | − | |
| 65 | − test("unwrap throws ApiError on 404", async () => { | |
| 66 | − const app = createTestApp(); | |
| 67 | − const client = treaty(app); | |
| 68 | − | |
| 69 | − try { | |
| 70 | − await unwrap(client.api.tasks({ id: "nonexistent" }).get()); | |
| 71 | − expect.unreachable("Should have thrown"); | |
| 72 | − } catch (err) { | |
| 73 | − expect(err).toBeInstanceOf(ApiError); | |
| 74 | − expect((err as ApiError).status).toBe(404); | |
| 75 | − } | |
| 76 | − }); | |
| 77 | − | |
| 78 | − test("type inference: task data has correct shape", async () => { | |
| 79 | − const app = createTestApp(); | |
| 80 | − const client = treaty(app); | |
| 81 | − | |
| 82 | − // Create a task | |
| 83 | − const created = await unwrap( | |
| 84 | − client.api.tasks.post({ | |
| 85 | − prompt: "Type test", | |
| 86 | − requesterId: "user_1", | |
| 87 | − }), | |
| 88 | − ); | |
| 89 | − | |
| 90 | − // Wait for completion | |
| 91 | − await Bun.sleep(200); | |
| 92 | − | |
| 93 | − // Get task — the data should have the full serialized shape | |
| 94 | − const task = await unwrap( | |
| 95 | − client.api.tasks({ id: created.taskId }).get(), | |
| 96 | − ); | |
| 97 | − | |
| 98 | − // These type-check at compile time thanks to Eden Treaty! | |
| 99 | − expect(typeof task.id).toBe("string"); | |
| 100 | − expect(typeof task.prompt).toBe("string"); | |
| 101 | − expect(typeof task.requesterId).toBe("string"); | |
| 102 | − expect(typeof task.createdAt).toBe("number"); | |
| 103 | − expect(typeof task.status).toBe("string"); | |
| 104 | − expect(typeof task.eventCount).toBe("number"); | |
| 105 | − expect(Array.isArray(task.pendingApprovals)).toBe(true); | |
| 106 | − }); | |
| 107 | − | |
| 108 | − test("type inference: approval response has correct shape", async () => { | |
| 109 | − const app = createTestApp(); | |
| 110 | − const client = treaty(app); | |
| 111 | − | |
| 112 | − // This should be a 404 since no approval exists | |
| 113 | − try { | |
| 114 | − await unwrap( | |
| 115 | − client.api.approvals({ callId: "fake" }).post({ | |
| 116 | − decision: "approved", | |
| 117 | − }), | |
| 118 | − ); | |
| 119 | − expect.unreachable("Should have thrown"); | |
| 120 | − } catch (err) { | |
| 121 | − expect(err).toBeInstanceOf(ApiError); | |
| 122 | − } | |
| 123 | − }); | |
| 124 | −}); | |
| 125 | − | |
| 126 | −describe("Direct treaty usage (no unwrap)", () => { | |
| 127 | − test("data/error pattern works as expected", async () => { | |
| 128 | − const app = createTestApp(); | |
| 129 | − const client = treaty(app); | |
| 130 | − | |
| 131 | − // Success case | |
| 132 | − const { data, error } = await client.api.tasks.post({ | |
| 133 | − prompt: "Direct test", | |
| 134 | − requesterId: "user_1", | |
| 135 | − }); | |
| 136 | − | |
| 137 | − expect(error).toBeNull(); | |
| 138 | − expect(data).toBeDefined(); | |
| 139 | − expect(data!.taskId).toBeDefined(); | |
| 140 | − | |
| 141 | − // Error case | |
| 142 | − const { data: data2, error: error2 } = await client.api.tasks({ | |
| 143 | − id: "nonexistent", | |
| 144 | − }).get(); | |
| 145 | − | |
| 146 | − expect(data2).toBeNull(); | |
| 147 | − expect(error2).toBeDefined(); | |
| 148 | − expect(error2!.status).toBe(404); | |
| 149 | − }); | |
| 150 | −}); | |
| @@ -1,104 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Eden Treaty client for OpenAssistant server. | |
| 3 | − * | |
| 4 | − * Provides a type-safe client that can be consumed by: | |
| 5 | − * - The Discord bot (Reacord) via Effect wrappers | |
| 6 | − * - Any other TypeScript client | |
| 7 | − * | |
| 8 | − * Usage: | |
| 9 | − * import { createClient, effect } from "@openassistant/server/client" | |
| 10 | − * | |
| 11 | − * // Plain Eden Treaty (Promise-based) | |
| 12 | − * const api = createClient("http://localhost:3000") | |
| 13 | − * const { data, error } = await api.api.tasks.post({ prompt: "...", requesterId: "..." }) | |
| 14 | − * | |
| 15 | − * // Effect-wrapped | |
| 16 | − * const result = yield* effect(api.api.tasks.post({ prompt: "...", requesterId: "..." })) | |
| 17 | − */ | |
| 18 | − | |
| 19 | −import { type Treaty, treaty } from "@elysiajs/eden"; | |
| 20 | −import type { App } from "./routes.js"; | |
| 21 | − | |
| 22 | −// --------------------------------------------------------------------------- | |
| 23 | −// Client factory | |
| 24 | −// --------------------------------------------------------------------------- | |
| 25 | − | |
| 26 | −export type Client = ReturnType<typeof treaty<App>>; | |
| 27 | − | |
| 28 | −export interface ClientConfig { | |
| 29 | − /** Base fetch configuration. */ | |
| 30 | − fetch?: Omit<RequestInit, "headers" | "method">; | |
| 31 | −} | |
| 32 | − | |
| 33 | −/** | |
| 34 | − * Create a typed Eden Treaty client for the OpenAssistant server. | |
| 35 | − */ | |
| 36 | −export function createClient( | |
| 37 | − baseUrl: string, | |
| 38 | − config?: ClientConfig, | |
| 39 | −): Client { | |
| 40 | − return treaty<App>(baseUrl, { | |
| 41 | − ...(config?.fetch ? { fetch: config.fetch } : {}), | |
| 42 | − }); | |
| 43 | −} | |
| 44 | − | |
| 45 | −// --------------------------------------------------------------------------- | |
| 46 | −// Effect wrapper (lazy import to avoid hard dep for non-Effect consumers) | |
| 47 | −// --------------------------------------------------------------------------- | |
| 48 | − | |
| 49 | −/** | |
| 50 | − * Error from an Eden Treaty call. | |
| 51 | − */ | |
| 52 | −export class ApiError { | |
| 53 | − readonly _tag = "ApiError"; | |
| 54 | − constructor( | |
| 55 | − readonly status: number, | |
| 56 | − readonly value: unknown, | |
| 57 | − ) {} | |
| 58 | −} | |
| 59 | − | |
| 60 | −/** | |
| 61 | − * Wrap an Eden Treaty call in an Effect. | |
| 62 | − * | |
| 63 | − * Converts the { data, error } pattern into Effect's success/failure. | |
| 64 | − * | |
| 65 | − * @example | |
| 66 | − * ```ts | |
| 67 | − * import { Effect } from "effect" | |
| 68 | − * import { createClient, effectify } from "@openassistant/server/client" | |
| 69 | − * | |
| 70 | − * const api = createClient("http://localhost:3000") | |
| 71 | − * const program = Effect.gen(function* () { | |
| 72 | − * const task = yield* effectify(api.api.tasks.post({ prompt: "hello", requesterId: "user_1" })) | |
| 73 | − * // task is typed as { taskId: string, status: TaskStatus } | |
| 74 | − * }) | |
| 75 | − * ``` | |
| 76 | − */ | |
| 77 | −export async function unwrap<T extends Record<number, unknown>>( | |
| 78 | − treatyCall: Promise<Treaty.TreatyResponse<T>>, | |
| 79 | −): Promise<Treaty.Data<Treaty.TreatyResponse<T>>> { | |
| 80 | − const response = await treatyCall; | |
| 81 | − | |
| 82 | − if (response.error) { | |
| 83 | − const err = response.error as unknown; | |
| 84 | − if (err && typeof err === "object" && "status" in err) { | |
| 85 | − throw new ApiError( | |
| 86 | − (err as { status: number }).status, | |
| 87 | − "value" in err ? (err as { value: unknown }).value : err, | |
| 88 | − ); | |
| 89 | − } | |
| 90 | − throw new ApiError(0, err); | |
| 91 | − } | |
| 92 | − | |
| 93 | − if (response.data !== undefined) { | |
| 94 | − return response.data as Treaty.Data<Treaty.TreatyResponse<T>>; | |
| 95 | − } | |
| 96 | − | |
| 97 | − throw new ApiError(0, "No data returned from API"); | |
| 98 | −} | |
| 99 | − | |
| 100 | −// --------------------------------------------------------------------------- | |
| 101 | −// Re-export types from routes | |
| 102 | −// --------------------------------------------------------------------------- | |
| 103 | − | |
| 104 | −export type { App } from "./routes.js"; | |
| @@ -1,229 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * OpenAssistant Server — Elysia + Eden Treaty | |
| 3 | − * | |
| 4 | − * Starts the server with tool sources loaded from config. | |
| 5 | − * The exported App type is consumed by Eden Treaty clients. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import { readFileSync } from "node:fs"; | |
| 9 | −import { createApp } from "./routes.js"; | |
| 10 | −import { createPiAiModel } from "@openassistant/core"; | |
| 11 | −import { defineTool, mergeToolTrees, type ToolTree } from "@openassistant/core/tools"; | |
| 12 | −import { generateMcpTools } from "@openassistant/tool-gen/mcp"; | |
| 13 | −import type { McpToolSource } from "@openassistant/tool-gen/mcp"; | |
| 14 | −import { generateOpenApiTools } from "@openassistant/tool-gen/openapi"; | |
| 15 | −import type { OpenApiToolSource } from "@openassistant/tool-gen/openapi"; | |
| 16 | −import { z } from "zod"; | |
| 17 | − | |
| 18 | −// --------------------------------------------------------------------------- | |
| 19 | −// API key resolution | |
| 20 | −// --------------------------------------------------------------------------- | |
| 21 | − | |
| 22 | −/** | |
| 23 | − * Resolve the Anthropic API key from (in order): | |
| 24 | − * 1. ANTHROPIC_OAUTH_TOKEN env var | |
| 25 | − * 2. ANTHROPIC_API_KEY env var | |
| 26 | − * 3. Claude Code's credential store (~/.claude/.credentials.json) | |
| 27 | − */ | |
| 28 | −function getAnthropicApiKey(): string | undefined { | |
| 29 | − if (process.env["ANTHROPIC_OAUTH_TOKEN"]) return process.env["ANTHROPIC_OAUTH_TOKEN"]; | |
| 30 | − if (process.env["ANTHROPIC_API_KEY"]) return process.env["ANTHROPIC_API_KEY"]; | |
| 31 | − | |
| 32 | − try { | |
| 33 | − const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? ""; | |
| 34 | − const text = readFileSync(`${home}/.claude/.credentials.json`, "utf-8"); | |
| 35 | − const creds = JSON.parse(text); | |
| 36 | − const token = (creds as Record<string, Record<string, unknown>>)?.["claudeAiOauth"]?.["accessToken"]; | |
| 37 | − if (typeof token === "string" && token.startsWith("sk-ant-")) { | |
| 38 | − return token; | |
| 39 | − } | |
| 40 | − } catch { | |
| 41 | − // No credentials file | |
| 42 | − } | |
| 43 | − | |
| 44 | − return undefined; | |
| 45 | −} | |
| 46 | − | |
| 47 | −// --------------------------------------------------------------------------- | |
| 48 | −// Tool source config | |
| 49 | −// --------------------------------------------------------------------------- | |
| 50 | − | |
| 51 | −const mcpSources: McpToolSource[] = [ | |
| 52 | − { | |
| 53 | − name: "answeroverflow", | |
| 54 | − url: "https://www.answeroverflow.com/mcp", | |
| 55 | − defaultApproval: "auto", | |
| 56 | − }, | |
| 57 | −]; | |
| 58 | − | |
| 59 | −const POSTHOG_API_KEY = process.env["POSTHOG_PERSONAL_API_KEY"]; | |
| 60 | −const POSTHOG_PROJECT_ID = process.env["POSTHOG_PROJECT_ID"]; | |
| 61 | −const GITHUB_TOKEN = process.env["OPENASSISTANT_GITHUB_TOKEN"]; | |
| 62 | −const VERCEL_TOKEN = process.env["VERCEL_TOKEN"]; | |
| 63 | − | |
| 64 | −const openApiSources: OpenApiToolSource[] = [ | |
| 65 | − ...(POSTHOG_API_KEY ? [{ | |
| 66 | − name: "posthog", | |
| 67 | − spec: "https://app.posthog.com/api/schema/?format=json", | |
| 68 | − baseUrl: "https://app.posthog.com", | |
| 69 | − auth: { type: "bearer" as const, token: POSTHOG_API_KEY }, | |
| 70 | − defaultReadApproval: "auto" as const, | |
| 71 | − defaultWriteApproval: "required" as const, | |
| 72 | − }] : []), | |
| 73 | − ...(GITHUB_TOKEN ? [{ | |
| 74 | − name: "github", | |
| 75 | − spec: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json", | |
| 76 | − auth: { type: "bearer" as const, token: GITHUB_TOKEN }, | |
| 77 | − defaultReadApproval: "auto" as const, | |
| 78 | − defaultWriteApproval: "required" as const, | |
| 79 | − }] : []), | |
| 80 | − ...(VERCEL_TOKEN ? [{ | |
| 81 | − name: "vercel", | |
| 82 | − spec: "https://openapi.vercel.sh/", | |
| 83 | − auth: { type: "bearer" as const, token: VERCEL_TOKEN }, | |
| 84 | − defaultReadApproval: "auto" as const, | |
| 85 | − defaultWriteApproval: "required" as const, | |
| 86 | − }] : []), | |
| 87 | −]; | |
| 88 | − | |
| 89 | −// --------------------------------------------------------------------------- | |
| 90 | −// Hand-written tools (approval testing) | |
| 91 | −// --------------------------------------------------------------------------- | |
| 92 | − | |
| 93 | −const builtinTools: ToolTree = { | |
| 94 | − admin: { | |
| 95 | − /** Requires approval — lets you test the approval UI flow. */ | |
| 96 | − send_announcement: defineTool({ | |
| 97 | − description: "Send an announcement message to a channel. Requires approval because it posts publicly.", | |
| 98 | − approval: "required", | |
| 99 | − args: z.object({ | |
| 100 | − channel: z.string().describe("Channel name to post in"), | |
| 101 | − message: z.string().describe("The announcement text"), | |
| 102 | − }), | |
| 103 | − returns: z.object({ sent: z.boolean(), channel: z.string(), message: z.string() }), | |
| 104 | − run: async (input) => ({ sent: true, channel: input.channel, message: input.message }), | |
| 105 | − formatApproval: (input) => ({ | |
| 106 | − title: `Post to #${input.channel}`, | |
| 107 | − details: `Message: "${input.message}"`, | |
| 108 | − }), | |
| 109 | − }), | |
| 110 | − | |
| 111 | − /** Requires approval — destructive action. */ | |
| 112 | − delete_data: defineTool({ | |
| 113 | − description: "Delete stored data by key. Requires approval because it's destructive.", | |
| 114 | − approval: "required", | |
| 115 | − args: z.object({ | |
| 116 | − key: z.string().describe("The data key to delete"), | |
| 117 | − }), | |
| 118 | − returns: z.object({ deleted: z.boolean(), key: z.string() }), | |
| 119 | − run: async (input) => ({ deleted: true, key: input.key }), | |
| 120 | − formatApproval: (input) => ({ | |
| 121 | − title: `Delete "${input.key}"`, | |
| 122 | − details: "This action cannot be undone.", | |
| 123 | − }), | |
| 124 | − }), | |
| 125 | − }, | |
| 126 | − | |
| 127 | − utils: { | |
| 128 | − /** Auto-approved — safe read-only tool. */ | |
| 129 | − get_time: defineTool({ | |
| 130 | − description: "Get the current date and time.", | |
| 131 | − approval: "auto", | |
| 132 | − args: z.object({}), | |
| 133 | − returns: z.object({ iso: z.string(), unix: z.number() }), | |
| 134 | − run: async () => ({ iso: new Date().toISOString(), unix: Date.now() }), | |
| 135 | − }), | |
| 136 | − }, | |
| 137 | −}; | |
| 138 | − | |
| 139 | −// --------------------------------------------------------------------------- | |
| 140 | −// Tool loading | |
| 141 | −// --------------------------------------------------------------------------- | |
| 142 | − | |
| 143 | −async function loadTools(): Promise<ToolTree> { | |
| 144 | − const trees: ToolTree[] = [builtinTools]; | |
| 145 | − | |
| 146 | − // Load MCP tools | |
| 147 | − for (const source of mcpSources) { | |
| 148 | − try { | |
| 149 | − console.log(`Loading MCP tools from ${source.name} (${source.url})...`); | |
| 150 | − const result = await generateMcpTools(source); | |
| 151 | − trees.push(result.tools); | |
| 152 | − console.log(` Loaded ${Object.keys(result.tools[source.name] ?? {}).length} tools from ${source.name}`); | |
| 153 | − } catch (error) { | |
| 154 | − console.error(` Failed to load ${source.name}:`, error instanceof Error ? error.message : error); | |
| 155 | − } | |
| 156 | − } | |
| 157 | − | |
| 158 | − // Load OpenAPI tools | |
| 159 | − for (const source of openApiSources) { | |
| 160 | − try { | |
| 161 | − console.log(`Loading OpenAPI tools from ${source.name}...`); | |
| 162 | − const result = await generateOpenApiTools(source); | |
| 163 | − trees.push(result.tools); | |
| 164 | − const nsTools = result.tools[source.name]; | |
| 165 | − const count = nsTools ? Object.values(nsTools).reduce((sum, tag) => { | |
| 166 | − return sum + (typeof tag === "object" && tag !== null ? Object.keys(tag).length : 1); | |
| 167 | − }, 0) : 0; | |
| 168 | − console.log(` Loaded ~${count} tools from ${source.name}`); | |
| 169 | − } catch (error) { | |
| 170 | − console.error(` Failed to load ${source.name}:`, error instanceof Error ? error.message : error); | |
| 171 | − } | |
| 172 | − } | |
| 173 | − | |
| 174 | − return mergeToolTrees(...trees); | |
| 175 | −} | |
| 176 | − | |
| 177 | −// --------------------------------------------------------------------------- | |
| 178 | −// Start | |
| 179 | −// --------------------------------------------------------------------------- | |
| 180 | − | |
| 181 | −const PORT = Number(process.env["PORT"] ?? 3000); | |
| 182 | −const EXECUTOR_URL = process.env["OPENASSISTANT_EXECUTOR_URL"]; | |
| 183 | −const CALLBACK_BASE_URL = process.env["OPENASSISTANT_CALLBACK_BASE_URL"] | |
| 184 | − ?? `http://localhost:${PORT}`; | |
| 185 | − | |
| 186 | −const apiKey = getAnthropicApiKey(); | |
| 187 | −if (!apiKey) { | |
| 188 | − console.error("WARNING: No Anthropic API key found. Set ANTHROPIC_API_KEY, ANTHROPIC_OAUTH_TOKEN, or have Claude Code credentials at ~/.claude/.credentials.json"); | |
| 189 | −} | |
| 190 | − | |
| 191 | −console.log("Loading tools..."); | |
| 192 | −const tools = await loadTools(); | |
| 193 | −const model = createPiAiModel({ apiKey }); | |
| 194 | − | |
| 195 | −// Build context for the system prompt | |
| 196 | −const contextLines: string[] = []; | |
| 197 | −if (POSTHOG_PROJECT_ID) { | |
| 198 | − contextLines.push(`- PostHog project ID: ${POSTHOG_PROJECT_ID} (use this for PostHog API calls that require a project_id)`); | |
| 199 | −} | |
| 200 | −if (GITHUB_TOKEN) { | |
| 201 | − contextLines.push(`- GitHub: authenticated via personal access token`); | |
| 202 | −} | |
| 203 | −if (VERCEL_TOKEN) { | |
| 204 | − contextLines.push(`- Vercel: authenticated via personal token`); | |
| 205 | −} | |
| 206 | −const context = contextLines.length > 0 ? contextLines.join("\n") : undefined; | |
| 207 | − | |
| 208 | −if (EXECUTOR_URL) { | |
| 209 | − console.log(`Using remote executor at ${EXECUTOR_URL}`); | |
| 210 | −} | |
| 211 | − | |
| 212 | −const app = createApp({ | |
| 213 | − tools, | |
| 214 | − model, | |
| 215 | − context, | |
| 216 | − executor: EXECUTOR_URL | |
| 217 | − ? { | |
| 218 | − url: EXECUTOR_URL, | |
| 219 | − callbackBaseUrl: CALLBACK_BASE_URL, | |
| 220 | − } | |
| 221 | − : undefined, | |
| 222 | −}); | |
| 223 | − | |
| 224 | −app.listen(PORT); | |
| 225 | − | |
| 226 | −console.log(`\u{1f98a} OpenAssistant server running at http://localhost:${PORT}`); | |
| 227 | − | |
| 228 | −// Re-export the app type for Eden Treaty clients | |
| 229 | −export type { App } from "./routes.js"; | |
| @@ -1,408 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Server route tests. | |
| 3 | − * | |
| 4 | − * Uses Elysia's direct handler testing (no HTTP server needed) | |
| 5 | − * and Eden Treaty for type-safe client calls. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import { test, expect, describe } from "bun:test"; | |
| 9 | −import { treaty } from "@elysiajs/eden"; | |
| 10 | −import { createApp } from "./routes.js"; | |
| 11 | −import { registerRemoteRunSession, removeRemoteRunSession } from "./state.js"; | |
| 12 | −import { defineTool } from "@openassistant/core/tools"; | |
| 13 | −import type { ToolTree } from "@openassistant/core/tools"; | |
| 14 | −import type { LanguageModel, GenerateResult, Message } from "@openassistant/core/agent"; | |
| 15 | −import { z } from "zod"; | |
| 16 | − | |
| 17 | −// --------------------------------------------------------------------------- | |
| 18 | −// Test fixtures | |
| 19 | −// --------------------------------------------------------------------------- | |
| 20 | − | |
| 21 | −/** A simple echo tool for testing. */ | |
| 22 | −const echoTool = defineTool({ | |
| 23 | − description: "Echo back the input", | |
| 24 | − approval: "auto", | |
| 25 | − args: z.object({ message: z.string() }), | |
| 26 | − returns: z.object({ echoed: z.string() }), | |
| 27 | − run: async (input) => ({ echoed: input.message }), | |
| 28 | −}); | |
| 29 | − | |
| 30 | −/** A tool that requires approval. */ | |
| 31 | −const dangerousTool = defineTool({ | |
| 32 | − description: "A dangerous operation that needs approval", | |
| 33 | − approval: "required", | |
| 34 | − args: z.object({ target: z.string() }), | |
| 35 | − returns: z.object({ deleted: z.boolean() }), | |
| 36 | − run: async () => ({ deleted: true }), | |
| 37 | − formatApproval: (input) => ({ | |
| 38 | − title: `Delete ${input.target}`, | |
| 39 | − details: `This will permanently delete ${input.target}`, | |
| 40 | − }), | |
| 41 | −}); | |
| 42 | − | |
| 43 | −const testTools: ToolTree = { | |
| 44 | − echo: echoTool, | |
| 45 | − danger: dangerousTool, | |
| 46 | −}; | |
| 47 | − | |
| 48 | −/** | |
| 49 | − * Create a mock LanguageModel for testing. | |
| 50 | − * By default, immediately returns text (no tool calls). | |
| 51 | − */ | |
| 52 | −function createMockModel( | |
| 53 | − handler?: (messages: Message[]) => GenerateResult, | |
| 54 | −): LanguageModel { | |
| 55 | − return { | |
| 56 | − async generate(messages: Message[]): Promise<GenerateResult> { | |
| 57 | − if (handler) return handler(messages); | |
| 58 | − return { text: "Done! I processed your request." }; | |
| 59 | − }, | |
| 60 | − }; | |
| 61 | −} | |
| 62 | − | |
| 63 | −/** | |
| 64 | − * Create a model that generates code calling the echo tool, | |
| 65 | − * then responds with text on the second call. | |
| 66 | − */ | |
| 67 | −function createEchoModel(): LanguageModel { | |
| 68 | − let callCount = 0; | |
| 69 | − return { | |
| 70 | − async generate(): Promise<GenerateResult> { | |
| 71 | − callCount++; | |
| 72 | − if (callCount === 1) { | |
| 73 | − return { | |
| 74 | − toolCalls: [ | |
| 75 | − { | |
| 76 | − id: "call_1", | |
| 77 | − name: "run_code", | |
| 78 | − args: { | |
| 79 | − code: `const result = await tools.echo({ message: "hello" });\nreturn result;`, | |
| 80 | − }, | |
| 81 | − }, | |
| 82 | − ], | |
| 83 | − }; | |
| 84 | − } | |
| 85 | − return { text: "Echo completed successfully." }; | |
| 86 | − }, | |
| 87 | − }; | |
| 88 | −} | |
| 89 | − | |
| 90 | −/** | |
| 91 | − * Create a model that generates code calling the dangerous tool, | |
| 92 | − * then responds with text. | |
| 93 | − */ | |
| 94 | −function createDangerModel(): LanguageModel { | |
| 95 | − let callCount = 0; | |
| 96 | − return { | |
| 97 | − async generate(): Promise<GenerateResult> { | |
| 98 | − callCount++; | |
| 99 | − if (callCount === 1) { | |
| 100 | − return { | |
| 101 | − toolCalls: [ | |
| 102 | − { | |
| 103 | − id: "call_1", | |
| 104 | − name: "run_code", | |
| 105 | − args: { | |
| 106 | − code: `const result = await tools.danger({ target: "everything" });\nreturn result;`, | |
| 107 | − }, | |
| 108 | − }, | |
| 109 | − ], | |
| 110 | − }; | |
| 111 | − } | |
| 112 | − return { text: "Danger operation completed." }; | |
| 113 | − }, | |
| 114 | − }; | |
| 115 | −} | |
| 116 | − | |
| 117 | −function createTestApp(model?: LanguageModel) { | |
| 118 | − return createApp({ | |
| 119 | − tools: testTools, | |
| 120 | − model: model ?? createMockModel(), | |
| 121 | − }); | |
| 122 | −} | |
| 123 | − | |
| 124 | −// --------------------------------------------------------------------------- | |
| 125 | −// Tests | |
| 126 | −// --------------------------------------------------------------------------- | |
| 127 | − | |
| 128 | −describe("POST /api/tasks", () => { | |
| 129 | − test("creates a task and returns taskId", async () => { | |
| 130 | − const app = createTestApp(); | |
| 131 | − const client = treaty(app); | |
| 132 | − | |
| 133 | − const { data, error } = await client.api.tasks.post({ | |
| 134 | − prompt: "Say hello", | |
| 135 | − requesterId: "user_1", | |
| 136 | − }); | |
| 137 | − | |
| 138 | − expect(error).toBeNull(); | |
| 139 | − expect(data).toBeDefined(); | |
| 140 | − expect(data!.taskId).toMatch(/^task_/); | |
| 141 | − expect(data!.status).toBe("running"); | |
| 142 | − expect(data!.executionMode).toBe("local"); | |
| 143 | − }); | |
| 144 | − | |
| 145 | − test("rejects empty prompt", async () => { | |
| 146 | − const app = createTestApp(); | |
| 147 | − const client = treaty(app); | |
| 148 | − | |
| 149 | − const { error } = await client.api.tasks.post({ | |
| 150 | − prompt: "", | |
| 151 | − requesterId: "user_1", | |
| 152 | − }); | |
| 153 | − | |
| 154 | − expect(error).toBeDefined(); | |
| 155 | − }); | |
| 156 | −}); | |
| 157 | − | |
| 158 | −describe("GET /api/tasks", () => { | |
| 159 | − test("lists created tasks", async () => { | |
| 160 | − const app = createTestApp(); | |
| 161 | − const client = treaty(app); | |
| 162 | − | |
| 163 | − await client.api.tasks.post({ | |
| 164 | − prompt: "Test task", | |
| 165 | − requesterId: "user_1", | |
| 166 | − }); | |
| 167 | − | |
| 168 | − await Bun.sleep(10); | |
| 169 | − | |
| 170 | − const { data, error } = await client.api.tasks.get({ | |
| 171 | − query: {}, | |
| 172 | − }); | |
| 173 | − | |
| 174 | − expect(error).toBeNull(); | |
| 175 | − expect(data).toBeInstanceOf(Array); | |
| 176 | − expect(data!.length).toBeGreaterThanOrEqual(1); | |
| 177 | − }); | |
| 178 | −}); | |
| 179 | − | |
| 180 | −describe("GET /api/tasks/:id", () => { | |
| 181 | − test("returns task details", async () => { | |
| 182 | − const app = createTestApp(); | |
| 183 | − const client = treaty(app); | |
| 184 | − | |
| 185 | − const { data: created } = await client.api.tasks.post({ | |
| 186 | − prompt: "Detail test", | |
| 187 | − requesterId: "user_1", | |
| 188 | − }); | |
| 189 | − | |
| 190 | − const { data, error } = await client.api.tasks({ id: created!.taskId }).get(); | |
| 191 | − | |
| 192 | − expect(error).toBeNull(); | |
| 193 | − expect(data).toBeDefined(); | |
| 194 | − expect(data!.id).toBe(created!.taskId); | |
| 195 | − expect(data!.prompt).toBe("Detail test"); | |
| 196 | − expect(data!.executionMode).toBe("local"); | |
| 197 | − }); | |
| 198 | − | |
| 199 | − test("returns 404 for unknown task", async () => { | |
| 200 | − const app = createTestApp(); | |
| 201 | − const client = treaty(app); | |
| 202 | − | |
| 203 | − const { error } = await client.api.tasks({ id: "nonexistent" }).get(); | |
| 204 | − | |
| 205 | − expect(error).toBeDefined(); | |
| 206 | − expect(error!.status).toBe(404); | |
| 207 | − }); | |
| 208 | −}); | |
| 209 | − | |
| 210 | −describe("Agent execution flow", () => { | |
| 211 | − test("simple model completes task with text", async () => { | |
| 212 | − const model = createMockModel(); | |
| 213 | − const app = createTestApp(model); | |
| 214 | − const client = treaty(app); | |
| 215 | − | |
| 216 | − const { data } = await client.api.tasks.post({ | |
| 217 | − prompt: "Just respond", | |
| 218 | − requesterId: "user_1", | |
| 219 | − }); | |
| 220 | − | |
| 221 | − // Wait for agent to finish | |
| 222 | − await Bun.sleep(500); | |
| 223 | − | |
| 224 | − const { data: task } = await client.api.tasks({ id: data!.taskId }).get(); | |
| 225 | − | |
| 226 | − expect(task!.status).toBe("completed"); | |
| 227 | − expect(task!.resultText).toBe("Done! I processed your request."); | |
| 228 | − }); | |
| 229 | − | |
| 230 | − test("echo model generates code and executes it", async () => { | |
| 231 | − const model = createEchoModel(); | |
| 232 | − const app = createTestApp(model); | |
| 233 | − const client = treaty(app); | |
| 234 | − | |
| 235 | − const { data } = await client.api.tasks.post({ | |
| 236 | − prompt: "Echo hello", | |
| 237 | − requesterId: "user_1", | |
| 238 | − }); | |
| 239 | − | |
| 240 | − // Wait for agent to finish | |
| 241 | − await Bun.sleep(1000); | |
| 242 | − | |
| 243 | − const { data: task } = await client.api.tasks({ id: data!.taskId }).get(); | |
| 244 | − | |
| 245 | − expect(task!.status).toBe("completed"); | |
| 246 | − expect(task!.eventCount).toBeGreaterThan(0); | |
| 247 | − }); | |
| 248 | −}); | |
| 249 | − | |
| 250 | −describe("Approval flow", () => { | |
| 251 | − test("dangerous tool creates approval request, resolving it completes task", async () => { | |
| 252 | − const model = createDangerModel(); | |
| 253 | − const app = createTestApp(model); | |
| 254 | − const client = treaty(app); | |
| 255 | − | |
| 256 | − const { data } = await client.api.tasks.post({ | |
| 257 | − prompt: "Delete everything", | |
| 258 | − requesterId: "user_1", | |
| 259 | − }); | |
| 260 | − | |
| 261 | − const taskId = data!.taskId; | |
| 262 | − | |
| 263 | − // Poll for the approval request to appear | |
| 264 | − let approvals: Array<{ callId: string; toolPath: string }> = []; | |
| 265 | − for (let i = 0; i < 20; i++) { | |
| 266 | − await Bun.sleep(100); | |
| 267 | − const { data: task } = await client.api.tasks({ id: taskId }).get(); | |
| 268 | − if (task && task.pendingApprovals.length > 0) { | |
| 269 | − approvals = task.pendingApprovals; | |
| 270 | − break; | |
| 271 | − } | |
| 272 | − } | |
| 273 | − | |
| 274 | − expect(approvals.length).toBeGreaterThan(0); | |
| 275 | − | |
| 276 | − // Resolve the approval | |
| 277 | − const callId = approvals[0]!.callId; | |
| 278 | − const { data: result, error } = await client.api.approvals({ callId }).post({ | |
| 279 | − decision: "approved", | |
| 280 | − }); | |
| 281 | − | |
| 282 | − expect(error).toBeNull(); | |
| 283 | − expect(result!.decision).toBe("approved"); | |
| 284 | − | |
| 285 | − // Wait for task to complete | |
| 286 | − await Bun.sleep(1000); | |
| 287 | − | |
| 288 | − const { data: finalTask } = await client.api.tasks({ id: taskId }).get(); | |
| 289 | − expect(finalTask!.status).toBe("completed"); | |
| 290 | − }); | |
| 291 | −}); | |
| 292 | − | |
| 293 | −describe("POST /api/approvals/:callId", () => { | |
| 294 | − test("returns 404 for unknown callId", async () => { | |
| 295 | − const app = createTestApp(); | |
| 296 | − const client = treaty(app); | |
| 297 | − | |
| 298 | − const { error } = await client.api.approvals({ callId: "unknown" }).post({ | |
| 299 | − decision: "approved", | |
| 300 | − }); | |
| 301 | − | |
| 302 | − expect(error).toBeDefined(); | |
| 303 | − expect(error!.status).toBe(404); | |
| 304 | − }); | |
| 305 | −}); | |
| 306 | − | |
| 307 | −describe("POST /api/tasks/:id/cancel", () => { | |
| 308 | − test("cancels a running task", async () => { | |
| 309 | − // Use a model that hangs forever (simulating a long-running task) | |
| 310 | − const hangingModel: LanguageModel = { | |
| 311 | − generate: () => new Promise(() => {}), // Never resolves | |
| 312 | − }; | |
| 313 | − | |
| 314 | − const app = createTestApp(hangingModel); | |
| 315 | − const client = treaty(app); | |
| 316 | − | |
| 317 | − const { data } = await client.api.tasks.post({ | |
| 318 | − prompt: "Hang forever", | |
| 319 | − requesterId: "user_1", | |
| 320 | − }); | |
| 321 | − | |
| 322 | − await Bun.sleep(50); | |
| 323 | − | |
| 324 | − const { data: result } = await client.api.tasks({ id: data!.taskId }).cancel.post(); | |
| 325 | − | |
| 326 | − expect(result!.status).toBe("cancelled"); | |
| 327 | − }); | |
| 328 | − | |
| 329 | − test("returns 404 for unknown task", async () => { | |
| 330 | − const app = createTestApp(); | |
| 331 | − const client = treaty(app); | |
| 332 | − | |
| 333 | − const { error } = await client.api.tasks({ id: "nonexistent" }).cancel.post(); | |
| 334 | − | |
| 335 | − expect(error).toBeDefined(); | |
| 336 | − expect(error!.status).toBe(404); | |
| 337 | − }); | |
| 338 | −}); | |
| 339 | − | |
| 340 | −describe("POST /internal/runs/:runId/invoke", () => { | |
| 341 | − test("invokes registered remote run callback", async () => { | |
| 342 | − const app = createTestApp(); | |
| 343 | − const runId = "run_test_invoke"; | |
| 344 | − | |
| 345 | − registerRemoteRunSession({ | |
| 346 | − runId, | |
| 347 | − token: "secret_token", | |
| 348 | − invokeTool: async (toolPath, input) => ({ | |
| 349 | − ok: true, | |
| 350 | − value: { toolPath, input }, | |
| 351 | − }), | |
| 352 | − }); | |
| 353 | − | |
| 354 | − try { | |
| 355 | − const response = await app.handle(new Request(`http://localhost/internal/runs/${runId}/invoke`, { | |
| 356 | − method: "POST", | |
| 357 | − headers: { | |
| 358 | − "content-type": "application/json", | |
| 359 | − authorization: "Bearer secret_token", | |
| 360 | − }, | |
| 361 | − body: JSON.stringify({ | |
| 362 | − toolPath: "echo", | |
| 363 | − input: { message: "hello" }, | |
| 364 | − }), | |
| 365 | − })); | |
| 366 | − | |
| 367 | − expect(response.status).toBe(200); | |
| 368 | − const body = await response.json() as { | |
| 369 | − ok: boolean; | |
| 370 | − value: { toolPath: string; input: { message: string } }; | |
| 371 | − }; | |
| 372 | − expect(body.ok).toBe(true); | |
| 373 | − expect(body.value.toolPath).toBe("echo"); | |
| 374 | − expect(body.value.input.message).toBe("hello"); | |
| 375 | − } finally { | |
| 376 | − removeRemoteRunSession(runId); | |
| 377 | − } | |
| 378 | − }); | |
| 379 | − | |
| 380 | − test("rejects invalid callback token", async () => { | |
| 381 | − const app = createTestApp(); | |
| 382 | − const runId = "run_test_unauthorized"; | |
| 383 | − | |
| 384 | − registerRemoteRunSession({ | |
| 385 | − runId, | |
| 386 | − token: "expected_token", | |
| 387 | − invokeTool: async () => ({ ok: true }), | |
| 388 | − }); | |
| 389 | − | |
| 390 | − try { | |
| 391 | − const response = await app.handle(new Request(`http://localhost/internal/runs/${runId}/invoke`, { | |
| 392 | − method: "POST", | |
| 393 | − headers: { | |
| 394 | − "content-type": "application/json", | |
| 395 | − authorization: "Bearer wrong_token", | |
| 396 | − }, | |
| 397 | − body: JSON.stringify({ | |
| 398 | − toolPath: "echo", | |
| 399 | − input: { message: "hello" }, | |
| 400 | − }), | |
| 401 | − })); | |
| 402 | − | |
| 403 | − expect(response.status).toBe(401); | |
| 404 | − } finally { | |
| 405 | − removeRemoteRunSession(runId); | |
| 406 | − } | |
| 407 | − }); | |
| 408 | −}); | |
| @@ -1,313 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Elysia server routes. | |
| 3 | − * | |
| 4 | − * Exports the Elysia app type for Eden Treaty consumption. | |
| 5 | − * Routes: | |
| 6 | − * POST /api/tasks — Create a new task | |
| 7 | − * GET /api/tasks — List tasks | |
| 8 | − * GET /api/tasks/:id — Get task status | |
| 9 | − * GET /api/tasks/:id/events — SSE stream of TaskEvents | |
| 10 | − * POST /api/tasks/:id/cancel — Cancel a task | |
| 11 | − * POST /api/approvals/:callId — Resolve an approval decision | |
| 12 | − */ | |
| 13 | − | |
| 14 | −import { Elysia, sse, t } from "elysia"; | |
| 15 | −import { | |
| 16 | − addApprovalRule, | |
| 17 | − createTask, | |
| 18 | − generateTaskId, | |
| 19 | − getTask, | |
| 20 | − listTasks, | |
| 21 | − listPendingApprovals, | |
| 22 | − getRemoteRunSession, | |
| 23 | − resolveApproval, | |
| 24 | − subscribeToTask, | |
| 25 | − type RuleOperator, | |
| 26 | −} from "./state.js"; | |
| 27 | −import { runTask, type TaskRunnerOptions } from "./task-runner.js"; | |
| 28 | − | |
| 29 | −// --------------------------------------------------------------------------- | |
| 30 | −// Elysia validation schemas (TypeBox — Elysia's native) | |
| 31 | −// --------------------------------------------------------------------------- | |
| 32 | − | |
| 33 | −const CreateTaskBody = t.Object({ | |
| 34 | − prompt: t.String({ minLength: 1 }), | |
| 35 | − requesterId: t.String({ minLength: 1 }), | |
| 36 | − channelId: t.Optional(t.String()), | |
| 37 | −}); | |
| 38 | − | |
| 39 | −const ApprovalBody = t.Object({ | |
| 40 | − decision: t.Union([t.Literal("approved"), t.Literal("denied")]), | |
| 41 | −}); | |
| 42 | − | |
| 43 | −const ApprovalRuleBody = t.Object({ | |
| 44 | − toolPath: t.String({ minLength: 1 }), | |
| 45 | − field: t.String({ minLength: 1 }), | |
| 46 | − operator: t.Union([ | |
| 47 | − t.Literal("equals"), | |
| 48 | − t.Literal("not_equals"), | |
| 49 | − t.Literal("includes"), | |
| 50 | − t.Literal("not_includes"), | |
| 51 | − ]), | |
| 52 | − value: t.String(), | |
| 53 | − decision: t.Union([t.Literal("approved"), t.Literal("denied")]), | |
| 54 | −}); | |
| 55 | − | |
| 56 | −const RemoteInvokeBody = t.Object({ | |
| 57 | − toolPath: t.String({ minLength: 1 }), | |
| 58 | − input: t.Any(), | |
| 59 | −}); | |
| 60 | − | |
| 61 | −// --------------------------------------------------------------------------- | |
| 62 | −// Task serialization (strip internal fields) | |
| 63 | −// --------------------------------------------------------------------------- | |
| 64 | − | |
| 65 | −function serializeTask(task: NonNullable<ReturnType<typeof getTask>>) { | |
| 66 | − // Find the last error event if any | |
| 67 | − const lastError = [...task.events].reverse().find((e) => e.type === "error"); | |
| 68 | − const errorMessage = lastError && lastError.type === "error" ? lastError.error : undefined; | |
| 69 | − | |
| 70 | − return { | |
| 71 | − id: task.id, | |
| 72 | − prompt: task.prompt, | |
| 73 | − requesterId: task.requesterId, | |
| 74 | − channelId: task.channelId, | |
| 75 | − executionMode: task.executionMode, | |
| 76 | − createdAt: task.createdAt, | |
| 77 | − status: task.status, | |
| 78 | − resultText: task.resultText, | |
| 79 | − errorMessage, | |
| 80 | − eventCount: task.events.length, | |
| 81 | − pendingApprovals: listPendingApprovals(task.id).map((a) => ({ | |
| 82 | − callId: a.callId, | |
| 83 | − toolPath: a.toolPath, | |
| 84 | − })), | |
| 85 | − }; | |
| 86 | −} | |
| 87 | − | |
| 88 | −// --------------------------------------------------------------------------- | |
| 89 | −// App factory | |
| 90 | −// --------------------------------------------------------------------------- | |
| 91 | − | |
| 92 | −export function createApp(runnerOptions: TaskRunnerOptions) { | |
| 93 | − const app = new Elysia() | |
| 94 | − // ----------------------------------------------------------------------- | |
| 95 | − // POST /api/tasks — create and start a task | |
| 96 | − // ----------------------------------------------------------------------- | |
| 97 | − .post( | |
| 98 | − "/api/tasks", | |
| 99 | − async ({ body }) => { | |
| 100 | − const taskId = generateTaskId(); | |
| 101 | − const executionMode = runnerOptions.executor ? "remote" as const : "local" as const; | |
| 102 | − const task = createTask({ | |
| 103 | − id: taskId, | |
| 104 | − prompt: body.prompt, | |
| 105 | − requesterId: body.requesterId, | |
| 106 | − channelId: body.channelId, | |
| 107 | − executionMode, | |
| 108 | − }); | |
| 109 | − | |
| 110 | − // Fire-and-forget: run the agent in the background | |
| 111 | − runTask(taskId, body.prompt, runnerOptions).catch((err) => { | |
| 112 | − console.error(`[task ${taskId}] unhandled error:`, err); | |
| 113 | − }); | |
| 114 | − | |
| 115 | − return { taskId: task.id, status: task.status, executionMode }; | |
| 116 | − }, | |
| 117 | − { body: CreateTaskBody }, | |
| 118 | − ) | |
| 119 | − | |
| 120 | − // ----------------------------------------------------------------------- | |
| 121 | − // GET /api/tasks — list all tasks | |
| 122 | − // ----------------------------------------------------------------------- | |
| 123 | − .get("/api/tasks", ({ query }) => { | |
| 124 | − const tasks = listTasks(query.requesterId); | |
| 125 | − return tasks.map(serializeTask); | |
| 126 | − }, { | |
| 127 | − query: t.Object({ | |
| 128 | − requesterId: t.Optional(t.String()), | |
| 129 | − }), | |
| 130 | − }) | |
| 131 | − | |
| 132 | − // ----------------------------------------------------------------------- | |
| 133 | − // GET /api/tasks/:id — get task status | |
| 134 | − // ----------------------------------------------------------------------- | |
| 135 | − .get( | |
| 136 | − "/api/tasks/:id", | |
| 137 | − ({ params, status }) => { | |
| 138 | − const task = getTask(params.id); | |
| 139 | − if (!task) { | |
| 140 | − return status(404, { error: "Task not found" as const }); | |
| 141 | − } | |
| 142 | − return serializeTask(task); | |
| 143 | − }, | |
| 144 | − ) | |
| 145 | − | |
| 146 | − // ----------------------------------------------------------------------- | |
| 147 | − // GET /api/tasks/:id/events — SSE stream of TaskEvents | |
| 148 | − // ----------------------------------------------------------------------- | |
| 149 | − .get( | |
| 150 | − "/api/tasks/:id/events", | |
| 151 | − async function* ({ params }) { | |
| 152 | − const task = getTask(params.id); | |
| 153 | − if (!task) { | |
| 154 | − yield sse({ event: "error", data: { error: "Task not found" } }); | |
| 155 | − return; | |
| 156 | − } | |
| 157 | − | |
| 158 | − // Subscribe FIRST to avoid race between replay and live events. | |
| 159 | − // Track how many events we've replayed so the subscriber can skip | |
| 160 | − // events that were already sent during replay. | |
| 161 | − type TaskEvent = (typeof task.events)[number]; | |
| 162 | − const queue: TaskEvent[] = []; | |
| 163 | − let resolveWait: (() => void) | null = null; | |
| 164 | − let done = false; | |
| 165 | − let replayedCount = 0; | |
| 166 | − | |
| 167 | − const unsubscribe = subscribeToTask(params.id, (event) => { | |
| 168 | − // During replay, events that are ALSO live will arrive here. | |
| 169 | − // We track replay count and only queue events beyond what was replayed. | |
| 170 | − // Since events are append-only, the subscriber index == task.events.length | |
| 171 | − // at the time of the call. We skip anything <= replayedCount. | |
| 172 | − queue.push(event); | |
| 173 | − if (resolveWait) { | |
| 174 | − resolveWait(); | |
| 175 | − resolveWait = null; | |
| 176 | − } | |
| 177 | − if (event.type === "completed" || event.type === "error") { | |
| 178 | − done = true; | |
| 179 | − } | |
| 180 | − }); | |
| 181 | − | |
| 182 | − if (!unsubscribe) { | |
| 183 | − return; | |
| 184 | − } | |
| 185 | − | |
| 186 | − // Replay existing events. Snapshot the current count to avoid | |
| 187 | − // replaying events that arrive during iteration. | |
| 188 | − const eventsSnapshot = task.events.length; | |
| 189 | − for (let i = 0; i < eventsSnapshot; i++) { | |
| 190 | − yield sse({ event: task.events[i]!.type, data: task.events[i] }); | |
| 191 | − } | |
| 192 | − replayedCount = eventsSnapshot; | |
| 193 | − | |
| 194 | − // If task is already done, close the stream | |
| 195 | − if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") { | |
| 196 | − unsubscribe(); | |
| 197 | − return; | |
| 198 | − } | |
| 199 | − | |
| 200 | − // Now drain the queue. Any events that arrived between subscribe | |
| 201 | − // and replay-end with index < replayedCount are skipped (they were | |
| 202 | − // already yielded in the replay loop above). We detect this by | |
| 203 | − // checking if the event is the same reference as one in task.events. | |
| 204 | − try { | |
| 205 | − while (!done) { | |
| 206 | − if (queue.length === 0) { | |
| 207 | − await new Promise<void>((resolve) => { | |
| 208 | − resolveWait = resolve; | |
| 209 | − }); | |
| 210 | − } | |
| 211 | − | |
| 212 | − while (queue.length > 0) { | |
| 213 | − const event = queue.shift()!; | |
| 214 | − // Skip events that were already replayed. The subscriber fires | |
| 215 | − // for ALL events (including ones emitted during replay). Check | |
| 216 | − // by finding this event's index in task.events — if it's within | |
| 217 | − // the replayed range, skip it. | |
| 218 | − const idx = task.events.indexOf(event); | |
| 219 | − if (idx !== -1 && idx < replayedCount) continue; | |
| 220 | − yield sse({ event: event.type, data: event }); | |
| 221 | − } | |
| 222 | − } | |
| 223 | − } finally { | |
| 224 | − unsubscribe(); | |
| 225 | − } | |
| 226 | − }, | |
| 227 | − ) | |
| 228 | − | |
| 229 | − // ----------------------------------------------------------------------- | |
| 230 | − // POST /api/tasks/:id/cancel — cancel a task | |
| 231 | − // ----------------------------------------------------------------------- | |
| 232 | − .post("/api/tasks/:id/cancel", ({ params, status }) => { | |
| 233 | − const task = getTask(params.id); | |
| 234 | − if (!task) { | |
| 235 | − return status(404, { error: "Task not found" as const }); | |
| 236 | − } | |
| 237 | − if (task.status !== "running") { | |
| 238 | − return status(400, { error: "Task is not running" as const }); | |
| 239 | − } | |
| 240 | − task.status = "cancelled"; | |
| 241 | − return { taskId: task.id, status: task.status }; | |
| 242 | − }) | |
| 243 | − | |
| 244 | − // ----------------------------------------------------------------------- | |
| 245 | − // POST /api/approvals/:callId — resolve a pending approval | |
| 246 | − // ----------------------------------------------------------------------- | |
| 247 | − .post( | |
| 248 | − "/api/approvals/:callId", | |
| 249 | − ({ params, body, status }) => { | |
| 250 | − const resolved = resolveApproval(params.callId, body.decision); | |
| 251 | − if (!resolved) { | |
| 252 | − return status(404, { error: "Approval not found or already resolved" as const }); | |
| 253 | − } | |
| 254 | − return { callId: params.callId, decision: body.decision }; | |
| 255 | − }, | |
| 256 | − { body: ApprovalBody }, | |
| 257 | − ) | |
| 258 | − | |
| 259 | − // ----------------------------------------------------------------------- | |
| 260 | − // POST /api/tasks/:id/approval-rules — add an auto-approval rule | |
| 261 | − // ----------------------------------------------------------------------- | |
| 262 | − .post( | |
| 263 | − "/api/tasks/:id/approval-rules", | |
| 264 | − ({ params, body, status }) => { | |
| 265 | − const task = getTask(params.id); | |
| 266 | − if (!task) { | |
| 267 | − return status(404, { error: "Task not found" as const }); | |
| 268 | − } | |
| 269 | − | |
| 270 | − const ruleId = `rule_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; | |
| 271 | − const resolved = addApprovalRule({ | |
| 272 | − id: ruleId, | |
| 273 | − taskId: params.id, | |
| 274 | − toolPath: body.toolPath, | |
| 275 | − field: body.field, | |
| 276 | − operator: body.operator as RuleOperator, | |
| 277 | − value: body.value, | |
| 278 | − decision: body.decision, | |
| 279 | − }); | |
| 280 | − | |
| 281 | − return { ruleId, resolved, toolPath: body.toolPath, field: body.field, operator: body.operator, value: body.value, decision: body.decision }; | |
| 282 | − }, | |
| 283 | − { body: ApprovalRuleBody }, | |
| 284 | − ) | |
| 285 | − | |
| 286 | − // ----------------------------------------------------------------------- | |
| 287 | − // POST /internal/runs/:runId/invoke — executor callback for tools.* calls | |
| 288 | − // ----------------------------------------------------------------------- | |
| 289 | − .post( | |
| 290 | − "/internal/runs/:runId/invoke", | |
| 291 | − async ({ params, body, headers, status }) => { | |
| 292 | − const session = getRemoteRunSession(params.runId); | |
| 293 | − if (!session) { | |
| 294 | − return status(404, { ok: false as const, error: "Run session not found" }); | |
| 295 | − } | |
| 296 | − | |
| 297 | − const authHeader = headers["authorization"]; | |
| 298 | − const bearer = typeof authHeader === "string" && authHeader.startsWith("Bearer ") | |
| 299 | − ? authHeader.slice("Bearer ".length) | |
| 300 | − : undefined; | |
| 301 | − if (!bearer || bearer !== session.token) { | |
| 302 | − return status(401, { ok: false as const, error: "Unauthorized" }); | |
| 303 | − } | |
| 304 | − | |
| 305 | − return await session.invokeTool(body.toolPath, body.input); | |
| 306 | − }, | |
| 307 | − { body: RemoteInvokeBody }, | |
| 308 | − ); | |
| 309 | − | |
| 310 | − return app; | |
| 311 | −} | |
| 312 | − | |
| 313 | −export type App = ReturnType<typeof createApp>; | |
| @@ -1,276 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * In-memory task state management. | |
| 3 | − * | |
| 4 | − * Tracks active tasks, their event streams, and pending approval resolvers. | |
| 5 | − * This is the MVP — swap for bun:sqlite later. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import type { TaskEvent } from "@openassistant/core/events"; | |
| 9 | −import type { ApprovalDecision } from "@openassistant/core/tools"; | |
| 10 | − | |
| 11 | −// --------------------------------------------------------------------------- | |
| 12 | −// Types | |
| 13 | −// --------------------------------------------------------------------------- | |
| 14 | − | |
| 15 | −export type TaskStatus = "running" | "completed" | "failed" | "cancelled"; | |
| 16 | −export type TaskExecutionMode = "local" | "remote"; | |
| 17 | − | |
| 18 | −export interface Task { | |
| 19 | − readonly id: string; | |
| 20 | − readonly prompt: string; | |
| 21 | − readonly requesterId: string; | |
| 22 | − readonly channelId?: string | undefined; | |
| 23 | − readonly executionMode: TaskExecutionMode; | |
| 24 | − readonly createdAt: number; | |
| 25 | − status: TaskStatus; | |
| 26 | − events: TaskEvent[]; | |
| 27 | − /** Subscribers get called for each new event. */ | |
| 28 | − subscribers: Set<(event: TaskEvent) => void>; | |
| 29 | − /** Final result text, set on completion. */ | |
| 30 | − resultText?: string | undefined; | |
| 31 | −} | |
| 32 | − | |
| 33 | −export interface PendingApproval { | |
| 34 | − readonly callId: string; | |
| 35 | − readonly taskId: string; | |
| 36 | − readonly toolPath: string; | |
| 37 | − readonly input: unknown; | |
| 38 | − resolve: (decision: ApprovalDecision) => void; | |
| 39 | −} | |
| 40 | − | |
| 41 | −export type RuleOperator = "equals" | "not_equals" | "includes" | "not_includes"; | |
| 42 | − | |
| 43 | −export interface ApprovalRule { | |
| 44 | − readonly id: string; | |
| 45 | − readonly taskId: string; | |
| 46 | − /** Tool path the rule applies to (e.g. "vercel.projects.removeProjectDomain") */ | |
| 47 | − readonly toolPath: string; | |
| 48 | − /** Dot-path into the input object (e.g. "idOrName", "owner") */ | |
| 49 | − readonly field: string; | |
| 50 | − readonly operator: RuleOperator; | |
| 51 | − readonly value: string; | |
| 52 | − readonly decision: ApprovalDecision; | |
| 53 | −} | |
| 54 | − | |
| 55 | −export interface RemoteRunInvokeResult { | |
| 56 | − readonly ok: boolean; | |
| 57 | − readonly value?: unknown; | |
| 58 | − readonly error?: string; | |
| 59 | − readonly denied?: boolean; | |
| 60 | −} | |
| 61 | − | |
| 62 | −export interface RemoteRunSession { | |
| 63 | − readonly runId: string; | |
| 64 | − readonly token: string; | |
| 65 | − readonly invokeTool: ( | |
| 66 | − toolPath: string, | |
| 67 | − input: unknown, | |
| 68 | − ) => Promise<RemoteRunInvokeResult>; | |
| 69 | −} | |
| 70 | − | |
| 71 | −// --------------------------------------------------------------------------- | |
| 72 | −// Store | |
| 73 | −// --------------------------------------------------------------------------- | |
| 74 | − | |
| 75 | −const tasks = new Map<string, Task>(); | |
| 76 | −const pendingApprovals = new Map<string, PendingApproval>(); | |
| 77 | −const approvalRules = new Map<string, ApprovalRule[]>(); // taskId → rules | |
| 78 | −const remoteRunSessions = new Map<string, RemoteRunSession>(); | |
| 79 | − | |
| 80 | −let taskCounter = 0; | |
| 81 | − | |
| 82 | −export function generateTaskId(): string { | |
| 83 | − return `task_${Date.now()}_${++taskCounter}`; | |
| 84 | −} | |
| 85 | − | |
| 86 | −// -- Tasks -- | |
| 87 | − | |
| 88 | −export function createTask(opts: { | |
| 89 | − id: string; | |
| 90 | − prompt: string; | |
| 91 | − requesterId: string; | |
| 92 | − channelId?: string | undefined; | |
| 93 | − executionMode: TaskExecutionMode; | |
| 94 | −}): Task { | |
| 95 | − const task: Task = { | |
| 96 | − id: opts.id, | |
| 97 | − prompt: opts.prompt, | |
| 98 | − requesterId: opts.requesterId, | |
| 99 | − channelId: opts.channelId, | |
| 100 | − executionMode: opts.executionMode, | |
| 101 | − createdAt: Date.now(), | |
| 102 | − status: "running", | |
| 103 | − events: [], | |
| 104 | − subscribers: new Set(), | |
| 105 | − }; | |
| 106 | − tasks.set(task.id, task); | |
| 107 | − return task; | |
| 108 | −} | |
| 109 | − | |
| 110 | −export function getTask(id: string): Task | undefined { | |
| 111 | − return tasks.get(id); | |
| 112 | −} | |
| 113 | − | |
| 114 | −export function listTasks(requesterId?: string): Task[] { | |
| 115 | − const all = [...tasks.values()]; | |
| 116 | − if (requesterId) { | |
| 117 | − return all.filter((t) => t.requesterId === requesterId); | |
| 118 | − } | |
| 119 | − return all; | |
| 120 | −} | |
| 121 | − | |
| 122 | −export function emitTaskEvent(taskId: string, event: TaskEvent): void { | |
| 123 | − const task = tasks.get(taskId); | |
| 124 | − if (!task) return; | |
| 125 | − | |
| 126 | − task.events.push(event); | |
| 127 | − | |
| 128 | − // Notify all subscribers | |
| 129 | − for (const subscriber of task.subscribers) { | |
| 130 | − try { | |
| 131 | − subscriber(event); | |
| 132 | − } catch { | |
| 133 | − // Subscriber threw — remove it | |
| 134 | − task.subscribers.delete(subscriber); | |
| 135 | − } | |
| 136 | − } | |
| 137 | − | |
| 138 | − // Update task status based on events | |
| 139 | − if (event.type === "completed") { | |
| 140 | − task.status = "completed"; | |
| 141 | − } else if (event.type === "error") { | |
| 142 | − task.status = "failed"; | |
| 143 | − } else if (event.type === "agent_message") { | |
| 144 | − task.resultText = event.text; | |
| 145 | − } | |
| 146 | −} | |
| 147 | − | |
| 148 | −export function subscribeToTask( | |
| 149 | − taskId: string, | |
| 150 | − callback: (event: TaskEvent) => void, | |
| 151 | −): (() => void) | undefined { | |
| 152 | − const task = tasks.get(taskId); | |
| 153 | − if (!task) return undefined; | |
| 154 | − | |
| 155 | − task.subscribers.add(callback); | |
| 156 | − return () => { | |
| 157 | − task.subscribers.delete(callback); | |
| 158 | − }; | |
| 159 | −} | |
| 160 | − | |
| 161 | −// -- Approvals -- | |
| 162 | − | |
| 163 | −export function registerApproval(approval: PendingApproval): void { | |
| 164 | − // Check rules first — auto-resolve if matched (scoped to same tool) | |
| 165 | − const autoDecision = checkApprovalRules(approval.taskId, approval.toolPath, approval.input); | |
| 166 | − if (autoDecision) { | |
| 167 | − // Resolve immediately without registering as pending | |
| 168 | − approval.resolve(autoDecision); | |
| 169 | − return; | |
| 170 | − } | |
| 171 | − pendingApprovals.set(approval.callId, approval); | |
| 172 | −} | |
| 173 | − | |
| 174 | −export function resolveApproval( | |
| 175 | − callId: string, | |
| 176 | − decision: ApprovalDecision, | |
| 177 | −): boolean { | |
| 178 | − const approval = pendingApprovals.get(callId); | |
| 179 | − if (!approval) return false; | |
| 180 | − | |
| 181 | − approval.resolve(decision); | |
| 182 | − pendingApprovals.delete(callId); | |
| 183 | − return true; | |
| 184 | −} | |
| 185 | − | |
| 186 | −export function getPendingApproval(callId: string): PendingApproval | undefined { | |
| 187 | − return pendingApprovals.get(callId); | |
| 188 | −} | |
| 189 | − | |
| 190 | −export function listPendingApprovals(taskId?: string): PendingApproval[] { | |
| 191 | − const all = [...pendingApprovals.values()]; | |
| 192 | − if (taskId) { | |
| 193 | − return all.filter((a) => a.taskId === taskId); | |
| 194 | − } | |
| 195 | − return all; | |
| 196 | −} | |
| 197 | − | |
| 198 | −// -- Approval Rules -- | |
| 199 | − | |
| 200 | −/** | |
| 201 | − * Get the value at a dot-path in an object. | |
| 202 | − * e.g. getFieldValue({ a: { b: "c" } }, "a.b") → "c" | |
| 203 | − */ | |
| 204 | −function getFieldValue(obj: unknown, path: string): unknown { | |
| 205 | − let current: unknown = obj; | |
| 206 | − for (const key of path.split(".")) { | |
| 207 | − if (current == null || typeof current !== "object") return undefined; | |
| 208 | − current = (current as Record<string, unknown>)[key]; | |
| 209 | − } | |
| 210 | − return current; | |
| 211 | −} | |
| 212 | − | |
| 213 | −/** | |
| 214 | − * Check if an approval input matches a rule. | |
| 215 | − */ | |
| 216 | −function matchesRule(input: unknown, rule: ApprovalRule): boolean { | |
| 217 | − const fieldValue = String(getFieldValue(input, rule.field) ?? ""); | |
| 218 | − switch (rule.operator) { | |
| 219 | − case "equals": return fieldValue === rule.value; | |
| 220 | − case "not_equals": return fieldValue !== rule.value; | |
| 221 | − case "includes": return fieldValue.includes(rule.value); | |
| 222 | − case "not_includes": return !fieldValue.includes(rule.value); | |
| 223 | − } | |
| 224 | −} | |
| 225 | − | |
| 226 | −/** | |
| 227 | − * Check if any rules match this approval. Returns the decision if matched. | |
| 228 | − * Rules only apply to the same tool path they were created for. | |
| 229 | − */ | |
| 230 | −export function checkApprovalRules(taskId: string, toolPath: string, input: unknown): ApprovalDecision | undefined { | |
| 231 | − const rules = approvalRules.get(taskId); | |
| 232 | − if (!rules || rules.length === 0) return undefined; | |
| 233 | − for (const rule of rules) { | |
| 234 | − if (rule.toolPath === toolPath && matchesRule(input, rule)) return rule.decision; | |
| 235 | − } | |
| 236 | − return undefined; | |
| 237 | −} | |
| 238 | − | |
| 239 | −/** | |
| 240 | − * Add a rule for a task. Also retroactively applies to pending approvals | |
| 241 | − * for the same tool. Returns the number of approvals that were auto-resolved. | |
| 242 | − */ | |
| 243 | −export function addApprovalRule(rule: ApprovalRule): number { | |
| 244 | − const existing = approvalRules.get(rule.taskId) ?? []; | |
| 245 | − existing.push(rule); | |
| 246 | − approvalRules.set(rule.taskId, existing); | |
| 247 | − | |
| 248 | − // Retroactively apply to pending approvals for the same tool | |
| 249 | − let resolved = 0; | |
| 250 | − const pending = listPendingApprovals(rule.taskId); | |
| 251 | − for (const approval of pending) { | |
| 252 | − if (approval.toolPath === rule.toolPath && matchesRule(approval.input, rule)) { | |
| 253 | − resolveApproval(approval.callId, rule.decision); | |
| 254 | − resolved++; | |
| 255 | − } | |
| 256 | − } | |
| 257 | − return resolved; | |
| 258 | −} | |
| 259 | − | |
| 260 | −export function listApprovalRules(taskId: string): ApprovalRule[] { | |
| 261 | − return approvalRules.get(taskId) ?? []; | |
| 262 | −} | |
| 263 | − | |
| 264 | −// -- Remote Runner Sessions -- | |
| 265 | − | |
| 266 | −export function registerRemoteRunSession(session: RemoteRunSession): void { | |
| 267 | − remoteRunSessions.set(session.runId, session); | |
| 268 | −} | |
| 269 | − | |
| 270 | −export function getRemoteRunSession(runId: string): RemoteRunSession | undefined { | |
| 271 | − return remoteRunSessions.get(runId); | |
| 272 | −} | |
| 273 | − | |
| 274 | −export function removeRemoteRunSession(runId: string): void { | |
| 275 | − remoteRunSessions.delete(runId); | |
| 276 | −} | |
| @@ -1,201 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Task runner — wires createAgent to the task state system. | |
| 3 | − * | |
| 4 | − * Starts an agent run for a task, emitting TaskEvents to state, | |
| 5 | − * with approval resolution via the pending approval registry. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import { createRemoteRunner } from "@openassistant/core"; | |
| 9 | −import type { ToolDefinition, ToolTree, ApprovalRequest, ApprovalDecision } from "@openassistant/core/tools"; | |
| 10 | −import { isToolDefinition } from "@openassistant/core/tools"; | |
| 11 | −import type { LanguageModel } from "@openassistant/core/agent"; | |
| 12 | −import { createAgent } from "@openassistant/core/agent"; | |
| 13 | −import { | |
| 14 | − emitTaskEvent, | |
| 15 | − getTask, | |
| 16 | − registerRemoteRunSession, | |
| 17 | − registerApproval, | |
| 18 | − removeRemoteRunSession, | |
| 19 | − type RemoteRunInvokeResult, | |
| 20 | −} from "./state.js"; | |
| 21 | − | |
| 22 | −export interface TaskRunnerOptions { | |
| 23 | − readonly tools: ToolTree; | |
| 24 | − readonly model: LanguageModel; | |
| 25 | − /** Additional context for the system prompt (project IDs, org info, etc.) */ | |
| 26 | − readonly context?: string | undefined; | |
| 27 | − /** Optional remote executor config. If omitted, uses in-process runner. */ | |
| 28 | − readonly executor?: { | |
| 29 | − readonly url: string; | |
| 30 | − readonly callbackBaseUrl: string; | |
| 31 | − } | undefined; | |
| 32 | −} | |
| 33 | − | |
| 34 | −function describeError(error: unknown): string { | |
| 35 | − if (error instanceof Error) return error.message; | |
| 36 | − return String(error); | |
| 37 | −} | |
| 38 | − | |
| 39 | −function buildFallbackApprovalPreview(toolPath: string, input: unknown) { | |
| 40 | − return { | |
| 41 | − title: `Run via ${toolPath}`, | |
| 42 | − details: `Arguments: ${JSON.stringify(input)}`, | |
| 43 | − action: "execute" as const, | |
| 44 | − }; | |
| 45 | −} | |
| 46 | − | |
| 47 | −function resolveToolDefinition(tools: ToolTree, toolPath: string): ToolDefinition | undefined { | |
| 48 | − const parts = toolPath.split(".").filter(Boolean); | |
| 49 | − if (parts.length === 0) return undefined; | |
| 50 | − | |
| 51 | − let current: ToolTree | ToolDefinition = tools; | |
| 52 | − for (const part of parts) { | |
| 53 | − if (isToolDefinition(current)) return undefined; | |
| 54 | − const next = current[part]; | |
| 55 | − if (!next) return undefined; | |
| 56 | − current = next as ToolTree | ToolDefinition; | |
| 57 | − } | |
| 58 | − | |
| 59 | − return isToolDefinition(current) ? current : undefined; | |
| 60 | −} | |
| 61 | − | |
| 62 | −/** | |
| 63 | − * Run an agent turn for a task. Emits events to the task's event stream | |
| 64 | − * and registers approval requests that can be resolved via the REST API. | |
| 65 | − */ | |
| 66 | −export async function runTask( | |
| 67 | − taskId: string, | |
| 68 | − prompt: string, | |
| 69 | − options: TaskRunnerOptions, | |
| 70 | −): Promise<void> { | |
| 71 | − const task = getTask(taskId); | |
| 72 | − if (!task) { | |
| 73 | − throw new Error(`Task ${taskId} not found`); | |
| 74 | − } | |
| 75 | − | |
| 76 | − const requestApproval = (request: ApprovalRequest): Promise<ApprovalDecision> => { | |
| 77 | − // Emit the approval_request event so SSE subscribers see it | |
| 78 | − emitTaskEvent(taskId, { | |
| 79 | − type: "approval_request", | |
| 80 | − id: request.callId, | |
| 81 | − toolPath: request.toolPath, | |
| 82 | − input: request.input, | |
| 83 | − preview: request.preview, | |
| 84 | − }); | |
| 85 | − | |
| 86 | − return new Promise<ApprovalDecision>((resolve) => { | |
| 87 | − registerApproval({ | |
| 88 | − callId: request.callId, | |
| 89 | − taskId, | |
| 90 | − toolPath: request.toolPath, | |
| 91 | − input: request.input, | |
| 92 | − resolve: (decision: ApprovalDecision) => { | |
| 93 | − // Emit resolved event before unblocking the runner | |
| 94 | − emitTaskEvent(taskId, { | |
| 95 | − type: "approval_resolved", | |
| 96 | − id: request.callId, | |
| 97 | − decision, | |
| 98 | − }); | |
| 99 | − resolve(decision); | |
| 100 | − }, | |
| 101 | − }); | |
| 102 | − }); | |
| 103 | − }; | |
| 104 | − | |
| 105 | − let approvalSeq = 0; | |
| 106 | − const invokeToolForRemoteRun = async ( | |
| 107 | − toolPath: string, | |
| 108 | − input: unknown, | |
| 109 | − ): Promise<RemoteRunInvokeResult> => { | |
| 110 | − const tool = resolveToolDefinition(options.tools, toolPath); | |
| 111 | − if (!tool) { | |
| 112 | − return { ok: false, error: `Unknown tool: ${toolPath}` }; | |
| 113 | − } | |
| 114 | − | |
| 115 | − const parseResult = tool.args.safeParse(input); | |
| 116 | − if (!parseResult.success) { | |
| 117 | − return { | |
| 118 | − ok: false, | |
| 119 | − error: `Input validation failed: ${parseResult.error.message}`, | |
| 120 | − }; | |
| 121 | − } | |
| 122 | − | |
| 123 | − const validatedInput = parseResult.data; | |
| 124 | − if (tool.approval === "required") { | |
| 125 | − const approvalDecision = await requestApproval({ | |
| 126 | − callId: `remote_${taskId}_${++approvalSeq}`, | |
| 127 | − toolPath, | |
| 128 | − input: validatedInput, | |
| 129 | − preview: tool.formatApproval | |
| 130 | − ? tool.formatApproval(validatedInput) | |
| 131 | − : buildFallbackApprovalPreview(toolPath, validatedInput), | |
| 132 | − }); | |
| 133 | − | |
| 134 | − if (approvalDecision === "denied") { | |
| 135 | − return { | |
| 136 | − ok: true, | |
| 137 | − denied: true, | |
| 138 | − error: `Tool call denied: ${toolPath}`, | |
| 139 | − }; | |
| 140 | − } | |
| 141 | − } | |
| 142 | − | |
| 143 | − try { | |
| 144 | − const value = await tool.run(validatedInput); | |
| 145 | − return { ok: true, value }; | |
| 146 | − } catch (error) { | |
| 147 | − return { ok: false, error: describeError(error) }; | |
| 148 | − } | |
| 149 | − }; | |
| 150 | − | |
| 151 | − const runId = `run_${taskId}_${Date.now().toString(36)}`; | |
| 152 | − let registeredRemoteRun = false; | |
| 153 | − | |
| 154 | − const remoteRunner = options.executor | |
| 155 | − ? (() => { | |
| 156 | − const callbackToken = crypto.randomUUID(); | |
| 157 | − registerRemoteRunSession({ | |
| 158 | − runId, | |
| 159 | − token: callbackToken, | |
| 160 | − invokeTool: invokeToolForRemoteRun, | |
| 161 | − }); | |
| 162 | − registeredRemoteRun = true; | |
| 163 | − | |
| 164 | − return createRemoteRunner({ | |
| 165 | − tools: options.tools, | |
| 166 | − executorUrl: options.executor.url, | |
| 167 | − runId, | |
| 168 | − callbackBaseUrl: options.executor.callbackBaseUrl, | |
| 169 | − callbackToken, | |
| 170 | − }); | |
| 171 | − })() | |
| 172 | − : undefined; | |
| 173 | − | |
| 174 | − const agent = createAgent({ | |
| 175 | − tools: options.tools, | |
| 176 | − model: options.model, | |
| 177 | − context: options.context, | |
| 178 | − requestApproval, | |
| 179 | − runner: remoteRunner, | |
| 180 | − onEvent: (event) => { | |
| 181 | − emitTaskEvent(taskId, event); | |
| 182 | − }, | |
| 183 | − }); | |
| 184 | − | |
| 185 | − try { | |
| 186 | − await agent.run(prompt); | |
| 187 | − } catch (error) { | |
| 188 | − const message = describeError(error); | |
| 189 | − const stack = error instanceof Error ? error.stack : undefined; | |
| 190 | − console.error(`[task ${taskId}] agent error:`, message); | |
| 191 | − if (stack) console.error(stack); | |
| 192 | − emitTaskEvent(taskId, { | |
| 193 | − type: "error", | |
| 194 | − error: message, | |
| 195 | − }); | |
| 196 | − } finally { | |
| 197 | − if (registeredRemoteRun) { | |
| 198 | − removeRemoteRunSession(runId); | |
| 199 | − } | |
| 200 | − } | |
| 201 | −} | |
| @@ -1,15 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "extends": "../../tsconfig.base.json", | |
| 3 | − "compilerOptions": { | |
| 4 | − "rootDir": "src", | |
| 5 | − "outDir": "dist", | |
| 6 | − "composite": true, | |
| 7 | − "noUnusedLocals": false, | |
| 8 | − "noUnusedParameters": false | |
| 9 | − }, | |
| 10 | − "include": ["src/**/*.ts"], | |
| 11 | − "references": [ | |
| 12 | − { "path": "../../packages/core" }, | |
| 13 | − { "path": "../../packages/tool-gen" } | |
| 14 | − ] | |
| 15 | −} | |
| @@ -1,1127 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "lockfileVersion": 1, | |
| 3 | − "configVersion": 1, | |
| 4 | − "workspaces": { | |
| 5 | − "": { | |
| 6 | − "name": "assistant", | |
| 7 | − "dependencies": { | |
| 8 | − "@apidevtools/swagger-parser": "^12.1.0", | |
| 9 | − "@modelcontextprotocol/sdk": "^1.26.0", | |
| 10 | − "yaml": "^2.8.2", | |
| 11 | − }, | |
| 12 | − "devDependencies": { | |
| 13 | − "@effect/language-service": "latest", | |
| 14 | − "@effect/vitest": "^0.27.0", | |
| 15 | − "@types/bun": "latest", | |
| 16 | − "@typescript/native-preview": "7.0.0-dev.20260205.1", | |
| 17 | − "typescript": "latest", | |
| 18 | − "vitest": "^3.2.0", | |
| 19 | − }, | |
| 20 | − }, | |
| 21 | − "apps/bot": { | |
| 22 | − "name": "@openassistant/bot", | |
| 23 | − "dependencies": { | |
| 24 | − "@elysiajs/eden": "latest", | |
| 25 | − "@openassistant/core": "workspace:*", | |
| 26 | − "@openassistant/reacord": "workspace:*", | |
| 27 | − "@openassistant/server": "workspace:*", | |
| 28 | − "discord.js": "^14.17.3", | |
| 29 | − "effect": "latest", | |
| 30 | − "react": "^19.0.0", | |
| 31 | − }, | |
| 32 | − "devDependencies": { | |
| 33 | − "@types/bun": "latest", | |
| 34 | − "@types/react": "^19.0.0", | |
| 35 | − }, | |
| 36 | − }, | |
| 37 | − "apps/executor": { | |
| 38 | − "name": "@openassistant/executor", | |
| 39 | − "dependencies": { | |
| 40 | − "@openassistant/core": "workspace:*", | |
| 41 | − "zod": "^4.3.6", | |
| 42 | − }, | |
| 43 | − "devDependencies": { | |
| 44 | − "@types/bun": "latest", | |
| 45 | − }, | |
| 46 | − }, | |
| 47 | − "apps/server": { | |
| 48 | − "name": "@openassistant/server", | |
| 49 | − "dependencies": { | |
| 50 | − "@elysiajs/eden": "latest", | |
| 51 | − "@openassistant/core": "workspace:*", | |
| 52 | − "@openassistant/tool-gen": "workspace:*", | |
| 53 | − "elysia": "latest", | |
| 54 | − "zod": "^4.3.6", | |
| 55 | − }, | |
| 56 | − "devDependencies": { | |
| 57 | − "@types/bun": "latest", | |
| 58 | − }, | |
| 59 | − }, | |
| 60 | − "packages/core": { | |
| 61 | − "name": "@openassistant/core", | |
| 62 | − "dependencies": { | |
| 63 | − "@mariozechner/pi-ai": "^0.52.6", | |
| 64 | − "@sinclair/typebox": "^0.34.48", | |
| 65 | − "effect": "latest", | |
| 66 | − "zod": "^4.3.6", | |
| 67 | − }, | |
| 68 | − }, | |
| 69 | − "packages/reacord": { | |
| 70 | − "name": "@openassistant/reacord", | |
| 71 | − "dependencies": { | |
| 72 | − "@effect-atom/atom": "^0.4.11", | |
| 73 | − "@effect-atom/atom-react": "^0.4.4", | |
| 74 | − "discord.js": "^14.17.3", | |
| 75 | − "effect": "latest", | |
| 76 | − "react": "^19.0.0", | |
| 77 | − "react-reconciler": "^0.31.0", | |
| 78 | − }, | |
| 79 | − "devDependencies": { | |
| 80 | − "@types/react": "^19.0.0", | |
| 81 | − "@types/react-reconciler": "^0.31.0", | |
| 82 | − }, | |
| 83 | − }, | |
| 84 | − "packages/tool-gen": { | |
| 85 | − "name": "@openassistant/tool-gen", | |
| 86 | − "dependencies": { | |
| 87 | − "@apidevtools/swagger-parser": "^12.1.0", | |
| 88 | − "@mariozechner/pi-ai": "^0.52.6", | |
| 89 | − "@modelcontextprotocol/sdk": "^1.26.0", | |
| 90 | − "@openassistant/core": "workspace:*", | |
| 91 | − "@sinclair/typebox": "^0.34.48", | |
| 92 | − "openapi-typescript": "^7.10.1", | |
| 93 | − "yaml": "^2.8.2", | |
| 94 | − "zod": "^4.3.6", | |
| 95 | − }, | |
| 96 | − }, | |
| 97 | − }, | |
| 98 | − "overrides": { | |
| 99 | − "@ai-sdk/anthropic": "^2.0.0", | |
| 100 | − "@effect-atom/atom": "^0.4.11", | |
| 101 | − "@effect-atom/atom-react": "^0.4.4", | |
| 102 | − "@effect/platform": "latest", | |
| 103 | − "@effect/platform-bun": "latest", | |
| 104 | − "@effect/rpc": "latest", | |
| 105 | − "@types/react": "^19.0.0", | |
| 106 | − "@types/react-reconciler": "^0.31.0", | |
| 107 | − "ai": "^6.0.73", | |
| 108 | − "discord.js": "^14.17.3", | |
| 109 | − "effect": "latest", | |
| 110 | − "react": "^19.0.0", | |
| 111 | − "react-reconciler": "^0.31.0", | |
| 112 | − "zod": "^4.3.6", | |
| 113 | − }, | |
| 114 | − "packages": { | |
| 115 | − "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.73.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw=="], | |
| 116 | − | |
| 117 | − "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@14.0.1", "", { "dependencies": { "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw=="], | |
| 118 | − | |
| 119 | − "@apidevtools/openapi-schemas": ["@apidevtools/openapi-schemas@2.1.0", "", {}, "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ=="], | |
| 120 | − | |
| 121 | − "@apidevtools/swagger-methods": ["@apidevtools/swagger-methods@3.0.2", "", {}, "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="], | |
| 122 | − | |
| 123 | − "@apidevtools/swagger-parser": ["@apidevtools/swagger-parser@12.1.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "14.0.1", "@apidevtools/openapi-schemas": "^2.1.0", "@apidevtools/swagger-methods": "^3.0.2", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "call-me-maybe": "^1.0.2" }, "peerDependencies": { "openapi-types": ">=7" } }, "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng=="], | |
| 124 | − | |
| 125 | − "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], | |
| 126 | − | |
| 127 | − "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], | |
| 128 | − | |
| 129 | − "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], | |
| 130 | − | |
| 131 | − "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], | |
| 132 | − | |
| 133 | − "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], | |
| 134 | − | |
| 135 | − "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.984.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-node": "^3.972.5", "@aws-sdk/eventstream-handler-node": "^3.972.5", "@aws-sdk/middleware-eventstream": "^3.972.3", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/middleware-websocket": "^3.972.5", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/token-providers": "3.984.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iFrdkDXdo+ELZ5qD8ZYw9MHoOhcXyVutO8z7csnYpJO0rbET/X6B8cQlOCMsqJHxkyMwW21J4vt9S5k2/FgPCg=="], | |
| 136 | − | |
| 137 | − "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.982.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-qJrIiivmvujdGqJ0ldSUvhN3k3N7GtPesoOI1BSt0fNXovVnMz4C/JmnkhZihU7hJhDvxJaBROLYTU+lpild4w=="], | |
| 138 | − | |
| 139 | − "@aws-sdk/core": ["@aws-sdk/core@3.973.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", "@smithy/core": "^3.22.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-pz4ZOw3BLG0NdF25HoB9ymSYyPbMiIjwQJ2aROXRhAzt+b+EOxStfFv8s5iZyP6Kiw7aYhyWxj5G3NhmkoOTKw=="], | |
| 140 | − | |
| 141 | − "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-/8dnc7+XNMmViEom2xsNdArQxQPSgy4Z/lm6qaFPTrMFesT1bV3PsBhb19n09nmxHdrtQskYmViddUIjUQElXg=="], | |
| 142 | − | |
| 143 | − "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-5ERWqRljiZv44AIdvIRQ3k+EAV0Sq2WeJHvXuK7gL7bovSxOf8Al7MLH7Eh3rdovH4KHFnlIty7J71mzvQBl5Q=="], | |
| 144 | − | |
| 145 | − "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/credential-provider-env": "^3.972.4", "@aws-sdk/credential-provider-http": "^3.972.6", "@aws-sdk/credential-provider-login": "^3.972.4", "@aws-sdk/credential-provider-process": "^3.972.4", "@aws-sdk/credential-provider-sso": "^3.972.4", "@aws-sdk/credential-provider-web-identity": "^3.972.4", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eRUg+3HaUKuXWn/lEMirdiA5HOKmEl8hEHVuszIDt2MMBUKgVX5XNGmb3XmbgU17h6DZ+RtjbxQpjhz3SbTjZg=="], | |
| 146 | − | |
| 147 | − "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nLGjXuvWWDlQAp505xIONI7Gam0vw2p7Qu3P6on/W2q7rjJXtYjtpHbcsaOjJ/pAju3eTvEQuSuRedcRHVQIAQ=="], | |
| 148 | − | |
| 149 | − "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.5", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.4", "@aws-sdk/credential-provider-http": "^3.972.6", "@aws-sdk/credential-provider-ini": "^3.972.4", "@aws-sdk/credential-provider-process": "^3.972.4", "@aws-sdk/credential-provider-sso": "^3.972.4", "@aws-sdk/credential-provider-web-identity": "^3.972.4", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-VWXKgSISQCI2GKN3zakTNHSiZ0+mux7v6YHmmbLQp/o3fvYUQJmKGcLZZzg2GFA+tGGBStplra9VFNf/WwxpYg=="], | |
| 150 | − | |
| 151 | − "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-TCZpWUnBQN1YPk6grvd5x419OfXjHvhj5Oj44GYb84dOVChpg/+2VoEj+YVA4F4E/6huQPNnX7UYbTtxJqgihw=="], | |
| 152 | − | |
| 153 | − "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.4", "", { "dependencies": { "@aws-sdk/client-sso": "3.982.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/token-providers": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-wzsGwv9mKlwJ3vHLyembBvGE/5nPUIwRR2I51B1cBV4Cb4ql9nIIfpmHzm050XYTY5fqTOKJQnhLj7zj89VG8g=="], | |
| 154 | − | |
| 155 | − "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.4", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-hIzw2XzrG8jzsUSEatehmpkd5rWzASg5IHUfA+m01k/RtvfAML7ZJVVohuKdhAYx+wV2AThLiQJVzqn7F0khrw=="], | |
| 156 | − | |
| 157 | − "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-xEmd3dnyn83K6t4AJxBJA63wpEoCD45ERFG0XMTViD2E/Ohls9TLxjOWPb1PAxR9/46cKy/TImez1GoqP6xVNQ=="], | |
| 158 | − | |
| 159 | − "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-pbvZ6Ye/Ks6BAZPa3RhsNjHrvxU9li25PMhSdDpbX0jzdpKpAkIR65gXSNKmA/REnSdEMWSD4vKUW+5eMFzB6w=="], | |
| 160 | − | |
| 161 | − "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="], | |
| 162 | − | |
| 163 | − "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="], | |
| 164 | − | |
| 165 | − "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="], | |
| 166 | − | |
| 167 | − "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.6", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@smithy/core": "^3.22.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-TehLN8W/kivl0U9HcS+keryElEWORROpghDXZBLfnb40DXM7hx/i+7OOjkogXQOF3QtUraJVRkHQ07bPhrWKlw=="], | |
| 168 | − | |
| 169 | − "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-format-url": "^3.972.3", "@smithy/eventstream-codec": "^4.2.8", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-BN4A9K71WRIlpQ3+IYGdBC2wVyobZ95g6ZomodmJ8Te772GWo0iDk2Mv6JIHdr842tOTgi1b3npLIFDUS4hl4g=="], | |
| 170 | − | |
| 171 | − "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.984.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.984.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-E9Os+U9NWFoEJXbTVT8sCi+HMnzmsMA8cuCkvlUUfin/oWewUTnCkB/OwFwiUQ2N7v1oBk+i4ZSsI1PiuOy8/w=="], | |
| 172 | − | |
| 173 | − "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="], | |
| 174 | − | |
| 175 | − "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.984.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/nested-clients": "3.984.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-UJ/+OzZv+4nAQ1bSspCSb4JlYbMB2Adn8CK7hySpKX5sjhRu1bm6w1PqQq59U67LZEKsPdhl1rzcZ7ybK8YQxw=="], | |
| 176 | − | |
| 177 | − "@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="], | |
| 178 | − | |
| 179 | − "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.984.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-9ebjLA0hMKHeVvXEtTDCCOBtwjb0bOXiuUV06HNeVdgAjH6gj4x4Zwt4IBti83TiyTGOCl5YfZqGx4ehVsasbQ=="], | |
| 180 | − | |
| 181 | − "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g=="], | |
| 182 | − | |
| 183 | − "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="], | |
| 184 | − | |
| 185 | − "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="], | |
| 186 | − | |
| 187 | − "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.4", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-3WFCBLiM8QiHDfosQq3Py+lIMgWlFWwFQliUHUqwEiRqLnKyhgbU3AKa7AWJF7lW2Oc/2kFNY4MlAYVnVc0i8A=="], | |
| 188 | − | |
| 189 | − "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.4", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" } }, "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q=="], | |
| 190 | − | |
| 191 | − "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="], | |
| 192 | − | |
| 193 | − "@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=="], | |
| 194 | − | |
| 195 | − "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], | |
| 196 | − | |
| 197 | − "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], | |
| 198 | − | |
| 199 | − "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], | |
| 200 | − | |
| 201 | − "@discordjs/builders": ["@discordjs/builders@1.13.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="], | |
| 202 | − | |
| 203 | − "@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="], | |
| 204 | − | |
| 205 | − "@discordjs/formatters": ["@discordjs/formatters@0.6.2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="], | |
| 206 | − | |
| 207 | − "@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="], | |
| 208 | − | |
| 209 | − "@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="], | |
| 210 | − | |
| 211 | − "@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="], | |
| 212 | − | |
| 213 | − "@effect-atom/atom": ["@effect-atom/atom@0.4.13", "", { "peerDependencies": { "@effect/experimental": "^0.57.0", "@effect/platform": "^0.93.0", "@effect/rpc": "^0.72.1", "effect": "^3.19.0" } }, "sha512-wYCPDg/vN2IWTqxho7kTTSivh//V2TSAcdkQvLXMJWww8YzhcCGmCiDpT6cefyNXvHj+qZrqYB54mrUqG6EYCA=="], | |
| 214 | − | |
| 215 | − "@effect-atom/atom-react": ["@effect-atom/atom-react@0.4.6", "", { "dependencies": { "@effect-atom/atom": "^0.4.13" }, "peerDependencies": { "effect": "^3.19", "react": ">=18 <20", "scheduler": "*" } }, "sha512-ZvncuSQsCcUrsddD00JcKDMraPge0AiDMGiJqKc7ProvagCVrxB+Emy5uHM0l0SLGUx4X56bFE0d1tp9Z/Bf+g=="], | |
| 216 | − | |
| 217 | − "@effect/experimental": ["@effect/experimental@0.57.11", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.93.6", "effect": "^3.19.9", "ioredis": "^5", "lmdb": "^3" }, "optionalPeers": ["ioredis", "lmdb"] }, "sha512-M5uug3Drs/gyTHLfA+XzcIZQGUEV/Jn5yi1POki4oZswhpzNmsVTHl4THpxAordRKwa5lFvTSlsRP684YH7pSw=="], | |
| 218 | − | |
| 219 | − "@effect/language-service": ["@effect/language-service@0.73.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-/SGoq50VDm/XwQI6d0cReYcLjwfdqwZv7uEJp92+ssx5vLCsm+QvHf4Ul6l6PYVzorsgAp/b6fhAwI2VSkqcJQ=="], | |
| 220 | − | |
| 221 | − "@effect/platform": ["@effect/platform@0.94.3", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.16" } }, "sha512-bvTR8xLQoRpKgHuprZDOeQdPkhyVw+WT05iI9jl2s8Qiblyk5Dz2JLwJU+EFeksIBaPYz49xa635Om91T1CefQ=="], | |
| 222 | − | |
| 223 | − "@effect/rpc": ["@effect/rpc@0.73.0", "", { "dependencies": { "msgpackr": "^1.11.4" }, "peerDependencies": { "@effect/platform": "^0.94.0", "effect": "^3.19.13" } }, "sha512-iMPf6tTriz8sK0l5x4koFId8Hz5nFptHYg8WqyjHGIIVLTpZxuiSqhmXZG7FnAs5N2n6uCEws4wWGcIgXNUrFg=="], | |
| 224 | − | |
| 225 | − "@effect/vitest": ["@effect/vitest@0.27.0", "", { "peerDependencies": { "effect": "^3.19.0", "vitest": "^3.2.0" } }, "sha512-8bM7n9xlMUYw9GqPIVgXFwFm2jf27m/R7psI64PGpwU5+26iwyxp9eAXEsfT5S6lqztYfpQQ1Ubp5o6HfNYzJQ=="], | |
| 226 | − | |
| 227 | − "@elysiajs/eden": ["@elysiajs/eden@1.4.6", "", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-Tsa4NwXEWg/u73vWiYZQ3L5/ecgZSxqiEjYwpS+4qBKXeTZqZKl2hcgHJSVBL+InEDMi35Xugct7qyAXE5oM4Q=="], | |
| 228 | − | |
| 229 | − "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], | |
| 230 | − | |
| 231 | − "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], | |
| 232 | − | |
| 233 | − "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], | |
| 234 | − | |
| 235 | − "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], | |
| 236 | − | |
| 237 | − "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], | |
| 238 | − | |
| 239 | − "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], | |
| 240 | − | |
| 241 | − "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], | |
| 242 | − | |
| 243 | − "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], | |
| 244 | − | |
| 245 | − "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], | |
| 246 | − | |
| 247 | − "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], | |
| 248 | − | |
| 249 | − "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], | |
| 250 | − | |
| 251 | − "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], | |
| 252 | − | |
| 253 | − "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], | |
| 254 | − | |
| 255 | − "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], | |
| 256 | − | |
| 257 | − "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], | |
| 258 | − | |
| 259 | − "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], | |
| 260 | − | |
| 261 | − "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], | |
| 262 | − | |
| 263 | − "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], | |
| 264 | − | |
| 265 | − "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], | |
| 266 | − | |
| 267 | − "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], | |
| 268 | − | |
| 269 | − "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], | |
| 270 | − | |
| 271 | − "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], | |
| 272 | − | |
| 273 | − "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], | |
| 274 | − | |
| 275 | − "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], | |
| 276 | − | |
| 277 | − "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], | |
| 278 | − | |
| 279 | − "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], | |
| 280 | − | |
| 281 | − "@google/genai": ["@google/genai@1.40.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-fhIww8smT0QYRX78qWOiz/nIQhHMF5wXOrlXvj33HBrz3vKDBb+wibLcEmTA+L9dmPD4KmfNr7UF3LDQVTXNjA=="], | |
| 282 | − | |
| 283 | − "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], | |
| 284 | − | |
| 285 | − "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], | |
| 286 | − | |
| 287 | − "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], | |
| 288 | − | |
| 289 | − "@mariozechner/pi-ai": ["@mariozechner/pi-ai@0.52.6", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.983.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.10.0", "@sinclair/typebox": "^0.34.41", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "chalk": "^5.6.2", "openai": "6.10.0", "partial-json": "^0.1.7", "proxy-agent": "^6.5.0", "undici": "^7.19.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-4oqhoFvYh5GQI8TzxhrXs3tXLOAw+/VvqEQRDJzo0k7Rye0ONWOLcaHAUSfBtOTn15gMUh6m+SjtWXmKVisdBg=="], | |
| 290 | − | |
| 291 | − "@mistralai/mistralai": ["@mistralai/mistralai@1.10.0", "", { "dependencies": { "zod": "^3.20.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg=="], | |
| 292 | − | |
| 293 | − "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.26.0", "", { "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-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="], | |
| 294 | − | |
| 295 | − "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], | |
| 296 | − | |
| 297 | − "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], | |
| 298 | − | |
| 299 | − "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], | |
| 300 | − | |
| 301 | − "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], | |
| 302 | − | |
| 303 | − "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], | |
| 304 | − | |
| 305 | − "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], | |
| 306 | − | |
| 307 | − "@openassistant/bot": ["@openassistant/bot@workspace:apps/bot"], | |
| 308 | − | |
| 309 | − "@openassistant/core": ["@openassistant/core@workspace:packages/core"], | |
| 310 | − | |
| 311 | − "@openassistant/executor": ["@openassistant/executor@workspace:apps/executor"], | |
| 312 | − | |
| 313 | − "@openassistant/reacord": ["@openassistant/reacord@workspace:packages/reacord"], | |
| 314 | − | |
| 315 | − "@openassistant/server": ["@openassistant/server@workspace:apps/server"], | |
| 316 | − | |
| 317 | − "@openassistant/tool-gen": ["@openassistant/tool-gen@workspace:packages/tool-gen"], | |
| 318 | − | |
| 319 | − "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], | |
| 320 | − | |
| 321 | − "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], | |
| 322 | − | |
| 323 | − "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], | |
| 324 | − | |
| 325 | − "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], | |
| 326 | − | |
| 327 | − "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], | |
| 328 | − | |
| 329 | − "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], | |
| 330 | − | |
| 331 | − "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], | |
| 332 | − | |
| 333 | − "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], | |
| 334 | − | |
| 335 | − "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], | |
| 336 | − | |
| 337 | − "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], | |
| 338 | − | |
| 339 | − "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], | |
| 340 | − | |
| 341 | − "@redocly/ajv": ["@redocly/ajv@8.17.3", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-NQsbJbB/GV7JVO88ebFkMndrnuGp/dTm5/2NISeg+JGcLzTfGBJZ01+V5zD8nKBOpi/dLLNFT+Ql6IcUk8ehng=="], | |
| 342 | − | |
| 343 | − "@redocly/config": ["@redocly/config@0.22.2", "", {}, "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ=="], | |
| 344 | − | |
| 345 | − "@redocly/openapi-core": ["@redocly/openapi-core@1.34.6", "", { "dependencies": { "@redocly/ajv": "^8.11.2", "@redocly/config": "^0.22.0", "colorette": "^1.2.0", "https-proxy-agent": "^7.0.5", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", "minimatch": "^5.0.1", "pluralize": "^8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw=="], | |
| 346 | − | |
| 347 | − "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], | |
| 348 | − | |
| 349 | − "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], | |
| 350 | − | |
| 351 | − "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], | |
| 352 | − | |
| 353 | − "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], | |
| 354 | − | |
| 355 | − "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], | |
| 356 | − | |
| 357 | − "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], | |
| 358 | − | |
| 359 | − "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], | |
| 360 | − | |
| 361 | − "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], | |
| 362 | − | |
| 363 | − "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], | |
| 364 | − | |
| 365 | − "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], | |
| 366 | − | |
| 367 | − "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], | |
| 368 | − | |
| 369 | − "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], | |
| 370 | − | |
| 371 | − "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], | |
| 372 | − | |
| 373 | − "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], | |
| 374 | − | |
| 375 | − "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], | |
| 376 | − | |
| 377 | − "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], | |
| 378 | − | |
| 379 | − "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], | |
| 380 | − | |
| 381 | − "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], | |
| 382 | − | |
| 383 | − "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], | |
| 384 | − | |
| 385 | − "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], | |
| 386 | − | |
| 387 | − "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], | |
| 388 | − | |
| 389 | − "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], | |
| 390 | − | |
| 391 | − "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], | |
| 392 | − | |
| 393 | − "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], | |
| 394 | − | |
| 395 | − "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], | |
| 396 | − | |
| 397 | − "@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="], | |
| 398 | − | |
| 399 | − "@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="], | |
| 400 | − | |
| 401 | − "@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="], | |
| 402 | − | |
| 403 | − "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], | |
| 404 | − | |
| 405 | − "@smithy/abort-controller": ["@smithy/abort-controller@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw=="], | |
| 406 | − | |
| 407 | − "@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="], | |
| 408 | − | |
| 409 | − "@smithy/core": ["@smithy/core@3.22.1", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.11", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g=="], | |
| 410 | − | |
| 411 | − "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="], | |
| 412 | − | |
| 413 | − "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="], | |
| 414 | − | |
| 415 | − "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw=="], | |
| 416 | − | |
| 417 | − "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ=="], | |
| 418 | − | |
| 419 | − "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A=="], | |
| 420 | − | |
| 421 | − "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.8", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ=="], | |
| 422 | − | |
| 423 | − "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA=="], | |
| 424 | − | |
| 425 | − "@smithy/hash-node": ["@smithy/hash-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA=="], | |
| 426 | − | |
| 427 | − "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ=="], | |
| 428 | − | |
| 429 | − "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="], | |
| 430 | − | |
| 431 | − "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="], | |
| 432 | − | |
| 433 | − "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.13", "", { "dependencies": { "@smithy/core": "^3.22.1", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w=="], | |
| 434 | − | |
| 435 | − "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.30", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg=="], | |
| 436 | − | |
| 437 | − "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="], | |
| 438 | − | |
| 439 | − "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA=="], | |
| 440 | − | |
| 441 | − "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="], | |
| 442 | − | |
| 443 | − "@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.9", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w=="], | |
| 444 | − | |
| 445 | − "@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="], | |
| 446 | − | |
| 447 | − "@smithy/protocol-http": ["@smithy/protocol-http@5.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ=="], | |
| 448 | − | |
| 449 | − "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw=="], | |
| 450 | − | |
| 451 | − "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA=="], | |
| 452 | − | |
| 453 | − "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0" } }, "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ=="], | |
| 454 | − | |
| 455 | − "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.3", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg=="], | |
| 456 | − | |
| 457 | − "@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="], | |
| 458 | − | |
| 459 | − "@smithy/smithy-client": ["@smithy/smithy-client@4.11.2", "", { "dependencies": { "@smithy/core": "^3.22.1", "@smithy/middleware-endpoint": "^4.4.13", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.11", "tslib": "^2.6.2" } }, "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A=="], | |
| 460 | − | |
| 461 | − "@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="], | |
| 462 | − | |
| 463 | − "@smithy/url-parser": ["@smithy/url-parser@4.2.8", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA=="], | |
| 464 | − | |
| 465 | − "@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="], | |
| 466 | − | |
| 467 | − "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="], | |
| 468 | − | |
| 469 | − "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="], | |
| 470 | − | |
| 471 | − "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="], | |
| 472 | − | |
| 473 | − "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], | |
| 474 | − | |
| 475 | − "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.29", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q=="], | |
| 476 | − | |
| 477 | − "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.32", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.2", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q=="], | |
| 478 | − | |
| 479 | − "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="], | |
| 480 | − | |
| 481 | − "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="], | |
| 482 | − | |
| 483 | − "@smithy/util-middleware": ["@smithy/util-middleware@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A=="], | |
| 484 | − | |
| 485 | − "@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="], | |
| 486 | − | |
| 487 | − "@smithy/util-stream": ["@smithy/util-stream@4.5.11", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.9", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA=="], | |
| 488 | − | |
| 489 | − "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="], | |
| 490 | − | |
| 491 | − "@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="], | |
| 492 | − | |
| 493 | − "@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="], | |
| 494 | − | |
| 495 | − "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], | |
| 496 | − | |
| 497 | − "@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="], | |
| 498 | − | |
| 499 | − "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], | |
| 500 | − | |
| 501 | − "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], | |
| 502 | − | |
| 503 | − "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="], | |
| 504 | − | |
| 505 | − "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], | |
| 506 | − | |
| 507 | − "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], | |
| 508 | − | |
| 509 | − "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], | |
| 510 | − | |
| 511 | − "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], | |
| 512 | − | |
| 513 | − "@types/node": ["@types/node@25.2.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg=="], | |
| 514 | − | |
| 515 | − "@types/react": ["@types/react@19.2.13", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ=="], | |
| 516 | − | |
| 517 | − "@types/react-reconciler": ["@types/react-reconciler@0.31.0", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-wa1KtV3SHKnWeLHfZFg/7nrPSe/Ejdn+qbanNX4V6i9I7C3ECKPfBnC1KM3ef58KFWEohLmTvoTH5XuEIKGX2g=="], | |
| 518 | − | |
| 519 | − "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], | |
| 520 | − | |
| 521 | − "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260205.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260205.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260205.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260205.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260205.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260205.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260205.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260205.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-eSgzYCbdCXP/E0XL53yIMZNLoY3z1xMOgGyjstVLgUCMLv1yNrFvkhKhHFjM84OTY/LxqRb6ACtvjFO/oSZzvQ=="], | |
| 522 | − | |
| 523 | − "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260205.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ULATKP9a26qh8vcmP4qPz8UugGKIwhQPKi3NhvlbTPwhl3fMd3GJd9/B9LJSHw7lIuELQGZxhSlDq9l0FMb/FQ=="], | |
| 524 | − | |
| 525 | − "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260205.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-moaKDZHK2dbgcHCnxcwhH8kYRgY69wzPcH5hCNaSrmpbC+Garr78oLtyXot2EDotRDT9foeYsWKdmD6Hx/ypxg=="], | |
| 526 | − | |
| 527 | − "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260205.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3qfjUQlYCkwQmbpIeXMw75bLXkCI3Uo88Ug1n9p4j6KFaek5TjnHOTmlO6V3pkyH9pEXQEVXTn0pXzQytxqEqw=="], | |
| 528 | − | |
| 529 | − "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260205.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Wfp2bPmrTLb+dpp2bHDjMqMKGjQ9dp5KSw0jV4LSlbgcVvRSEWqs2ByVVj61Z4qiHgwlVyoPTewdan2CWnoBgQ=="], | |
| 530 | − | |
| 531 | − "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260205.1", "", { "os": "linux", "cpu": "x64" }, "sha512-p59oY35gvvmdy/iZYxdbFAUXusb7joX2i1Nwl15i4TOn52NcIcW3wb9U/uBrIXKev5VEdlH6BS6VA6dM57zD6w=="], | |
| 532 | − | |
| 533 | − "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260205.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-+NQTlmvtZEXwIlw8j+tvAAn1gLDqyWJEjnA5vmT9MoJuEBrxvuS8azn/q26MOp/w8bWfxe3haVyB+L4VurCF6w=="], | |
| 534 | − | |
| 535 | − "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260205.1", "", { "os": "win32", "cpu": "x64" }, "sha512-kRa4kaiORAWQx9sHylewUhKsNxz3dRBy6AM/U02UebJRlt6c+JnSjIxAFP+iNQaRpoYNs8UdKKGPrHc7Q0oYow=="], | |
| 536 | − | |
| 537 | − "@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=="], | |
| 538 | − | |
| 539 | − "@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=="], | |
| 540 | − | |
| 541 | − "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], | |
| 542 | − | |
| 543 | − "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="], | |
| 544 | − | |
| 545 | − "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="], | |
| 546 | − | |
| 547 | − "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], | |
| 548 | − | |
| 549 | − "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], | |
| 550 | − | |
| 551 | − "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="], | |
| 552 | − | |
| 553 | − "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], | |
| 554 | − | |
| 555 | − "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], | |
| 556 | − | |
| 557 | − "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], | |
| 558 | − | |
| 559 | − "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], | |
| 560 | − | |
| 561 | − "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], | |
| 562 | − | |
| 563 | − "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], | |
| 564 | − | |
| 565 | − "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], | |
| 566 | − | |
| 567 | − "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], | |
| 568 | − | |
| 569 | − "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], | |
| 570 | − | |
| 571 | − "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], | |
| 572 | − | |
| 573 | − "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], | |
| 574 | − | |
| 575 | − "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], | |
| 576 | − | |
| 577 | − "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], | |
| 578 | − | |
| 579 | − "basic-ftp": ["basic-ftp@5.1.0", "", {}, "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw=="], | |
| 580 | − | |
| 581 | − "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], | |
| 582 | − | |
| 583 | − "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=="], | |
| 584 | − | |
| 585 | − "bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="], | |
| 586 | − | |
| 587 | − "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], | |
| 588 | − | |
| 589 | − "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], | |
| 590 | − | |
| 591 | − "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="], | |
| 592 | − | |
| 593 | − "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], | |
| 594 | − | |
| 595 | − "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], | |
| 596 | − | |
| 597 | − "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], | |
| 598 | − | |
| 599 | − "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], | |
| 600 | − | |
| 601 | − "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="], | |
| 602 | − | |
| 603 | − "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=="], | |
| 604 | − | |
| 605 | − "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], | |
| 606 | − | |
| 607 | − "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], | |
| 608 | − | |
| 609 | − "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], | |
| 610 | − | |
| 611 | − "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], | |
| 612 | − | |
| 613 | − "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], | |
| 614 | − | |
| 615 | − "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], | |
| 616 | − | |
| 617 | − "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], | |
| 618 | − | |
| 619 | − "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], | |
| 620 | − | |
| 621 | − "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], | |
| 622 | − | |
| 623 | − "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], | |
| 624 | − | |
| 625 | − "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], | |
| 626 | − | |
| 627 | − "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], | |
| 628 | − | |
| 629 | − "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], | |
| 630 | − | |
| 631 | − "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], | |
| 632 | − | |
| 633 | − "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], | |
| 634 | − | |
| 635 | − "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], | |
| 636 | − | |
| 637 | − "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], | |
| 638 | − | |
| 639 | − "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], | |
| 640 | − | |
| 641 | − "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], | |
| 642 | − | |
| 643 | − "discord-api-types": ["discord-api-types@0.38.38", "", {}, "sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q=="], | |
| 644 | − | |
| 645 | − "discord.js": ["discord.js@14.25.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="], | |
| 646 | − | |
| 647 | − "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], | |
| 648 | − | |
| 649 | − "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], | |
| 650 | − | |
| 651 | − "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], | |
| 652 | − | |
| 653 | − "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], | |
| 654 | − | |
| 655 | − "effect": ["effect@3.19.16", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-7+XC3vGrbAhCHd8LTFHvnZjRpZKZ8YHRZqJTkpNoxcJ2mCyNs2SwI+6VkV/ij8Y3YW7wfBN4EbU06/F5+m/wkQ=="], | |
| 656 | − | |
| 657 | − "elysia": ["elysia@1.4.22", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="], | |
| 658 | − | |
| 659 | − "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], | |
| 660 | − | |
| 661 | − "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], | |
| 662 | − | |
| 663 | − "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], | |
| 664 | − | |
| 665 | − "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], | |
| 666 | − | |
| 667 | − "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], | |
| 668 | − | |
| 669 | − "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], | |
| 670 | − | |
| 671 | − "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], | |
| 672 | − | |
| 673 | − "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], | |
| 674 | − | |
| 675 | − "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], | |
| 676 | − | |
| 677 | − "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], | |
| 678 | − | |
| 679 | − "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], | |
| 680 | − | |
| 681 | − "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], | |
| 682 | − | |
| 683 | − "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], | |
| 684 | − | |
| 685 | − "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], | |
| 686 | − | |
| 687 | − "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], | |
| 688 | − | |
| 689 | − "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], | |
| 690 | − | |
| 691 | − "exact-mirror": ["exact-mirror@0.2.6", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-7s059UIx9/tnOKSySzUk5cPGkoILhTE4p6ncf6uIPaQ+9aRBQzQjc9+q85l51+oZ+P6aBxh084pD0CzBQPcFUA=="], | |
| 692 | − | |
| 693 | − "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], | |
| 694 | − | |
| 695 | − "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], | |
| 696 | − | |
| 697 | − "express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="], | |
| 698 | − | |
| 699 | − "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], | |
| 700 | − | |
| 701 | − "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], | |
| 702 | − | |
| 703 | − "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], | |
| 704 | − | |
| 705 | − "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], | |
| 706 | − | |
| 707 | − "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], | |
| 708 | − | |
| 709 | − "fast-xml-parser": ["fast-xml-parser@5.3.4", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA=="], | |
| 710 | − | |
| 711 | − "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], | |
| 712 | − | |
| 713 | − "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], | |
| 714 | − | |
| 715 | − "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], | |
| 716 | − | |
| 717 | − "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], | |
| 718 | − | |
| 719 | − "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], | |
| 720 | − | |
| 721 | − "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], | |
| 722 | − | |
| 723 | − "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], | |
| 724 | − | |
| 725 | − "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], | |
| 726 | − | |
| 727 | − "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], | |
| 728 | − | |
| 729 | − "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], | |
| 730 | − | |
| 731 | − "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], | |
| 732 | − | |
| 733 | − "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], | |
| 734 | − | |
| 735 | − "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], | |
| 736 | − | |
| 737 | − "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=="], | |
| 738 | − | |
| 739 | − "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], | |
| 740 | − | |
| 741 | − "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], | |
| 742 | − | |
| 743 | − "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], | |
| 744 | − | |
| 745 | − "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], | |
| 746 | − | |
| 747 | − "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], | |
| 748 | − | |
| 749 | − "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], | |
| 750 | − | |
| 751 | − "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], | |
| 752 | − | |
| 753 | − "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], | |
| 754 | − | |
| 755 | − "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], | |
| 756 | − | |
| 757 | − "hono": ["hono@4.11.8", "", {}, "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q=="], | |
| 758 | − | |
| 759 | − "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=="], | |
| 760 | − | |
| 761 | − "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], | |
| 762 | − | |
| 763 | − "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], | |
| 764 | − | |
| 765 | − "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], | |
| 766 | − | |
| 767 | − "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], | |
| 768 | − | |
| 769 | − "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], | |
| 770 | − | |
| 771 | − "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], | |
| 772 | − | |
| 773 | − "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], | |
| 774 | − | |
| 775 | − "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], | |
| 776 | − | |
| 777 | − "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], | |
| 778 | − | |
| 779 | − "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], | |
| 780 | − | |
| 781 | − "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], | |
| 782 | − | |
| 783 | − "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], | |
| 784 | − | |
| 785 | − "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], | |
| 786 | − | |
| 787 | − "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], | |
| 788 | − | |
| 789 | − "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], | |
| 790 | − | |
| 791 | − "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], | |
| 792 | − | |
| 793 | − "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], | |
| 794 | − | |
| 795 | − "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], | |
| 796 | − | |
| 797 | − "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], | |
| 798 | − | |
| 799 | − "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], | |
| 800 | − | |
| 801 | − "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=="], | |
| 802 | − | |
| 803 | − "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=="], | |
| 804 | − | |
| 805 | − "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], | |
| 806 | − | |
| 807 | − "lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="], | |
| 808 | − | |
| 809 | − "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], | |
| 810 | − | |
| 811 | − "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], | |
| 812 | − | |
| 813 | − "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], | |
| 814 | − | |
| 815 | − "magic-bytes.js": ["magic-bytes.js@1.13.0", "", {}, "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg=="], | |
| 816 | − | |
| 817 | − "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], | |
| 818 | − | |
| 819 | − "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], | |
| 820 | − | |
| 821 | − "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], | |
| 822 | − | |
| 823 | − "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], | |
| 824 | − | |
| 825 | − "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], | |
| 826 | − | |
| 827 | − "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], | |
| 828 | − | |
| 829 | − "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], | |
| 830 | − | |
| 831 | − "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], | |
| 832 | − | |
| 833 | − "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], | |
| 834 | − | |
| 835 | − "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], | |
| 836 | − | |
| 837 | − "msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="], | |
| 838 | − | |
| 839 | − "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], | |
| 840 | − | |
| 841 | − "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], | |
| 842 | − | |
| 843 | − "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], | |
| 844 | − | |
| 845 | − "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], | |
| 846 | − | |
| 847 | − "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], | |
| 848 | − | |
| 849 | − "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], | |
| 850 | − | |
| 851 | − "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], | |
| 852 | − | |
| 853 | − "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=="], | |
| 854 | − | |
| 855 | − "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], | |
| 856 | − | |
| 857 | − "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], | |
| 858 | − | |
| 859 | − "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], | |
| 860 | − | |
| 861 | − "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], | |
| 862 | − | |
| 863 | − "openai": ["openai@6.10.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-ITxOGo7rO3XRMiKA5l7tQ43iNNu+iXGFAcf2t+aWVzzqRaS0i7m1K2BhxNdaveB+5eENhO0VY1FkiZzhBk4v3A=="], | |
| 864 | − | |
| 865 | − "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], | |
| 866 | − | |
| 867 | − "openapi-typescript": ["openapi-typescript@7.10.1", "", { "dependencies": { "@redocly/openapi-core": "^1.34.5", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw=="], | |
| 868 | − | |
| 869 | − "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], | |
| 870 | − | |
| 871 | − "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], | |
| 872 | − | |
| 873 | − "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], | |
| 874 | − | |
| 875 | − "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], | |
| 876 | − | |
| 877 | − "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], | |
| 878 | − | |
| 879 | − "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], | |
| 880 | − | |
| 881 | − "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], | |
| 882 | − | |
| 883 | − "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], | |
| 884 | − | |
| 885 | − "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], | |
| 886 | − | |
| 887 | − "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], | |
| 888 | − | |
| 889 | − "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], | |
| 890 | − | |
| 891 | − "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], | |
| 892 | − | |
| 893 | − "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], | |
| 894 | − | |
| 895 | − "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], | |
| 896 | − | |
| 897 | − "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], | |
| 898 | − | |
| 899 | − "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], | |
| 900 | − | |
| 901 | − "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], | |
| 902 | − | |
| 903 | − "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=="], | |
| 904 | − | |
| 905 | − "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], | |
| 906 | − | |
| 907 | − "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], | |
| 908 | − | |
| 909 | − "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], | |
| 910 | − | |
| 911 | − "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], | |
| 912 | − | |
| 913 | − "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], | |
| 914 | − | |
| 915 | − "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=="], | |
| 916 | − | |
| 917 | − "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], | |
| 918 | − | |
| 919 | − "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="], | |
| 920 | − | |
| 921 | − "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], | |
| 922 | − | |
| 923 | − "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], | |
| 924 | − | |
| 925 | − "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], | |
| 926 | − | |
| 927 | − "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], | |
| 928 | − | |
| 929 | − "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], | |
| 930 | − | |
| 931 | − "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], | |
| 932 | − | |
| 933 | − "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], | |
| 934 | − | |
| 935 | − "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=="], | |
| 936 | − | |
| 937 | − "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=="], | |
| 938 | − | |
| 939 | − "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], | |
| 940 | − | |
| 941 | − "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], | |
| 942 | − | |
| 943 | − "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], | |
| 944 | − | |
| 945 | − "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=="], | |
| 946 | − | |
| 947 | − "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], | |
| 948 | − | |
| 949 | − "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], | |
| 950 | − | |
| 951 | − "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], | |
| 952 | − | |
| 953 | − "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], | |
| 954 | − | |
| 955 | − "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], | |
| 956 | − | |
| 957 | − "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], | |
| 958 | − | |
| 959 | − "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], | |
| 960 | − | |
| 961 | − "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], | |
| 962 | − | |
| 963 | − "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], | |
| 964 | − | |
| 965 | − "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], | |
| 966 | − | |
| 967 | − "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], | |
| 968 | − | |
| 969 | − "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], | |
| 970 | − | |
| 971 | − "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], | |
| 972 | − | |
| 973 | − "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], | |
| 974 | − | |
| 975 | − "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], | |
| 976 | − | |
| 977 | − "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], | |
| 978 | − | |
| 979 | − "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], | |
| 980 | − | |
| 981 | − "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], | |
| 982 | − | |
| 983 | − "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], | |
| 984 | − | |
| 985 | − "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], | |
| 986 | − | |
| 987 | − "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], | |
| 988 | − | |
| 989 | − "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], | |
| 990 | − | |
| 991 | − "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], | |
| 992 | − | |
| 993 | − "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], | |
| 994 | − | |
| 995 | − "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], | |
| 996 | − | |
| 997 | − "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], | |
| 998 | − | |
| 999 | − "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="], | |
| 1000 | − | |
| 1001 | − "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], | |
| 1002 | − | |
| 1003 | − "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], | |
| 1004 | − | |
| 1005 | − "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], | |
| 1006 | − | |
| 1007 | − "ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="], | |
| 1008 | − | |
| 1009 | − "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], | |
| 1010 | − | |
| 1011 | − "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], | |
| 1012 | − | |
| 1013 | − "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=="], | |
| 1014 | − | |
| 1015 | − "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], | |
| 1016 | − | |
| 1017 | − "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], | |
| 1018 | − | |
| 1019 | − "undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="], | |
| 1020 | − | |
| 1021 | − "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], | |
| 1022 | − | |
| 1023 | − "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], | |
| 1024 | − | |
| 1025 | − "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], | |
| 1026 | − | |
| 1027 | − "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], | |
| 1028 | − | |
| 1029 | − "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=="], | |
| 1030 | − | |
| 1031 | − "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=="], | |
| 1032 | − | |
| 1033 | − "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="], | |
| 1034 | − | |
| 1035 | − "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], | |
| 1036 | − | |
| 1037 | − "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], | |
| 1038 | − | |
| 1039 | − "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], | |
| 1040 | − | |
| 1041 | − "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], | |
| 1042 | − | |
| 1043 | − "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], | |
| 1044 | − | |
| 1045 | − "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], | |
| 1046 | − | |
| 1047 | − "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], | |
| 1048 | − | |
| 1049 | − "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], | |
| 1050 | − | |
| 1051 | − "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], | |
| 1052 | − | |
| 1053 | − "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], | |
| 1054 | − | |
| 1055 | − "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], | |
| 1056 | − | |
| 1057 | − "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], | |
| 1058 | − | |
| 1059 | − "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], | |
| 1060 | − | |
| 1061 | − "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], | |
| 1062 | − | |
| 1063 | − "@aws-sdk/client-sso/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="], | |
| 1064 | − | |
| 1065 | − "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.982.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ=="], | |
| 1066 | − | |
| 1067 | − "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.982.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ=="], | |
| 1068 | − | |
| 1069 | − "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.982.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.6", "@aws-sdk/nested-clients": "3.982.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v3M0KYp2TVHYHNBT7jHD9lLTWAdS9CaWJ2jboRKt0WAB65bA7iUEpR+k4VqKYtpQN4+8kKSc4w+K6kUNZkHKQw=="], | |
| 1070 | − | |
| 1071 | − "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.982.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ=="], | |
| 1072 | − | |
| 1073 | − "@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="], | |
| 1074 | − | |
| 1075 | − "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], | |
| 1076 | − | |
| 1077 | − "@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], | |
| 1078 | − | |
| 1079 | − "@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], | |
| 1080 | − | |
| 1081 | − "@mariozechner/pi-ai/undici": ["undici@7.20.0", "", {}, "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ=="], | |
| 1082 | − | |
| 1083 | − "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], | |
| 1084 | − | |
| 1085 | − "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], | |
| 1086 | − | |
| 1087 | − "node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], | |
| 1088 | − | |
| 1089 | − "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], | |
| 1090 | − | |
| 1091 | − "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], | |
| 1092 | − | |
| 1093 | − "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], | |
| 1094 | − | |
| 1095 | − "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], | |
| 1096 | − | |
| 1097 | − "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], | |
| 1098 | − | |
| 1099 | − "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], | |
| 1100 | − | |
| 1101 | − "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], | |
| 1102 | − | |
| 1103 | − "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], | |
| 1104 | − | |
| 1105 | − "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], | |
| 1106 | − | |
| 1107 | − "@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="], | |
| 1108 | − | |
| 1109 | − "@aws-sdk/credential-provider-login/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="], | |
| 1110 | − | |
| 1111 | − "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.982.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.6", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.982.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.4", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.22.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.12", "@smithy/middleware-retry": "^4.4.29", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.28", "@smithy/util-defaults-mode-node": "^4.2.31", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-VVkaH27digrJfdVrT64rjkllvOp4oRiZuuJvrylLXAKl18ujToJR7AqpDldL/LS63RVne3QWIpkygIymxFtliQ=="], | |
| 1112 | − | |
| 1113 | − "@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="], | |
| 1114 | − | |
| 1115 | − "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], | |
| 1116 | − | |
| 1117 | − "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], | |
| 1118 | − | |
| 1119 | − "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], | |
| 1120 | − | |
| 1121 | − "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], | |
| 1122 | − | |
| 1123 | − "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], | |
| 1124 | − | |
| 1125 | − "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.982.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-M27u8FJP7O0Of9hMWX5dipp//8iglmV9jr7R8SR8RveU+Z50/8TqH68Tu6wUWBGMfXjzbVwn1INIAO5lZrlxXQ=="], | |
| 1126 | − } | |
| 1127 | −} | |
| @@ -1 +0,0 @@ | ||
| 1 | −console.log("Hello via Bun!"); | |
| @@ -1,52 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "name": "openassistant", | |
| 3 | − "private": true, | |
| 4 | − "type": "module", | |
| 5 | − "workspaces": [ | |
| 6 | − "apps/*", | |
| 7 | − "packages/*" | |
| 8 | − ], | |
| 9 | − "scripts": { | |
| 10 | − "with-env": "bun run --env-file=.env --", | |
| 11 | − "with-env:prod": "bun run --env-file=.env.production --", | |
| 12 | − "prepare": "effect-language-service patch", | |
| 13 | − "typecheck": "bun run --filter '@openassistant/core' typecheck && bun run --filter '@openassistant/reacord' typecheck && bun run --filter '@openassistant/server' typecheck && bun run --filter '@openassistant/executor' typecheck", | |
| 14 | − "typecheck:tsc": "tsc --build", | |
| 15 | − "build": "bun run typecheck", | |
| 16 | − "test": "bun test packages/ apps/", | |
| 17 | − "test:watch": "bun test --watch packages/ apps/", | |
| 18 | − "dev": "bun run dev:server & bun run dev:executor & bun run dev:bot & wait", | |
| 19 | − "dev:server": "bun run --filter '@openassistant/server' dev", | |
| 20 | − "dev:executor": "bun run --filter '@openassistant/executor' dev", | |
| 21 | − "dev:bot": "bun run --filter '@openassistant/bot' dev" | |
| 22 | − }, | |
| 23 | − "devDependencies": { | |
| 24 | − "@effect/language-service": "latest", | |
| 25 | − "@effect/vitest": "^0.27.0", | |
| 26 | − "@types/bun": "latest", | |
| 27 | − "@typescript/native-preview": "7.0.0-dev.20260205.1", | |
| 28 | − "typescript": "latest", | |
| 29 | − "vitest": "^3.2.0" | |
| 30 | − }, | |
| 31 | − "overrides": { | |
| 32 | − "@ai-sdk/anthropic": "^2.0.0", | |
| 33 | − "@effect/platform": "latest", | |
| 34 | − "@effect/platform-bun": "latest", | |
| 35 | − "@effect/rpc": "latest", | |
| 36 | − "@effect-atom/atom": "^0.4.11", | |
| 37 | − "@effect-atom/atom-react": "^0.4.4", | |
| 38 | − "@types/react": "^19.0.0", | |
| 39 | − "@types/react-reconciler": "^0.31.0", | |
| 40 | − "ai": "^6.0.73", | |
| 41 | − "discord.js": "^14.17.3", | |
| 42 | − "effect": "latest", | |
| 43 | − "react": "^19.0.0", | |
| 44 | − "react-reconciler": "^0.31.0", | |
| 45 | − "zod": "^4.3.6" | |
| 46 | − }, | |
| 47 | − "dependencies": { | |
| 48 | − "@apidevtools/swagger-parser": "^12.1.0", | |
| 49 | − "@modelcontextprotocol/sdk": "^1.26.0", | |
| 50 | − "yaml": "^2.8.2" | |
| 51 | − } | |
| 52 | −} | |
| @@ -1,25 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "name": "@openassistant/core", | |
| 3 | − "private": true, | |
| 4 | − "type": "module", | |
| 5 | − "exports": { | |
| 6 | − ".": "./src/index.ts", | |
| 7 | − "./tools": "./src/tools.ts", | |
| 8 | − "./runner": "./src/runner.ts", | |
| 9 | − "./remote-runner": "./src/remote-runner.ts", | |
| 10 | − "./typechecker": "./src/typechecker.ts", | |
| 11 | − "./agent": "./src/agent.ts", | |
| 12 | − "./events": "./src/events.ts", | |
| 13 | − "./discovery": "./src/discovery.ts" | |
| 14 | − }, | |
| 15 | − "scripts": { | |
| 16 | − "test": "bun test", | |
| 17 | − "typecheck": "tsgo --noEmit --incremental false -p tsconfig.json" | |
| 18 | − }, | |
| 19 | − "dependencies": { | |
| 20 | − "@mariozechner/pi-ai": "^0.52.6", | |
| 21 | − "@sinclair/typebox": "^0.34.48", | |
| 22 | − "effect": "latest", | |
| 23 | − "zod": "^4.3.6" | |
| 24 | − } | |
| 25 | −} | |
| @@ -1,218 +0,0 @@ | ||
| 1 | −import { describe, test, expect } from "bun:test"; | |
| 2 | −import { z } from "zod"; | |
| 3 | −import { defineTool, type ToolTree } from "./tools.js"; | |
| 4 | −import { createAgent, type LanguageModel, type Message, type GenerateResult } from "./agent.js"; | |
| 5 | −import type { TaskEvent } from "./events.js"; | |
| 6 | − | |
| 7 | −// --------------------------------------------------------------------------- | |
| 8 | −// Test helpers | |
| 9 | −// --------------------------------------------------------------------------- | |
| 10 | − | |
| 11 | −function mathTools(): ToolTree { | |
| 12 | − return { | |
| 13 | − math: { | |
| 14 | − add: defineTool({ | |
| 15 | − description: "Add two numbers", | |
| 16 | − approval: "auto", | |
| 17 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 18 | − returns: z.number(), | |
| 19 | − run: async (input) => input.a + input.b, | |
| 20 | − }), | |
| 21 | − multiply: defineTool({ | |
| 22 | − description: "Multiply two numbers", | |
| 23 | − approval: "auto", | |
| 24 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 25 | − returns: z.number(), | |
| 26 | − run: async (input) => input.a * input.b, | |
| 27 | − }), | |
| 28 | − }, | |
| 29 | − }; | |
| 30 | −} | |
| 31 | − | |
| 32 | −/** | |
| 33 | − * Create a fake model that returns pre-scripted responses. | |
| 34 | − * Each call to generate() returns the next response in the sequence. | |
| 35 | − */ | |
| 36 | −function fakeModel(responses: GenerateResult[]): LanguageModel { | |
| 37 | − let callIndex = 0; | |
| 38 | − return { | |
| 39 | − async generate(_messages: Message[]): Promise<GenerateResult> { | |
| 40 | − const response = responses[callIndex]; | |
| 41 | − if (!response) { | |
| 42 | − return { text: "No more scripted responses." }; | |
| 43 | − } | |
| 44 | − callIndex++; | |
| 45 | − return response; | |
| 46 | − }, | |
| 47 | − }; | |
| 48 | −} | |
| 49 | − | |
| 50 | −// --------------------------------------------------------------------------- | |
| 51 | −// Tests | |
| 52 | −// --------------------------------------------------------------------------- | |
| 53 | − | |
| 54 | −describe("agent — basic flow", () => { | |
| 55 | − test("model returns text without tool calls", async () => { | |
| 56 | − const model = fakeModel([{ text: "Hello! How can I help?" }]); | |
| 57 | − | |
| 58 | − const agent = createAgent({ | |
| 59 | − tools: mathTools(), | |
| 60 | − model, | |
| 61 | − requestApproval: async () => "approved", | |
| 62 | − }); | |
| 63 | − | |
| 64 | − const result = await agent.run("Hi there"); | |
| 65 | − expect(result.text).toBe("Hello! How can I help?"); | |
| 66 | − expect(result.runs).toHaveLength(0); | |
| 67 | − expect(result.allReceipts).toHaveLength(0); | |
| 68 | − }); | |
| 69 | − | |
| 70 | − test("model calls run_code, then returns text", async () => { | |
| 71 | − const model = fakeModel([ | |
| 72 | − // First call: model wants to run code | |
| 73 | − { | |
| 74 | − toolCalls: [ | |
| 75 | − { | |
| 76 | − id: "tc_1", | |
| 77 | − name: "run_code", | |
| 78 | − args: { code: "return await tools.math.add({ a: 3, b: 4 })" }, | |
| 79 | − }, | |
| 80 | − ], | |
| 81 | − }, | |
| 82 | − // Second call: model sees the result and responds with text | |
| 83 | − { text: "The result of 3 + 4 is 7." }, | |
| 84 | − ]); | |
| 85 | − | |
| 86 | − const agent = createAgent({ | |
| 87 | − tools: mathTools(), | |
| 88 | − model, | |
| 89 | − requestApproval: async () => "approved", | |
| 90 | − }); | |
| 91 | − | |
| 92 | − const result = await agent.run("What is 3 + 4?"); | |
| 93 | − expect(result.text).toBe("The result of 3 + 4 is 7."); | |
| 94 | − expect(result.runs).toHaveLength(1); | |
| 95 | − expect(result.runs[0]!.result.ok).toBe(true); | |
| 96 | − expect(result.runs[0]!.result.value).toBe(7); | |
| 97 | − expect(result.allReceipts).toHaveLength(1); | |
| 98 | − }); | |
| 99 | − | |
| 100 | − test("model chains multiple run_code calls", async () => { | |
| 101 | − const model = fakeModel([ | |
| 102 | − { | |
| 103 | − toolCalls: [ | |
| 104 | − { | |
| 105 | − id: "tc_1", | |
| 106 | − name: "run_code", | |
| 107 | − args: { code: "return await tools.math.add({ a: 1, b: 2 })" }, | |
| 108 | − }, | |
| 109 | − ], | |
| 110 | − }, | |
| 111 | − { | |
| 112 | − toolCalls: [ | |
| 113 | − { | |
| 114 | − id: "tc_2", | |
| 115 | − name: "run_code", | |
| 116 | − args: { code: "return await tools.math.multiply({ a: 3, b: 4 })" }, | |
| 117 | − }, | |
| 118 | − ], | |
| 119 | − }, | |
| 120 | − { text: "1 + 2 = 3, then 3 * 4 = 12." }, | |
| 121 | − ]); | |
| 122 | − | |
| 123 | − const agent = createAgent({ | |
| 124 | − tools: mathTools(), | |
| 125 | − model, | |
| 126 | − requestApproval: async () => "approved", | |
| 127 | − }); | |
| 128 | − | |
| 129 | − const result = await agent.run("Add 1+2 then multiply 3*4"); | |
| 130 | − expect(result.text).toBe("1 + 2 = 3, then 3 * 4 = 12."); | |
| 131 | − expect(result.runs).toHaveLength(2); | |
| 132 | − expect(result.allReceipts).toHaveLength(2); | |
| 133 | − }); | |
| 134 | − | |
| 135 | − test("emits events throughout execution", async () => { | |
| 136 | − const model = fakeModel([ | |
| 137 | − { | |
| 138 | − toolCalls: [ | |
| 139 | − { | |
| 140 | − id: "tc_1", | |
| 141 | − name: "run_code", | |
| 142 | − args: { code: "return await tools.math.add({ a: 1, b: 2 })" }, | |
| 143 | − }, | |
| 144 | − ], | |
| 145 | − }, | |
| 146 | − { text: "Done." }, | |
| 147 | − ]); | |
| 148 | − | |
| 149 | − const events: TaskEvent[] = []; | |
| 150 | − | |
| 151 | − const agent = createAgent({ | |
| 152 | − tools: mathTools(), | |
| 153 | − model, | |
| 154 | − requestApproval: async () => "approved", | |
| 155 | − onEvent: (e) => events.push(e), | |
| 156 | − }); | |
| 157 | − | |
| 158 | − await agent.run("Add 1+2"); | |
| 159 | − | |
| 160 | − const eventTypes = events.map((e) => e.type); | |
| 161 | − expect(eventTypes).toContain("status"); | |
| 162 | − expect(eventTypes).toContain("code_generated"); | |
| 163 | − expect(eventTypes).toContain("tool_result"); | |
| 164 | − expect(eventTypes).toContain("agent_message"); | |
| 165 | − expect(eventTypes).toContain("completed"); | |
| 166 | − }); | |
| 167 | − | |
| 168 | − test("handles code that fails in sandbox", async () => { | |
| 169 | − const model = fakeModel([ | |
| 170 | − { | |
| 171 | − toolCalls: [ | |
| 172 | − { | |
| 173 | − id: "tc_1", | |
| 174 | − name: "run_code", | |
| 175 | − // This passes typecheck but fails at runtime (undefined access) | |
| 176 | − args: { code: "const x: any = undefined;\nreturn x.foo.bar;" }, | |
| 177 | − }, | |
| 178 | − ], | |
| 179 | − }, | |
| 180 | − { text: "The code failed." }, | |
| 181 | − ]); | |
| 182 | − | |
| 183 | − const agent = createAgent({ | |
| 184 | − tools: mathTools(), | |
| 185 | − model, | |
| 186 | − requestApproval: async () => "approved", | |
| 187 | − }); | |
| 188 | − | |
| 189 | − const result = await agent.run("Do something that fails"); | |
| 190 | − expect(result.text).toBe("The code failed."); | |
| 191 | − expect(result.runs).toHaveLength(1); | |
| 192 | − expect(result.runs[0]!.result.ok).toBe(false); | |
| 193 | − }); | |
| 194 | − | |
| 195 | − test("respects maxCodeRuns limit", async () => { | |
| 196 | − // Model always wants to run more code | |
| 197 | − const infiniteModel: LanguageModel = { | |
| 198 | − async generate(): Promise<GenerateResult> { | |
| 199 | − return { | |
| 200 | − toolCalls: [ | |
| 201 | − { id: `tc_${Date.now()}`, name: "run_code", args: { code: "return 1" } }, | |
| 202 | − ], | |
| 203 | − }; | |
| 204 | − }, | |
| 205 | − }; | |
| 206 | − | |
| 207 | − const agent = createAgent({ | |
| 208 | − tools: mathTools(), | |
| 209 | − model: infiniteModel, | |
| 210 | − requestApproval: async () => "approved", | |
| 211 | − maxCodeRuns: 3, | |
| 212 | − }); | |
| 213 | − | |
| 214 | − const result = await agent.run("Keep running"); | |
| 215 | − expect(result.runs).toHaveLength(3); | |
| 216 | − expect(result.text).toContain("maximum"); | |
| 217 | − }); | |
| 218 | −}); | |
| @@ -1,455 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Agent loop — orchestrates Claude + codemode runner. | |
| 3 | − * | |
| 4 | − * 1. Build system prompt from tool descriptions | |
| 5 | − * 2. Call Claude with the prompt and the run_code tool | |
| 6 | − * 3. Claude responds with run_code({ code: "..." }) | |
| 7 | − * 4. Typecheck the code | |
| 8 | − * 5. If typecheck fails: feed error back, retry (up to maxRetries) | |
| 9 | − * 6. Execute in sandbox | |
| 10 | − * 7. Collect receipts, feed back to Claude | |
| 11 | − * 8. Claude may call run_code again or produce final text | |
| 12 | − * | |
| 13 | − * When the tool count exceeds DISCOVERY_THRESHOLD, the agent injects | |
| 14 | − * a `tools.discover()` meta-tool so the LLM can search for relevant | |
| 15 | − * tools by keyword instead of having all tools described in the prompt. | |
| 16 | − */ | |
| 17 | − | |
| 18 | −import type { ToolTree, ApprovalDecision, ApprovalRequest, ToolCallReceipt } from "./tools.js"; | |
| 19 | −import type { RunResult, Runner } from "./runner.js"; | |
| 20 | −import { createRunner } from "./runner.js"; | |
| 21 | −import { generateToolDeclarations, generatePromptGuidance, typecheckCode } from "./typechecker.js"; | |
| 22 | −import { countTools, createDiscoverTool } from "./discovery.js"; | |
| 23 | −import type { TaskEvent } from "./events.js"; | |
| 24 | − | |
| 25 | −// --------------------------------------------------------------------------- | |
| 26 | −// Types | |
| 27 | −// --------------------------------------------------------------------------- | |
| 28 | − | |
| 29 | −/** | |
| 30 | − * A language model interface — thin abstraction over AI SDK or any LLM. | |
| 31 | − * We don't import AI SDK directly to keep core dependency-free. | |
| 32 | − */ | |
| 33 | −export interface LanguageModel { | |
| 34 | − /** | |
| 35 | − * Generate a response given messages. | |
| 36 | − * The model should be configured with the run_code tool. | |
| 37 | − * Returns either text or a tool call. | |
| 38 | − */ | |
| 39 | − generate(messages: Message[]): Promise<GenerateResult>; | |
| 40 | −} | |
| 41 | − | |
| 42 | −export type Message = | |
| 43 | − | { role: "system"; content: string } | |
| 44 | − | { role: "user"; content: string } | |
| 45 | − | { role: "assistant"; content: string } | |
| 46 | − | { role: "assistant"; content: string; toolCalls: ToolCall[] } | |
| 47 | − | { role: "tool"; toolCallId: string; content: string }; | |
| 48 | − | |
| 49 | −export interface ToolCall { | |
| 50 | − id: string; | |
| 51 | − name: string; | |
| 52 | − args: Record<string, unknown>; | |
| 53 | −} | |
| 54 | − | |
| 55 | −export interface GenerateResult { | |
| 56 | − text?: string | undefined; | |
| 57 | − toolCalls?: ToolCall[] | undefined; | |
| 58 | −} | |
| 59 | − | |
| 60 | −export interface AgentOptions { | |
| 61 | − /** The tool tree to expose to the agent. */ | |
| 62 | − readonly tools: ToolTree; | |
| 63 | − /** The language model to use. */ | |
| 64 | − readonly model: LanguageModel; | |
| 65 | − /** Called when a tool needs approval (required when `runner` is not provided). */ | |
| 66 | − readonly requestApproval?: ((request: ApprovalRequest) => Promise<ApprovalDecision>) | undefined; | |
| 67 | − /** Optional custom runner (e.g. remote executor transport). */ | |
| 68 | − readonly runner?: Runner | undefined; | |
| 69 | − /** Called for each task event. */ | |
| 70 | − readonly onEvent?: ((event: TaskEvent) => void) | undefined; | |
| 71 | − /** Maximum typecheck retries per run_code call. Defaults to 3. */ | |
| 72 | − readonly maxTypecheckRetries?: number | undefined; | |
| 73 | − /** Maximum total run_code calls per agent turn. Defaults to 10. */ | |
| 74 | − readonly maxCodeRuns?: number | undefined; | |
| 75 | − /** Execution timeout per code run in ms. Defaults to 30_000. */ | |
| 76 | − readonly timeoutMs?: number | undefined; | |
| 77 | − /** | |
| 78 | − * Tool count threshold for enabling discovery mode. | |
| 79 | − * When total tools exceed this, inject `tools.discover()` and | |
| 80 | − * only describe small tool sources in the system prompt. | |
| 81 | − * Defaults to 50. | |
| 82 | − */ | |
| 83 | − readonly discoveryThreshold?: number | undefined; | |
| 84 | − /** | |
| 85 | − * Additional context to include in the system prompt. | |
| 86 | − * Use this to provide environment-specific info like project IDs, | |
| 87 | − * usernames, organization names, etc. that the agent may need. | |
| 88 | − */ | |
| 89 | − readonly context?: string | undefined; | |
| 90 | −} | |
| 91 | − | |
| 92 | −export interface AgentResult { | |
| 93 | − readonly text: string; | |
| 94 | − readonly runs: readonly CodeRun[]; | |
| 95 | − readonly allReceipts: readonly ToolCallReceipt[]; | |
| 96 | −} | |
| 97 | − | |
| 98 | −export interface CodeRun { | |
| 99 | − readonly code: string; | |
| 100 | − readonly typecheckOk: boolean; | |
| 101 | − readonly result: RunResult; | |
| 102 | −} | |
| 103 | − | |
| 104 | −// --------------------------------------------------------------------------- | |
| 105 | −// Discovery mode | |
| 106 | −// --------------------------------------------------------------------------- | |
| 107 | − | |
| 108 | −const DISCOVERY_THRESHOLD = 50; | |
| 109 | − | |
| 110 | −/** | |
| 111 | − * When discovery mode is active, separate tools into: | |
| 112 | − * - "small" tools that fit in the prompt (under the threshold) | |
| 113 | − * - "large" tools that are only available via discover() | |
| 114 | − * | |
| 115 | − * The discover tool searches ALL tools. Small tools are described | |
| 116 | − * in the prompt AND available via discover. | |
| 117 | − */ | |
| 118 | −function prepareToolsForAgent( | |
| 119 | − tools: ToolTree, | |
| 120 | − threshold: number, | |
| 121 | −): { | |
| 122 | − /** Tools actually wired in the sandbox (always all of them + discover if needed) */ | |
| 123 | − sandboxTools: ToolTree; | |
| 124 | − /** Prompt guidance (subset or all) */ | |
| 125 | − promptGuidance: string; | |
| 126 | − /** Tool declarations for typechecker (subset or all) */ | |
| 127 | − toolDeclarations: string; | |
| 128 | − /** Whether discovery mode is active */ | |
| 129 | − discoveryMode: boolean; | |
| 130 | −} { | |
| 131 | − const totalCount = countTools(tools); | |
| 132 | − | |
| 133 | − if (totalCount <= threshold) { | |
| 134 | − // Small enough — describe everything in the prompt | |
| 135 | − return { | |
| 136 | − sandboxTools: tools, | |
| 137 | − promptGuidance: generatePromptGuidance(tools), | |
| 138 | − toolDeclarations: generateToolDeclarations(tools), | |
| 139 | − discoveryMode: false, | |
| 140 | − }; | |
| 141 | − } | |
| 142 | − | |
| 143 | − // Discovery mode: inject discover tool, only describe small namespaces in prompt | |
| 144 | − const discoverTool = createDiscoverTool(tools); | |
| 145 | − | |
| 146 | − // Separate namespaces into small (described) and large (discover-only) | |
| 147 | − const described: Record<string, ToolTree | import("./tools.js").ToolDefinition> = { discover: discoverTool }; | |
| 148 | − const largeNamespaces: string[] = []; | |
| 149 | − | |
| 150 | − for (const [key, value] of Object.entries(tools)) { | |
| 151 | − const nsCount = countTools({ [key]: value }); | |
| 152 | − if (nsCount <= threshold) { | |
| 153 | − described[key] = value; | |
| 154 | − } else { | |
| 155 | − largeNamespaces.push(`${key} (${nsCount} tools)`); | |
| 156 | − } | |
| 157 | − } | |
| 158 | − | |
| 159 | − const describedTree = described as ToolTree; | |
| 160 | − | |
| 161 | − // Sandbox gets ALL tools + discover | |
| 162 | − const sandboxTools = { ...tools, discover: discoverTool } as ToolTree; | |
| 163 | − | |
| 164 | − // Prompt only describes the small namespaces + discover | |
| 165 | − const promptGuidance = generatePromptGuidance(describedTree); | |
| 166 | − | |
| 167 | − // Typechecker: small namespaces are fully typed, large namespaces | |
| 168 | − // are declared as `Record<string, any>` so the LLM can call | |
| 169 | − // discovered tools without typecheck errors. | |
| 170 | − const largeNsDeclarations = Object.entries(tools) | |
| 171 | − .filter(([key]) => !described[key]) | |
| 172 | − .map(([key]) => ` ${key}: Record<string, Record<string, (...args: any[]) => Promise<any>>>;`) | |
| 173 | − .join("\n"); | |
| 174 | − | |
| 175 | − const toolDeclarations = generateToolDeclarations(describedTree).replace( | |
| 176 | − /\};$/, | |
| 177 | − largeNsDeclarations ? `${largeNsDeclarations}\n};` : "};", | |
| 178 | − ); | |
| 179 | − | |
| 180 | − return { | |
| 181 | − sandboxTools, | |
| 182 | − promptGuidance, | |
| 183 | − toolDeclarations, | |
| 184 | − discoveryMode: true, | |
| 185 | − }; | |
| 186 | −} | |
| 187 | − | |
| 188 | −// --------------------------------------------------------------------------- | |
| 189 | −// System prompt | |
| 190 | −// --------------------------------------------------------------------------- | |
| 191 | − | |
| 192 | −function buildSystemPrompt(guidance: string, discoveryMode: boolean, context?: string): string { | |
| 193 | − const discoveryInstructions = discoveryMode | |
| 194 | − ? ` | |
| 195 | −## Tool Discovery | |
| 196 | − | |
| 197 | −Some tool namespaces are too large to list here. Use \`tools.discover({ query })\` to search for tools by keyword. | |
| 198 | − | |
| 199 | −By default, discover returns the top 5 matches with **full type signatures** (input args + return types). Check the return type to understand response structure — look for pagination fields (\`next\`, \`cursor\`, \`page\`), wrapper objects, etc. | |
| 200 | − | |
| 201 | −Options: | |
| 202 | −- \`depth: 0\` — hide return types, just show input args. Use with \`limit: 20\` to browse broadly. | |
| 203 | −- \`depth: 2\` — include JSDoc comments and examples. Use for exact details. | |
| 204 | −- \`limit: N\` — control how many results (default 5, max 20). | |
| 205 | − | |
| 206 | −**Workflow:** | |
| 207 | −1. Discover tools to find what's available and see their types | |
| 208 | −2. Write a SINGLE self-contained script that does the entire task. If the return type has pagination, loop until all pages are fetched. | |
| 209 | − | |
| 210 | −Example — "close all open issues on acme/myapp": | |
| 211 | − | |
| 212 | −Call 1 (discover tools — returns types by default): | |
| 213 | −\`\`\`ts | |
| 214 | −return await tools.discover({ query: "issues list update repo" }); | |
| 215 | −\`\`\` | |
| 216 | − | |
| 217 | −Call 2 (do the work — everything in one script): | |
| 218 | −\`\`\`ts | |
| 219 | −const issues = await tools.github.issues.issues_list_for_repo({ | |
| 220 | − owner: "acme", repo: "myapp", state: "open", per_page: 100 | |
| 221 | −}); | |
| 222 | − | |
| 223 | −const results = []; | |
| 224 | −for (const issue of issues) { | |
| 225 | − const closed = await tools.github.issues.issues_update({ | |
| 226 | − owner: "acme", repo: "myapp", issue_number: issue.number, state: "closed" | |
| 227 | − }); | |
| 228 | − results.push({ number: issue.number, title: issue.title, state: closed.state }); | |
| 229 | −} | |
| 230 | − | |
| 231 | −return { closed: results.length, issues: results }; | |
| 232 | −\`\`\` | |
| 233 | −` | |
| 234 | − : ""; | |
| 235 | − | |
| 236 | − const contextSection = context | |
| 237 | − ? ` | |
| 238 | −## Context | |
| 239 | − | |
| 240 | −${context} | |
| 241 | −` | |
| 242 | − : ""; | |
| 243 | − | |
| 244 | − return `You are an AI assistant that executes tasks by generating TypeScript code. | |
| 245 | − | |
| 246 | −You have access to a set of tools via the \`tools\` object. When the user asks you to do something, generate TypeScript code that calls these tools to accomplish the task. | |
| 247 | −${contextSection} | |
| 248 | −## Available Tools | |
| 249 | − | |
| 250 | −${guidance} | |
| 251 | −${discoveryInstructions} | |
| 252 | −## Instructions | |
| 253 | − | |
| 254 | −- Use the \`run_code\` tool to execute TypeScript code | |
| 255 | −- **Write complete, self-contained scripts.** Once you know what tools are available, do all the work in a single run_code call — fetch data, loop, transform, write back, and return results. Don't do reads in one call and writes in another. | |
| 256 | −- The code runs in a sandboxed environment — only \`tools.*\` calls are available | |
| 257 | −- No \`fetch\`, \`process\`, \`require\`, or \`import\` — use tools for all external interactions | |
| 258 | −- Write clean, straightforward TypeScript — use for loops, map, filter, Promise.all as needed | |
| 259 | −- Handle errors gracefully with try/catch — if a tool call fails or is denied, continue with remaining work | |
| 260 | −- **Pagination:** If a list endpoint's return type has \`pagination\`, \`next\`, \`cursor\`, or similar fields, the results may be paginated. Check \`pagination.next\` (or equivalent) in the response — if it's not null, there are more pages. Paginate when the user needs complete results. Example pattern: | |
| 261 | − \`\`\`ts | |
| 262 | − let allItems = []; | |
| 263 | − let cursor = undefined; | |
| 264 | − while (true) { | |
| 265 | − const resp = await tools.api.list({ limit: 100, since: cursor }); | |
| 266 | − allItems.push(...resp.items); | |
| 267 | − if (!resp.pagination?.next) break; | |
| 268 | − cursor = resp.pagination.next; | |
| 269 | − } | |
| 270 | − \`\`\` | |
| 271 | −- Return a structured result from your script, then summarize what happened | |
| 272 | −- Be concise and accurate — base your response on actual tool results, not assumptions`; | |
| 273 | −} | |
| 274 | − | |
| 275 | −// --------------------------------------------------------------------------- | |
| 276 | −// Agent | |
| 277 | −// --------------------------------------------------------------------------- | |
| 278 | − | |
| 279 | −export function createAgent(options: AgentOptions): { | |
| 280 | − run(prompt: string): Promise<AgentResult>; | |
| 281 | −} { | |
| 282 | − const { | |
| 283 | − tools, | |
| 284 | − model, | |
| 285 | − requestApproval, | |
| 286 | − runner: providedRunner, | |
| 287 | − onEvent, | |
| 288 | − maxTypecheckRetries = 3, | |
| 289 | − maxCodeRuns = 100, | |
| 290 | − timeoutMs = 30_000, | |
| 291 | − discoveryThreshold = DISCOVERY_THRESHOLD, | |
| 292 | − context, | |
| 293 | − } = options; | |
| 294 | − | |
| 295 | − const { sandboxTools, promptGuidance, toolDeclarations, discoveryMode } = | |
| 296 | − prepareToolsForAgent(tools, discoveryThreshold); | |
| 297 | − | |
| 298 | − const runner = providedRunner ?? (() => { | |
| 299 | − if (!requestApproval) { | |
| 300 | − throw new Error("createAgent requires requestApproval when runner is not provided"); | |
| 301 | − } | |
| 302 | − return createRunner({ | |
| 303 | − tools: sandboxTools, | |
| 304 | − requestApproval, | |
| 305 | − timeoutMs, | |
| 306 | − }); | |
| 307 | − })(); | |
| 308 | − | |
| 309 | − function emit(event: TaskEvent): void { | |
| 310 | − onEvent?.(event); | |
| 311 | − } | |
| 312 | − | |
| 313 | − return { | |
| 314 | − async run(prompt: string): Promise<AgentResult> { | |
| 315 | − const systemPrompt = buildSystemPrompt(promptGuidance, discoveryMode, context); | |
| 316 | − const runs: CodeRun[] = []; | |
| 317 | − const allReceipts: ToolCallReceipt[] = []; | |
| 318 | − | |
| 319 | − const messages: Message[] = [ | |
| 320 | − { role: "system", content: systemPrompt }, | |
| 321 | − { role: "user", content: prompt }, | |
| 322 | − ]; | |
| 323 | − | |
| 324 | − emit({ type: "status", message: "Thinking..." }); | |
| 325 | − | |
| 326 | − let codeRunCount = 0; | |
| 327 | − | |
| 328 | − while (codeRunCount < maxCodeRuns) { | |
| 329 | − const response = await model.generate(messages); | |
| 330 | − | |
| 331 | − // If the model returned text without tool calls, we're done | |
| 332 | − if (!response.toolCalls || response.toolCalls.length === 0) { | |
| 333 | − const text = response.text ?? ""; | |
| 334 | − emit({ type: "agent_message", text }); | |
| 335 | − emit({ type: "completed", receipts: allReceipts }); | |
| 336 | − return { text, runs, allReceipts }; | |
| 337 | − } | |
| 338 | − | |
| 339 | − // Process each tool call (should be run_code) | |
| 340 | − for (const toolCall of response.toolCalls) { | |
| 341 | − if (toolCall.name !== "run_code") { | |
| 342 | − // Unknown tool call — tell the model | |
| 343 | − messages.push({ | |
| 344 | − role: "assistant", | |
| 345 | − content: "", | |
| 346 | − toolCalls: [toolCall], | |
| 347 | − }); | |
| 348 | − messages.push({ | |
| 349 | − role: "tool", | |
| 350 | − toolCallId: toolCall.id, | |
| 351 | − content: `Error: Unknown tool "${toolCall.name}". Use run_code to execute TypeScript code.`, | |
| 352 | − }); | |
| 353 | − continue; | |
| 354 | − } | |
| 355 | − | |
| 356 | − const code = String(toolCall.args["code"] ?? ""); | |
| 357 | − codeRunCount++; | |
| 358 | − | |
| 359 | − emit({ type: "code_generated", code }); | |
| 360 | − | |
| 361 | − // Typecheck with retries | |
| 362 | − let finalCode = code; | |
| 363 | − let typecheckOk = false; | |
| 364 | − let typecheckErrors: readonly string[] = []; | |
| 365 | − | |
| 366 | − for (let attempt = 0; attempt <= maxTypecheckRetries; attempt++) { | |
| 367 | − const check = typecheckCode(finalCode, toolDeclarations); | |
| 368 | − if (check.ok) { | |
| 369 | − typecheckOk = true; | |
| 370 | − break; | |
| 371 | − } | |
| 372 | − | |
| 373 | − typecheckErrors = check.errors; | |
| 374 | − | |
| 375 | − if (attempt < maxTypecheckRetries) { | |
| 376 | − emit({ | |
| 377 | − type: "status", | |
| 378 | − message: `Typecheck failed (attempt ${attempt + 1}/${maxTypecheckRetries + 1}), retrying...`, | |
| 379 | − }); | |
| 380 | − | |
| 381 | − // Ask the model to fix the code | |
| 382 | − const fixMessages: Message[] = [ | |
| 383 | − ...messages, | |
| 384 | − { | |
| 385 | − role: "assistant", | |
| 386 | − content: "", | |
| 387 | − toolCalls: [toolCall], | |
| 388 | − }, | |
| 389 | − { | |
| 390 | − role: "tool", | |
| 391 | − toolCallId: toolCall.id, | |
| 392 | − content: `Typecheck errors:\n${check.errors.join("\n")}\n\nPlease fix the code and try again.`, | |
| 393 | − }, | |
| 394 | − ]; | |
| 395 | − | |
| 396 | − const fixResponse = await model.generate(fixMessages); | |
| 397 | − if (fixResponse.toolCalls?.[0]?.name === "run_code") { | |
| 398 | − finalCode = String(fixResponse.toolCalls[0].args["code"] ?? ""); | |
| 399 | − emit({ type: "code_generated", code: finalCode }); | |
| 400 | − } else { | |
| 401 | − // Model gave up — use the original code | |
| 402 | − break; | |
| 403 | − } | |
| 404 | − } | |
| 405 | − } | |
| 406 | − | |
| 407 | − // Execute the code | |
| 408 | − emit({ type: "status", message: "Running code..." }); | |
| 409 | − const result = await runner.run(finalCode); | |
| 410 | − | |
| 411 | − // Emit receipts | |
| 412 | − for (const receipt of result.receipts) { | |
| 413 | − emit({ type: "tool_result", receipt }); | |
| 414 | − allReceipts.push(receipt); | |
| 415 | − } | |
| 416 | − | |
| 417 | − runs.push({ code: finalCode, typecheckOk, result }); | |
| 418 | − | |
| 419 | − // Build the tool result message for the model | |
| 420 | − const receiptSummary = result.receipts | |
| 421 | − .map((r) => { | |
| 422 | − const status = r.status === "succeeded" ? "OK" : r.status === "denied" ? "DENIED" : "FAILED"; | |
| 423 | − const output = r.outputPreview ? ` → ${r.outputPreview}` : ""; | |
| 424 | − const error = r.error ? ` (${r.error})` : ""; | |
| 425 | − return `[${status}] ${r.toolPath}(${r.inputPreview})${output}${error}`; | |
| 426 | − }) | |
| 427 | − .join("\n"); | |
| 428 | − | |
| 429 | − const resultContent = typecheckOk | |
| 430 | − ? result.ok | |
| 431 | − ? `Code executed successfully.\n\nTool call results:\n${receiptSummary}\n\nReturn value: ${JSON.stringify(result.value)}` | |
| 432 | − : `Code execution had issues.\n\nTool call results:\n${receiptSummary}\n\nError: ${result.error}` | |
| 433 | − : `Typecheck failed after ${maxTypecheckRetries + 1} attempts:\n${typecheckErrors.join("\n")}`; | |
| 434 | − | |
| 435 | − messages.push({ | |
| 436 | − role: "assistant", | |
| 437 | − content: "", | |
| 438 | − toolCalls: [{ ...toolCall, args: { code: finalCode } }], | |
| 439 | − }); | |
| 440 | − messages.push({ | |
| 441 | − role: "tool", | |
| 442 | − toolCallId: toolCall.id, | |
| 443 | − content: resultContent, | |
| 444 | − }); | |
| 445 | − } | |
| 446 | − } | |
| 447 | − | |
| 448 | − // Hit max code runs | |
| 449 | − const text = "I've reached the maximum number of code executions for this task."; | |
| 450 | − emit({ type: "agent_message", text }); | |
| 451 | − emit({ type: "completed", receipts: allReceipts }); | |
| 452 | − return { text, runs, allReceipts }; | |
| 453 | − }, | |
| 454 | − }; | |
| 455 | −} | |
| @@ -1,237 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Tool discovery — searchable index of tools for large APIs. | |
| 3 | − * | |
| 4 | − * The LLM uses `tools.discover({ query, depth? })` to find tools and | |
| 5 | − * control how much type detail to retrieve: | |
| 6 | − * | |
| 7 | − * - depth 0 (default): paths, descriptions, input args only. Cheap in tokens. | |
| 8 | − * - depth 1: adds return types with top-level properties. | |
| 9 | − * - depth 2: full return types with nested objects expanded. | |
| 10 | − * | |
| 11 | − * The discovered tools are already wired in the sandbox — the LLM | |
| 12 | − * just doesn't get told about all of them in the system prompt. | |
| 13 | − * `discover` fills that gap on demand. | |
| 14 | − */ | |
| 15 | − | |
| 16 | −import { defineTool, isToolDefinition, type ToolDefinition, type ToolTree } from "./tools.js"; | |
| 17 | −import { getArgsTypeString, getReturnsTypeString } from "./typechecker.js"; | |
| 18 | −import { z } from "zod"; | |
| 19 | − | |
| 20 | −// --------------------------------------------------------------------------- | |
| 21 | −// Index entry | |
| 22 | −// --------------------------------------------------------------------------- | |
| 23 | − | |
| 24 | −export interface ToolIndexEntry { | |
| 25 | − /** Full dotted path: "posthog.feature_flags.list" */ | |
| 26 | − readonly path: string; | |
| 27 | − /** Tool description */ | |
| 28 | − readonly description: string; | |
| 29 | − /** Approval mode */ | |
| 30 | − readonly approval: "auto" | "required"; | |
| 31 | − /** Full args type string (always available) */ | |
| 32 | − readonly argsType: string; | |
| 33 | − /** Full returns type string (may be large for OpenAPI tools) */ | |
| 34 | − readonly returnsType: string; | |
| 35 | − /** Searchable text (lowercase): path + description */ | |
| 36 | − readonly searchText: string; | |
| 37 | −} | |
| 38 | − | |
| 39 | −// --------------------------------------------------------------------------- | |
| 40 | −// Build index | |
| 41 | −// --------------------------------------------------------------------------- | |
| 42 | − | |
| 43 | −export function buildToolIndex(tree: ToolTree): ToolIndexEntry[] { | |
| 44 | − const entries: ToolIndexEntry[] = []; | |
| 45 | − | |
| 46 | − function walk(node: ToolTree, prefix: string) { | |
| 47 | − for (const [key, value] of Object.entries(node)) { | |
| 48 | − const path = prefix ? `${prefix}.${key}` : key; | |
| 49 | − if (isToolDefinition(value)) { | |
| 50 | − const tool = value as ToolDefinition; | |
| 51 | − const returnsType = getReturnsTypeString(tool); | |
| 52 | − // Detect paginated endpoints from return type | |
| 53 | − const isPaginated = /\bpagination\b|\bnext_cursor\b|\bhas_more\b|\bnextPage\b/.test(returnsType); | |
| 54 | − const description = isPaginated | |
| 55 | − ? `${tool.description} (⚠️ paginated — loop until pagination.next is null to get all results)` | |
| 56 | − : tool.description; | |
| 57 | − entries.push({ | |
| 58 | − path, | |
| 59 | − description, | |
| 60 | − approval: tool.approval, | |
| 61 | − argsType: getArgsTypeString(tool), | |
| 62 | − returnsType, | |
| 63 | − searchText: `${path} ${description}`.toLowerCase(), | |
| 64 | − }); | |
| 65 | − } else { | |
| 66 | − walk(value as ToolTree, path); | |
| 67 | − } | |
| 68 | − } | |
| 69 | − } | |
| 70 | − | |
| 71 | − walk(tree, ""); | |
| 72 | − return entries; | |
| 73 | −} | |
| 74 | − | |
| 75 | −// --------------------------------------------------------------------------- | |
| 76 | −// Strip JSDoc comments from type strings | |
| 77 | −// --------------------------------------------------------------------------- | |
| 78 | − | |
| 79 | −function stripJSDoc(typeStr: string): string { | |
| 80 | − return typeStr | |
| 81 | − .replace(/\/\*\*[^]*?\*\//g, "") // block comments | |
| 82 | − .replace(/\n\s*\n/g, "\n") // collapse blank lines | |
| 83 | − .trim(); | |
| 84 | −} | |
| 85 | − | |
| 86 | −// --------------------------------------------------------------------------- | |
| 87 | −// Format signature at different depths | |
| 88 | −// --------------------------------------------------------------------------- | |
| 89 | − | |
| 90 | −/** | |
| 91 | − * Build a signature string for a tool at a given depth: | |
| 92 | − * - depth 0: input args only, no return type | |
| 93 | − * - depth 1: input args + return type (JSDoc stripped for conciseness) | |
| 94 | − * - depth 2: input args + full return type (with JSDoc) | |
| 95 | − */ | |
| 96 | −function formatSignature(entry: ToolIndexEntry, depth: number): string { | |
| 97 | − const args = depth >= 2 ? entry.argsType : stripJSDoc(entry.argsType); | |
| 98 | − | |
| 99 | − if (depth === 0) { | |
| 100 | − return `(input: ${args}): Promise<...>`; | |
| 101 | − } | |
| 102 | − | |
| 103 | − const returns = depth >= 2 ? entry.returnsType : stripJSDoc(entry.returnsType); | |
| 104 | − return `(input: ${args}): Promise<${returns}>`; | |
| 105 | −} | |
| 106 | − | |
| 107 | −// --------------------------------------------------------------------------- | |
| 108 | −// Search | |
| 109 | −// --------------------------------------------------------------------------- | |
| 110 | − | |
| 111 | −/** | |
| 112 | − * Keyword search with fuzzy matching. Scores entries by how many | |
| 113 | − * query terms match, with bonuses for path matches. An entry only | |
| 114 | − * needs to match at least half the terms (rounded up) to be included, | |
| 115 | − * so "github issues list update" finds tools matching any 2+ of those words. | |
| 116 | − */ | |
| 117 | −function searchIndex( | |
| 118 | − index: ToolIndexEntry[], | |
| 119 | − query: string, | |
| 120 | − limit: number, | |
| 121 | −): ToolIndexEntry[] { | |
| 122 | − const terms = query.toLowerCase().split(/\s+/).filter(Boolean); | |
| 123 | − if (terms.length === 0) return index.slice(0, limit); | |
| 124 | − | |
| 125 | − // Minimum terms that must match: at least 1, or half rounded up for 3+ terms | |
| 126 | − const minMatches = terms.length <= 2 ? 1 : Math.ceil(terms.length / 2); | |
| 127 | − | |
| 128 | − const scored = index | |
| 129 | − .map((entry) => { | |
| 130 | − let score = 0; | |
| 131 | − let matched = 0; | |
| 132 | − | |
| 133 | − for (const term of terms) { | |
| 134 | − const inText = entry.searchText.includes(term); | |
| 135 | − const inPath = entry.path.toLowerCase().includes(term); | |
| 136 | − | |
| 137 | − if (!inText && !inPath) continue; | |
| 138 | − | |
| 139 | − matched++; | |
| 140 | − score += 1; | |
| 141 | − // Bonus for path match (more specific) | |
| 142 | − if (inPath) score += 2; | |
| 143 | − // Bonus for exact word boundary match | |
| 144 | − if (entry.searchText.includes(` ${term}`) || entry.searchText.startsWith(term)) score += 1; | |
| 145 | − } | |
| 146 | − | |
| 147 | − if (matched < minMatches) return { entry, score: -1 }; | |
| 148 | − // Bonus for matching more terms | |
| 149 | − score += matched * 2; | |
| 150 | − return { entry, score }; | |
| 151 | − }) | |
| 152 | − .filter((s) => s.score > 0) | |
| 153 | − .sort((a, b) => b.score - a.score) | |
| 154 | − .slice(0, limit); | |
| 155 | − | |
| 156 | − return scored.map((s) => s.entry); | |
| 157 | −} | |
| 158 | − | |
| 159 | −// --------------------------------------------------------------------------- | |
| 160 | −// Create discover tool | |
| 161 | −// --------------------------------------------------------------------------- | |
| 162 | − | |
| 163 | −export interface DiscoverToolOptions { | |
| 164 | − /** Max results to return per query. Defaults to 20. */ | |
| 165 | − readonly maxResults?: number; | |
| 166 | −} | |
| 167 | − | |
| 168 | −/** | |
| 169 | − * Create a `discover` tool that searches the tool index. | |
| 170 | − * | |
| 171 | − * Usage in LLM-generated code: | |
| 172 | − * // Find tools — returns top 5 with input args + return types | |
| 173 | − * const results = await tools.discover({ query: "feature flags" }); | |
| 174 | − * // Then call: await tools.posthog.feature_flags.list({ ... }) | |
| 175 | − */ | |
| 176 | −export function createDiscoverTool( | |
| 177 | − tree: ToolTree, | |
| 178 | − options?: DiscoverToolOptions, | |
| 179 | −) { | |
| 180 | − const index = buildToolIndex(tree); | |
| 181 | − const maxResults = options?.maxResults ?? 20; | |
| 182 | − | |
| 183 | − return defineTool({ | |
| 184 | − description: `Search for available tools by keyword. Returns matching tool paths, descriptions, and type signatures (including return types). There are ${index.length} tools available. | |
| 185 | − | |
| 186 | −depth controls how much type detail to return: | |
| 187 | −- depth 0: input args only, return type hidden. Use to browse many results cheaply. | |
| 188 | −- depth 1 (default): input args + return types. Shows response shapes so you can handle pagination, nested fields, etc. | |
| 189 | −- depth 2: full signatures with JSDoc comments and examples. Use when you need exact details. | |
| 190 | − | |
| 191 | −limit controls how many results to return (default: 5, max: ${maxResults}). Use higher limits with depth 0 for broad browsing.`, | |
| 192 | − approval: "auto" as const, | |
| 193 | − args: z.object({ | |
| 194 | − query: z.string().describe("Search keywords (e.g. 'feature flags', 'create user', 'list issues')"), | |
| 195 | − depth: z.number().optional().describe("Type detail level: 0 = args only, 1 (default) = + return types, 2 = + JSDoc comments"), | |
| 196 | − limit: z.number().optional().describe("Max results to return (default: 5)"), | |
| 197 | − }), | |
| 198 | − returns: z.object({ | |
| 199 | − results: z.array(z.object({ | |
| 200 | − path: z.string(), | |
| 201 | − description: z.string(), | |
| 202 | − approval: z.string(), | |
| 203 | − signature: z.string(), | |
| 204 | − })), | |
| 205 | − total: z.number(), | |
| 206 | − }), | |
| 207 | − run: async (input) => { | |
| 208 | − const depth = input.depth ?? 1; | |
| 209 | − const limit = Math.min(input.limit ?? 5, maxResults); | |
| 210 | − const results = searchIndex(index, input.query, limit); | |
| 211 | − return { | |
| 212 | − results: results.map((e) => ({ | |
| 213 | − path: e.path, | |
| 214 | − description: e.description, | |
| 215 | − approval: e.approval, | |
| 216 | − signature: formatSignature(e, depth), | |
| 217 | − })), | |
| 218 | − total: results.length, | |
| 219 | − }; | |
| 220 | − }, | |
| 221 | − }); | |
| 222 | −} | |
| 223 | − | |
| 224 | −/** | |
| 225 | − * Count the total number of tool definitions in a tree. | |
| 226 | − */ | |
| 227 | −export function countTools(tree: ToolTree): number { | |
| 228 | − let count = 0; | |
| 229 | − for (const value of Object.values(tree)) { | |
| 230 | − if (isToolDefinition(value)) { | |
| 231 | − count++; | |
| 232 | − } else { | |
| 233 | − count += countTools(value as ToolTree); | |
| 234 | − } | |
| 235 | − } | |
| 236 | − return count; | |
| 237 | −} | |
| @@ -1,60 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Task event types — the stream of events produced by every task run. | |
| 3 | − * Clients subscribe to these and render them however they want. | |
| 4 | − */ | |
| 5 | − | |
| 6 | −import type { ApprovalPresentation, ToolCallReceipt } from "./tools.js"; | |
| 7 | − | |
| 8 | −export type TaskEvent = | |
| 9 | − | TaskStatusEvent | |
| 10 | − | TaskCodeGeneratedEvent | |
| 11 | − | TaskApprovalRequestEvent | |
| 12 | − | TaskApprovalResolvedEvent | |
| 13 | − | TaskToolResultEvent | |
| 14 | − | TaskAgentMessageEvent | |
| 15 | − | TaskErrorEvent | |
| 16 | − | TaskCompletedEvent; | |
| 17 | − | |
| 18 | −export interface TaskStatusEvent { | |
| 19 | − readonly type: "status"; | |
| 20 | − readonly message: string; | |
| 21 | −} | |
| 22 | − | |
| 23 | −export interface TaskCodeGeneratedEvent { | |
| 24 | − readonly type: "code_generated"; | |
| 25 | − readonly code: string; | |
| 26 | −} | |
| 27 | − | |
| 28 | −export interface TaskApprovalRequestEvent { | |
| 29 | − readonly type: "approval_request"; | |
| 30 | − readonly id: string; | |
| 31 | − readonly toolPath: string; | |
| 32 | − readonly input: unknown; | |
| 33 | − readonly preview: ApprovalPresentation; | |
| 34 | −} | |
| 35 | − | |
| 36 | −export interface TaskApprovalResolvedEvent { | |
| 37 | − readonly type: "approval_resolved"; | |
| 38 | − readonly id: string; | |
| 39 | − readonly decision: "approved" | "denied"; | |
| 40 | −} | |
| 41 | − | |
| 42 | −export interface TaskToolResultEvent { | |
| 43 | − readonly type: "tool_result"; | |
| 44 | − readonly receipt: ToolCallReceipt; | |
| 45 | −} | |
| 46 | − | |
| 47 | −export interface TaskAgentMessageEvent { | |
| 48 | − readonly type: "agent_message"; | |
| 49 | − readonly text: string; | |
| 50 | −} | |
| 51 | − | |
| 52 | −export interface TaskErrorEvent { | |
| 53 | − readonly type: "error"; | |
| 54 | − readonly error: string; | |
| 55 | −} | |
| 56 | − | |
| 57 | −export interface TaskCompletedEvent { | |
| 58 | − readonly type: "completed"; | |
| 59 | − readonly receipts: readonly ToolCallReceipt[]; | |
| 60 | −} | |
| @@ -1,85 +0,0 @@ | ||
| 1 | −// Tool system | |
| 2 | −export { | |
| 3 | − defineTool, | |
| 4 | − isToolDefinition, | |
| 5 | − walkToolTree, | |
| 6 | − mergeToolTrees, | |
| 7 | − type ApprovalMode, | |
| 8 | − type ApprovalDecision, | |
| 9 | − type ApprovalPresentation, | |
| 10 | − type ApprovalRequest, | |
| 11 | − type ReceiptDecision, | |
| 12 | − type ReceiptStatus, | |
| 13 | − type ToolCallReceipt, | |
| 14 | − type ToolDefinition, | |
| 15 | − type ToolTree, | |
| 16 | − type DefineToolInput, | |
| 17 | −} from "./tools.js"; | |
| 18 | − | |
| 19 | −// Runner (sandbox execution) | |
| 20 | −export { | |
| 21 | − createRunner, | |
| 22 | − ToolDeniedError, | |
| 23 | − type Runner, | |
| 24 | − type RunnerOptions, | |
| 25 | − type RunResult, | |
| 26 | −} from "./runner.js"; | |
| 27 | − | |
| 28 | −// Remote runner (executor HTTP transport) | |
| 29 | −export { | |
| 30 | − createRemoteRunner, | |
| 31 | − type RemoteRunnerOptions, | |
| 32 | − type ExecutorRunRequest, | |
| 33 | − type ExecutorInvokeRequest, | |
| 34 | − type ExecutorInvokeResponse, | |
| 35 | − type ExecutorToolManifestEntry, | |
| 36 | −} from "./remote-runner.js"; | |
| 37 | − | |
| 38 | −// Typechecker | |
| 39 | −export { | |
| 40 | − zodToTypeString, | |
| 41 | − generateToolDeclarations, | |
| 42 | − generatePromptGuidance, | |
| 43 | − typecheckCode, | |
| 44 | − type TypecheckResult, | |
| 45 | −} from "./typechecker.js"; | |
| 46 | − | |
| 47 | −// Agent loop | |
| 48 | −export { | |
| 49 | − createAgent, | |
| 50 | − type LanguageModel, | |
| 51 | − type Message, | |
| 52 | − type ToolCall, | |
| 53 | − type GenerateResult, | |
| 54 | − type AgentOptions, | |
| 55 | − type AgentResult, | |
| 56 | − type CodeRun, | |
| 57 | −} from "./agent.js"; | |
| 58 | − | |
| 59 | −// pi-ai adapter | |
| 60 | −export { | |
| 61 | − createPiAiModel, | |
| 62 | − type PiAiModelOptions, | |
| 63 | −} from "./pi-ai-adapter.js"; | |
| 64 | − | |
| 65 | −// Discovery | |
| 66 | −export { | |
| 67 | − buildToolIndex, | |
| 68 | − createDiscoverTool, | |
| 69 | − countTools, | |
| 70 | − type ToolIndexEntry, | |
| 71 | − type DiscoverToolOptions, | |
| 72 | −} from "./discovery.js"; | |
| 73 | − | |
| 74 | −// Events | |
| 75 | −export type { | |
| 76 | − TaskEvent, | |
| 77 | − TaskStatusEvent, | |
| 78 | − TaskCodeGeneratedEvent, | |
| 79 | − TaskApprovalRequestEvent, | |
| 80 | − TaskApprovalResolvedEvent, | |
| 81 | − TaskToolResultEvent, | |
| 82 | − TaskAgentMessageEvent, | |
| 83 | − TaskErrorEvent, | |
| 84 | − TaskCompletedEvent, | |
| 85 | −} from "./events.js"; | |
| @@ -1,219 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Test: sandbox loop behavior — list items, then mutate each in a for loop. | |
| 3 | − * Reproduces the "works for 1, breaks for many" issue. | |
| 4 | − */ | |
| 5 | − | |
| 6 | −import { test, expect, describe } from "bun:test"; | |
| 7 | −import { createRunner } from "./runner.js"; | |
| 8 | −import { defineTool, type ToolTree } from "./tools.js"; | |
| 9 | −import { z } from "zod"; | |
| 10 | − | |
| 11 | −// Simulate an API: list items, then update each | |
| 12 | −const items = [ | |
| 13 | − { id: 1, title: "Issue 1", state: "closed" }, | |
| 14 | − { id: 2, title: "Issue 2", state: "closed" }, | |
| 15 | − { id: 3, title: "Issue 3", state: "closed" }, | |
| 16 | − { id: 4, title: "Issue 4", state: "closed" }, | |
| 17 | − { id: 5, title: "Issue 5", state: "closed" }, | |
| 18 | −]; | |
| 19 | − | |
| 20 | −let updateCallCount = 0; | |
| 21 | − | |
| 22 | −const tools: ToolTree = { | |
| 23 | − api: { | |
| 24 | − list_items: defineTool({ | |
| 25 | − description: "List items", | |
| 26 | − approval: "auto", | |
| 27 | − args: z.object({ state: z.string().optional() }), | |
| 28 | − returns: z.array(z.object({ id: z.number(), title: z.string(), state: z.string() })), | |
| 29 | − run: async (input) => { | |
| 30 | − const filtered = input.state | |
| 31 | − ? items.filter((i) => i.state === input.state) | |
| 32 | − : items; | |
| 33 | − return filtered; | |
| 34 | − }, | |
| 35 | − }), | |
| 36 | − update_item: defineTool({ | |
| 37 | − description: "Update an item", | |
| 38 | − approval: "auto", | |
| 39 | − args: z.object({ id: z.number(), state: z.string() }), | |
| 40 | − returns: z.object({ id: z.number(), state: z.string() }), | |
| 41 | − run: async (input) => { | |
| 42 | − updateCallCount++; | |
| 43 | − // Simulate a small network delay | |
| 44 | − await new Promise((r) => setTimeout(r, 10)); | |
| 45 | − return { id: input.id, state: input.state }; | |
| 46 | − }, | |
| 47 | − }), | |
| 48 | − }, | |
| 49 | −}; | |
| 50 | − | |
| 51 | −describe("sandbox loop — list then mutate each", () => { | |
| 52 | − test("for loop with await works for multiple items", async () => { | |
| 53 | − updateCallCount = 0; | |
| 54 | − | |
| 55 | − const runner = createRunner({ | |
| 56 | − tools, | |
| 57 | − requestApproval: async () => "approved", | |
| 58 | − timeoutMs: 30_000, | |
| 59 | − }); | |
| 60 | − | |
| 61 | − const code = ` | |
| 62 | −const items = await tools.api.list_items({ state: "closed" }); | |
| 63 | − | |
| 64 | −const results = []; | |
| 65 | −for (const item of items) { | |
| 66 | − const updated = await tools.api.update_item({ id: item.id, state: "open" }); | |
| 67 | − results.push(updated); | |
| 68 | −} | |
| 69 | − | |
| 70 | −return { total: results.length, results }; | |
| 71 | −`; | |
| 72 | − | |
| 73 | − const result = await runner.run(code); | |
| 74 | − | |
| 75 | − console.log("Result:", JSON.stringify(result.value)); | |
| 76 | − console.log("OK:", result.ok); | |
| 77 | − console.log("Error:", result.error); | |
| 78 | − console.log("Receipts:", result.receipts.length); | |
| 79 | − console.log("Update calls:", updateCallCount); | |
| 80 | − | |
| 81 | − expect(result.ok).toBe(true); | |
| 82 | − expect(result.receipts.length).toBe(6); // 1 list + 5 updates | |
| 83 | − expect(updateCallCount).toBe(5); | |
| 84 | − expect((result.value as any).total).toBe(5); | |
| 85 | − }); | |
| 86 | − | |
| 87 | − test("Promise.all with map works for multiple items", async () => { | |
| 88 | − updateCallCount = 0; | |
| 89 | − | |
| 90 | − const runner = createRunner({ | |
| 91 | − tools, | |
| 92 | − requestApproval: async () => "approved", | |
| 93 | − timeoutMs: 30_000, | |
| 94 | − }); | |
| 95 | − | |
| 96 | − const code = ` | |
| 97 | −const items = await tools.api.list_items({ state: "closed" }); | |
| 98 | − | |
| 99 | −const results = await Promise.all( | |
| 100 | − items.map((item) => tools.api.update_item({ id: item.id, state: "open" })) | |
| 101 | −); | |
| 102 | − | |
| 103 | −return { total: results.length, results }; | |
| 104 | −`; | |
| 105 | − | |
| 106 | − const result = await runner.run(code); | |
| 107 | − | |
| 108 | − console.log("Result:", JSON.stringify(result.value)); | |
| 109 | − console.log("OK:", result.ok); | |
| 110 | − console.log("Error:", result.error); | |
| 111 | − | |
| 112 | − expect(result.ok).toBe(true); | |
| 113 | − expect(result.receipts.length).toBe(6); | |
| 114 | − expect(updateCallCount).toBe(5); | |
| 115 | − }); | |
| 116 | − | |
| 117 | − test("for loop with approval-required tools", async () => { | |
| 118 | − const approvalTools: ToolTree = { | |
| 119 | − api: { | |
| 120 | − list_items: defineTool({ | |
| 121 | − description: "List items", | |
| 122 | − approval: "auto", | |
| 123 | − args: z.object({}), | |
| 124 | − returns: z.array(z.object({ id: z.number() })), | |
| 125 | − run: async () => [{ id: 1 }, { id: 2 }, { id: 3 }], | |
| 126 | − }), | |
| 127 | − update_item: defineTool({ | |
| 128 | − description: "Update item", | |
| 129 | − approval: "required", | |
| 130 | − args: z.object({ id: z.number(), state: z.string() }), | |
| 131 | − returns: z.object({ id: z.number(), state: z.string() }), | |
| 132 | − run: async (input) => ({ id: input.id, state: input.state }), | |
| 133 | − }), | |
| 134 | − }, | |
| 135 | − }; | |
| 136 | − | |
| 137 | − const runner = createRunner({ | |
| 138 | − tools: approvalTools, | |
| 139 | − requestApproval: async () => "approved", | |
| 140 | − timeoutMs: 30_000, | |
| 141 | − }); | |
| 142 | − | |
| 143 | − const code = ` | |
| 144 | −const items = await tools.api.list_items({}); | |
| 145 | −const results = []; | |
| 146 | −for (const item of items) { | |
| 147 | − const updated = await tools.api.update_item({ id: item.id, state: "open" }); | |
| 148 | − results.push(updated); | |
| 149 | −} | |
| 150 | −return { total: results.length, results }; | |
| 151 | −`; | |
| 152 | − | |
| 153 | − const result = await runner.run(code); | |
| 154 | − | |
| 155 | − console.log("Approval loop result:", JSON.stringify(result.value)); | |
| 156 | − console.log("OK:", result.ok); | |
| 157 | − console.log("Error:", result.error); | |
| 158 | − console.log("Receipts:", result.receipts.length); | |
| 159 | − | |
| 160 | − expect(result.ok).toBe(true); | |
| 161 | − expect(result.receipts.length).toBe(4); // 1 list + 3 updates | |
| 162 | − expect((result.value as any).total).toBe(3); | |
| 163 | − }); | |
| 164 | − | |
| 165 | − test("large loop — 20 items", async () => { | |
| 166 | − const manyItems = Array.from({ length: 20 }, (_, i) => ({ | |
| 167 | − id: i + 1, | |
| 168 | − title: `Issue ${i + 1}`, | |
| 169 | − state: "closed", | |
| 170 | − })); | |
| 171 | − | |
| 172 | − const manyTools: ToolTree = { | |
| 173 | − api: { | |
| 174 | − list_items: defineTool({ | |
| 175 | − description: "List items", | |
| 176 | − approval: "auto", | |
| 177 | − args: z.object({}), | |
| 178 | − returns: z.any(), | |
| 179 | − run: async () => manyItems, | |
| 180 | − }), | |
| 181 | − update_item: defineTool({ | |
| 182 | − description: "Update item", | |
| 183 | − approval: "auto", | |
| 184 | − args: z.object({ id: z.number(), state: z.string() }), | |
| 185 | − returns: z.object({ id: z.number(), state: z.string() }), | |
| 186 | − run: async (input) => { | |
| 187 | − await new Promise((r) => setTimeout(r, 5)); | |
| 188 | − return { id: input.id, state: input.state }; | |
| 189 | − }, | |
| 190 | − }), | |
| 191 | − }, | |
| 192 | − }; | |
| 193 | − | |
| 194 | − const runner = createRunner({ | |
| 195 | − tools: manyTools, | |
| 196 | − requestApproval: async () => "approved", | |
| 197 | − timeoutMs: 30_000, | |
| 198 | − }); | |
| 199 | − | |
| 200 | − const code = ` | |
| 201 | −const items = await tools.api.list_items({}); | |
| 202 | −const results = []; | |
| 203 | −for (const item of items) { | |
| 204 | − const updated = await tools.api.update_item({ id: item.id, state: "open" }); | |
| 205 | − results.push(updated); | |
| 206 | −} | |
| 207 | −return { total: results.length }; | |
| 208 | −`; | |
| 209 | − | |
| 210 | − const result = await runner.run(code); | |
| 211 | − | |
| 212 | − console.log("20 items result:", JSON.stringify(result.value)); | |
| 213 | − console.log("OK:", result.ok); | |
| 214 | − console.log("Error:", result.error); | |
| 215 | − | |
| 216 | − expect(result.ok).toBe(true); | |
| 217 | − expect((result.value as any).total).toBe(20); | |
| 218 | − }); | |
| 219 | −}); | |
| @@ -1,217 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * pi-ai adapter — implements our LanguageModel interface using pi-ai. | |
| 3 | − * | |
| 4 | − * Supports Claude Max (via ANTHROPIC_OAUTH_TOKEN), Anthropic API keys, | |
| 5 | − * and any other provider pi-ai supports. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import { | |
| 9 | − completeSimple, | |
| 10 | − getModel, | |
| 11 | − type Context as PiContext, | |
| 12 | − type Message as PiMessage, | |
| 13 | − type UserMessage, | |
| 14 | − type AssistantMessage as PiAssistantMessage, | |
| 15 | − type ToolResultMessage, | |
| 16 | − type Tool as PiTool, | |
| 17 | − type SimpleStreamOptions, | |
| 18 | − type TextContent, | |
| 19 | − type ToolCall as PiToolCall, | |
| 20 | −} from "@mariozechner/pi-ai"; | |
| 21 | −import { Type } from "@sinclair/typebox"; | |
| 22 | −import type { LanguageModel, Message, GenerateResult, ToolCall } from "./agent.js"; | |
| 23 | − | |
| 24 | −// --------------------------------------------------------------------------- | |
| 25 | −// Config | |
| 26 | −// --------------------------------------------------------------------------- | |
| 27 | − | |
| 28 | −export interface PiAiModelOptions { | |
| 29 | − /** pi-ai provider name (e.g., "anthropic", "openai"). Defaults to "anthropic". */ | |
| 30 | − readonly provider?: string | undefined; | |
| 31 | − /** pi-ai model ID (e.g., "claude-sonnet-4-5"). Defaults to "claude-sonnet-4-5". */ | |
| 32 | − readonly modelId?: string | undefined; | |
| 33 | − /** API key. If not provided, uses ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY env vars. */ | |
| 34 | − readonly apiKey?: string | undefined; | |
| 35 | − /** Thinking level for reasoning models. */ | |
| 36 | − readonly reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | undefined; | |
| 37 | − /** Max tokens for the response. */ | |
| 38 | − readonly maxTokens?: number | undefined; | |
| 39 | −} | |
| 40 | − | |
| 41 | −// --------------------------------------------------------------------------- | |
| 42 | −// The run_code tool definition for pi-ai (TypeBox schema) | |
| 43 | −// --------------------------------------------------------------------------- | |
| 44 | − | |
| 45 | −const RUN_CODE_TOOL: PiTool = { | |
| 46 | − name: "run_code", | |
| 47 | − description: | |
| 48 | − "Execute TypeScript code in a sandboxed environment. The code has access to a `tools` object with typed methods for interacting with external services. The code should use `return` to return a value.", | |
| 49 | − parameters: Type.Object({ | |
| 50 | − code: Type.String({ | |
| 51 | − description: "The TypeScript code to execute. Use `await tools.<namespace>.<method>(input)` to call tools. Use `return` to return a value.", | |
| 52 | − }), | |
| 53 | − }), | |
| 54 | −}; | |
| 55 | − | |
| 56 | −// --------------------------------------------------------------------------- | |
| 57 | −// Message conversion: our format → pi-ai format | |
| 58 | −// --------------------------------------------------------------------------- | |
| 59 | − | |
| 60 | −function convertMessages(messages: Message[]): { systemPrompt: string | undefined; piMessages: PiMessage[] } { | |
| 61 | − let systemPrompt: string | undefined; | |
| 62 | − const piMessages: PiMessage[] = []; | |
| 63 | − | |
| 64 | − for (const msg of messages) { | |
| 65 | − if (msg.role === "system") { | |
| 66 | − systemPrompt = msg.content; | |
| 67 | − continue; | |
| 68 | − } | |
| 69 | − | |
| 70 | − if (msg.role === "user") { | |
| 71 | − piMessages.push({ | |
| 72 | − role: "user", | |
| 73 | − content: msg.content, | |
| 74 | − timestamp: Date.now(), | |
| 75 | − } satisfies UserMessage); | |
| 76 | − continue; | |
| 77 | − } | |
| 78 | − | |
| 79 | − if (msg.role === "assistant") { | |
| 80 | − if ("toolCalls" in msg && msg.toolCalls) { | |
| 81 | − // Assistant message with tool calls | |
| 82 | − const content: (TextContent | PiToolCall)[] = []; | |
| 83 | − if (msg.content) { | |
| 84 | − content.push({ type: "text", text: msg.content }); | |
| 85 | − } | |
| 86 | − for (const tc of msg.toolCalls) { | |
| 87 | − content.push({ | |
| 88 | − type: "toolCall", | |
| 89 | − id: tc.id, | |
| 90 | − name: tc.name, | |
| 91 | − arguments: tc.args, | |
| 92 | − }); | |
| 93 | − } | |
| 94 | − piMessages.push({ | |
| 95 | − role: "assistant", | |
| 96 | − content, | |
| 97 | − api: "anthropic-messages", | |
| 98 | − provider: "anthropic", | |
| 99 | − model: "", | |
| 100 | − usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, | |
| 101 | − stopReason: "toolUse", | |
| 102 | − timestamp: Date.now(), | |
| 103 | − } satisfies PiAssistantMessage); | |
| 104 | − } else { | |
| 105 | − // Plain text assistant message | |
| 106 | − piMessages.push({ | |
| 107 | − role: "assistant", | |
| 108 | − content: [{ type: "text", text: msg.content }], | |
| 109 | − api: "anthropic-messages", | |
| 110 | − provider: "anthropic", | |
| 111 | − model: "", | |
| 112 | − usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, | |
| 113 | − stopReason: "stop", | |
| 114 | − timestamp: Date.now(), | |
| 115 | − } satisfies PiAssistantMessage); | |
| 116 | − } | |
| 117 | − continue; | |
| 118 | − } | |
| 119 | − | |
| 120 | − if (msg.role === "tool") { | |
| 121 | − piMessages.push({ | |
| 122 | − role: "toolResult", | |
| 123 | − toolCallId: msg.toolCallId, | |
| 124 | − toolName: "run_code", | |
| 125 | − content: [{ type: "text", text: msg.content }], | |
| 126 | − isError: false, | |
| 127 | − timestamp: Date.now(), | |
| 128 | − } satisfies ToolResultMessage); | |
| 129 | − continue; | |
| 130 | − } | |
| 131 | − } | |
| 132 | − | |
| 133 | − return { systemPrompt, piMessages }; | |
| 134 | −} | |
| 135 | − | |
| 136 | −// --------------------------------------------------------------------------- | |
| 137 | −// Response conversion: pi-ai format → our format | |
| 138 | −// --------------------------------------------------------------------------- | |
| 139 | − | |
| 140 | −function convertResponse(response: PiAssistantMessage): GenerateResult { | |
| 141 | − const toolCalls: ToolCall[] = []; | |
| 142 | − const textParts: string[] = []; | |
| 143 | − | |
| 144 | − for (const block of response.content) { | |
| 145 | − if (block.type === "text") { | |
| 146 | − textParts.push((block as TextContent).text); | |
| 147 | − } else if (block.type === "toolCall") { | |
| 148 | − const tc = block as PiToolCall; | |
| 149 | − toolCalls.push({ | |
| 150 | − id: tc.id, | |
| 151 | − name: tc.name, | |
| 152 | − args: tc.arguments, | |
| 153 | − }); | |
| 154 | − } | |
| 155 | − // Skip thinking blocks | |
| 156 | − } | |
| 157 | − | |
| 158 | − const text = textParts.join("") || undefined; | |
| 159 | − | |
| 160 | − if (toolCalls.length > 0) { | |
| 161 | − return { text, toolCalls }; | |
| 162 | − } | |
| 163 | − | |
| 164 | − return { text }; | |
| 165 | −} | |
| 166 | − | |
| 167 | −// --------------------------------------------------------------------------- | |
| 168 | −// Adapter | |
| 169 | −// --------------------------------------------------------------------------- | |
| 170 | − | |
| 171 | −/** | |
| 172 | − * Create a LanguageModel backed by pi-ai. | |
| 173 | − * | |
| 174 | − * Works with Claude Max (set ANTHROPIC_OAUTH_TOKEN env var), | |
| 175 | − * Anthropic API keys (set ANTHROPIC_API_KEY), or any pi-ai provider. | |
| 176 | − */ | |
| 177 | −export function createPiAiModel(options: PiAiModelOptions = {}): LanguageModel { | |
| 178 | − const { | |
| 179 | − provider = "anthropic", | |
| 180 | − modelId = "claude-sonnet-4-5", | |
| 181 | − apiKey, | |
| 182 | − reasoning, | |
| 183 | − maxTokens = 8192, | |
| 184 | − } = options; | |
| 185 | − | |
| 186 | − const model = getModel(provider as Parameters<typeof getModel>[0], modelId as never); | |
| 187 | − | |
| 188 | − return { | |
| 189 | − async generate(messages: Message[]): Promise<GenerateResult> { | |
| 190 | − const { systemPrompt, piMessages } = convertMessages(messages); | |
| 191 | − | |
| 192 | − const context: PiContext = { | |
| 193 | − messages: piMessages, | |
| 194 | − tools: [RUN_CODE_TOOL], | |
| 195 | − }; | |
| 196 | − if (systemPrompt !== undefined) { | |
| 197 | − context.systemPrompt = systemPrompt; | |
| 198 | − } | |
| 199 | − | |
| 200 | − const streamOptions: SimpleStreamOptions = { | |
| 201 | − maxTokens, | |
| 202 | − }; | |
| 203 | − | |
| 204 | − if (apiKey) { | |
| 205 | − streamOptions.apiKey = apiKey; | |
| 206 | − } | |
| 207 | − | |
| 208 | − if (reasoning) { | |
| 209 | − streamOptions.reasoning = reasoning; | |
| 210 | − } | |
| 211 | − | |
| 212 | − const response = await completeSimple(model, context, streamOptions); | |
| 213 | − | |
| 214 | − return convertResponse(response); | |
| 215 | − }, | |
| 216 | − }; | |
| 217 | −} | |
| @@ -1,89 +0,0 @@ | ||
| 1 | −import { describe, expect, test } from "bun:test"; | |
| 2 | −import { z } from "zod"; | |
| 3 | −import { defineTool, type ToolTree } from "./tools.js"; | |
| 4 | −import { createRemoteRunner } from "./remote-runner.js"; | |
| 5 | − | |
| 6 | −describe("remote runner", () => { | |
| 7 | − test("sends run request with tool manifest", async () => { | |
| 8 | − let capturedUrl = ""; | |
| 9 | − let capturedBody: unknown; | |
| 10 | − | |
| 11 | − const tools: ToolTree = { | |
| 12 | − math: { | |
| 13 | − add: defineTool({ | |
| 14 | − description: "Add two numbers", | |
| 15 | − approval: "auto", | |
| 16 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 17 | − returns: z.number(), | |
| 18 | − run: async (input) => input.a + input.b, | |
| 19 | − }), | |
| 20 | − }, | |
| 21 | − db: { | |
| 22 | − delete: defineTool({ | |
| 23 | − description: "Delete a row", | |
| 24 | − approval: "required", | |
| 25 | − args: z.object({ id: z.string() }), | |
| 26 | − returns: z.object({ deleted: z.boolean() }), | |
| 27 | − run: async () => ({ deleted: true }), | |
| 28 | − }), | |
| 29 | − }, | |
| 30 | − }; | |
| 31 | − | |
| 32 | − const runner = createRemoteRunner({ | |
| 33 | − tools, | |
| 34 | − executorUrl: "http://executor.local", | |
| 35 | − runId: "run_123", | |
| 36 | − callbackBaseUrl: "http://api.local", | |
| 37 | − callbackToken: "token_123", | |
| 38 | − timeoutMs: 5_000, | |
| 39 | − fetchImpl: async (url, init) => { | |
| 40 | − capturedUrl = String(url); | |
| 41 | − capturedBody = JSON.parse(String(init?.body ?? "{}")); | |
| 42 | − return new Response( | |
| 43 | − JSON.stringify({ ok: true, value: 42, receipts: [] }), | |
| 44 | − { status: 200, headers: { "content-type": "application/json" } }, | |
| 45 | − ); | |
| 46 | − }, | |
| 47 | − }); | |
| 48 | − | |
| 49 | − const result = await runner.run("return 42;"); | |
| 50 | − | |
| 51 | − expect(capturedUrl).toBe("http://executor.local/internal/execute"); | |
| 52 | − expect(result.ok).toBe(true); | |
| 53 | − expect(result.value).toBe(42); | |
| 54 | − | |
| 55 | − const request = capturedBody as { | |
| 56 | − runId: string; | |
| 57 | − timeoutMs: number; | |
| 58 | − callbackBaseUrl: string; | |
| 59 | − callbackToken: string; | |
| 60 | − tools: Array<{ toolPath: string; approval: string }>; | |
| 61 | − }; | |
| 62 | − expect(request.runId).toBe("run_123"); | |
| 63 | − expect(request.timeoutMs).toBe(5_000); | |
| 64 | − expect(request.callbackBaseUrl).toBe("http://api.local"); | |
| 65 | − expect(request.callbackToken).toBe("token_123"); | |
| 66 | − expect(request.tools).toContainEqual({ toolPath: "math.add", approval: "auto" }); | |
| 67 | − expect(request.tools).toContainEqual({ toolPath: "db.delete", approval: "required" }); | |
| 68 | − }); | |
| 69 | − | |
| 70 | − test("returns error when executor response is invalid", async () => { | |
| 71 | − const runner = createRemoteRunner({ | |
| 72 | − tools: {}, | |
| 73 | − executorUrl: "http://executor.local", | |
| 74 | − runId: "run_456", | |
| 75 | − callbackBaseUrl: "http://api.local", | |
| 76 | − callbackToken: "token_456", | |
| 77 | − fetchImpl: async () => { | |
| 78 | − return new Response(JSON.stringify({ nope: true }), { | |
| 79 | − status: 200, | |
| 80 | − headers: { "content-type": "application/json" }, | |
| 81 | − }); | |
| 82 | − }, | |
| 83 | − }); | |
| 84 | − | |
| 85 | − const result = await runner.run("return 1;"); | |
| 86 | − expect(result.ok).toBe(false); | |
| 87 | − expect(result.error).toContain("invalid response"); | |
| 88 | − }); | |
| 89 | −}); | |
| @@ -1,124 +0,0 @@ | ||
| 1 | −import type { ApprovalMode, ToolTree } from "./tools.js"; | |
| 2 | −import { walkToolTree } from "./tools.js"; | |
| 3 | −import type { RunResult, Runner } from "./runner.js"; | |
| 4 | − | |
| 5 | −export interface ExecutorToolManifestEntry { | |
| 6 | − readonly toolPath: string; | |
| 7 | − readonly approval: ApprovalMode; | |
| 8 | −} | |
| 9 | − | |
| 10 | −export interface ExecutorRunRequest { | |
| 11 | − readonly runId: string; | |
| 12 | − readonly code: string; | |
| 13 | − readonly timeoutMs: number; | |
| 14 | − readonly callbackBaseUrl: string; | |
| 15 | − readonly callbackToken: string; | |
| 16 | − readonly tools: readonly ExecutorToolManifestEntry[]; | |
| 17 | −} | |
| 18 | − | |
| 19 | −export interface ExecutorInvokeRequest { | |
| 20 | − readonly toolPath: string; | |
| 21 | − readonly input: unknown; | |
| 22 | −} | |
| 23 | − | |
| 24 | −export interface ExecutorInvokeResponse { | |
| 25 | − readonly ok: boolean; | |
| 26 | − readonly value?: unknown; | |
| 27 | − readonly error?: string; | |
| 28 | − readonly denied?: boolean; | |
| 29 | −} | |
| 30 | − | |
| 31 | −export interface RemoteRunnerOptions { | |
| 32 | − readonly tools: ToolTree; | |
| 33 | − readonly executorUrl: string; | |
| 34 | − readonly runId: string; | |
| 35 | − readonly callbackBaseUrl: string; | |
| 36 | − readonly callbackToken: string; | |
| 37 | − readonly timeoutMs?: number | undefined; | |
| 38 | − readonly fetchImpl?: FetchLike | undefined; | |
| 39 | −} | |
| 40 | − | |
| 41 | −const DEFAULT_TIMEOUT_MS = 30_000; | |
| 42 | −type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>; | |
| 43 | − | |
| 44 | −function trimTrailingSlash(value: string): string { | |
| 45 | − return value.endsWith("/") ? value.slice(0, -1) : value; | |
| 46 | −} | |
| 47 | − | |
| 48 | −function buildToolManifest(tools: ToolTree): ExecutorToolManifestEntry[] { | |
| 49 | − const manifest: ExecutorToolManifestEntry[] = []; | |
| 50 | − walkToolTree(tools, (toolPath, tool) => { | |
| 51 | − manifest.push({ toolPath, approval: tool.approval }); | |
| 52 | − }); | |
| 53 | − return manifest; | |
| 54 | −} | |
| 55 | − | |
| 56 | −function isRunResult(value: unknown): value is RunResult { | |
| 57 | − if (!value || typeof value !== "object") return false; | |
| 58 | − const record = value as Record<string, unknown>; | |
| 59 | − return typeof record["ok"] === "boolean" && Array.isArray(record["receipts"]); | |
| 60 | −} | |
| 61 | − | |
| 62 | −export function createRemoteRunner(options: RemoteRunnerOptions): Runner { | |
| 63 | − const { | |
| 64 | − tools, | |
| 65 | − executorUrl, | |
| 66 | − runId, | |
| 67 | − callbackBaseUrl, | |
| 68 | − callbackToken, | |
| 69 | − timeoutMs = DEFAULT_TIMEOUT_MS, | |
| 70 | − fetchImpl = fetch as FetchLike, | |
| 71 | − } = options; | |
| 72 | − | |
| 73 | − const endpoint = `${trimTrailingSlash(executorUrl)}/internal/execute`; | |
| 74 | − const manifest = buildToolManifest(tools); | |
| 75 | − | |
| 76 | − return { | |
| 77 | − async run(code: string): Promise<RunResult> { | |
| 78 | − const payload: ExecutorRunRequest = { | |
| 79 | − runId, | |
| 80 | − code, | |
| 81 | − timeoutMs, | |
| 82 | − callbackBaseUrl, | |
| 83 | − callbackToken, | |
| 84 | − tools: manifest, | |
| 85 | − }; | |
| 86 | − | |
| 87 | − try { | |
| 88 | − const response = await fetchImpl(endpoint, { | |
| 89 | − method: "POST", | |
| 90 | − headers: { | |
| 91 | − "content-type": "application/json", | |
| 92 | − }, | |
| 93 | − body: JSON.stringify(payload), | |
| 94 | − }); | |
| 95 | − | |
| 96 | − if (!response.ok) { | |
| 97 | − return { | |
| 98 | − ok: false, | |
| 99 | − error: `Executor request failed: HTTP ${response.status}`, | |
| 100 | − receipts: [], | |
| 101 | − }; | |
| 102 | − } | |
| 103 | − | |
| 104 | − const result = await response.json(); | |
| 105 | − if (!isRunResult(result)) { | |
| 106 | − return { | |
| 107 | − ok: false, | |
| 108 | − error: "Executor returned an invalid response", | |
| 109 | − receipts: [], | |
| 110 | − }; | |
| 111 | − } | |
| 112 | − | |
| 113 | − return result; | |
| 114 | − } catch (error) { | |
| 115 | − const message = error instanceof Error ? error.message : String(error); | |
| 116 | − return { | |
| 117 | − ok: false, | |
| 118 | − error: `Executor request failed: ${message}`, | |
| 119 | − receipts: [], | |
| 120 | − }; | |
| 121 | − } | |
| 122 | − }, | |
| 123 | − }; | |
| 124 | −} | |
| @@ -1,453 +0,0 @@ | ||
| 1 | −import { describe, test, expect } from "bun:test"; | |
| 2 | −import { z } from "zod"; | |
| 3 | −import { defineTool, type ToolTree, type ApprovalDecision } from "./tools.js"; | |
| 4 | −import { createRunner, ToolDeniedError } from "./runner.js"; | |
| 5 | − | |
| 6 | −// --------------------------------------------------------------------------- | |
| 7 | −// Test helpers | |
| 8 | −// --------------------------------------------------------------------------- | |
| 9 | − | |
| 10 | −function mathTools() { | |
| 11 | − return { | |
| 12 | − math: { | |
| 13 | − add: defineTool({ | |
| 14 | − description: "Add two numbers", | |
| 15 | − approval: "auto", | |
| 16 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 17 | − returns: z.number(), | |
| 18 | − run: async (input) => input.a + input.b, | |
| 19 | − }), | |
| 20 | − multiply: defineTool({ | |
| 21 | − description: "Multiply two numbers", | |
| 22 | − approval: "auto", | |
| 23 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 24 | − returns: z.number(), | |
| 25 | − run: async (input) => input.a * input.b, | |
| 26 | − }), | |
| 27 | − }, | |
| 28 | − } satisfies ToolTree; | |
| 29 | −} | |
| 30 | − | |
| 31 | −function writeTools(sideEffects: string[]) { | |
| 32 | − return { | |
| 33 | − db: { | |
| 34 | − insert: defineTool({ | |
| 35 | − description: "Insert a record", | |
| 36 | − approval: "required", | |
| 37 | − args: z.object({ table: z.string(), data: z.record(z.string(), z.unknown()) }), | |
| 38 | − returns: z.object({ id: z.string() }), | |
| 39 | − run: async (input) => { | |
| 40 | − sideEffects.push(`insert:${input.table}`); | |
| 41 | − return { id: "new_1" }; | |
| 42 | − }, | |
| 43 | − formatApproval: (input) => ({ | |
| 44 | − title: `Insert into ${input.table}`, | |
| 45 | − }), | |
| 46 | − }), | |
| 47 | − delete: defineTool({ | |
| 48 | − description: "Delete a record", | |
| 49 | − approval: "required", | |
| 50 | − args: z.object({ table: z.string(), id: z.string() }), | |
| 51 | − returns: z.object({ deleted: z.boolean() }), | |
| 52 | − run: async (input) => { | |
| 53 | − sideEffects.push(`delete:${input.table}:${input.id}`); | |
| 54 | − return { deleted: true }; | |
| 55 | − }, | |
| 56 | − formatApproval: (input) => ({ | |
| 57 | − title: `Delete ${input.id} from ${input.table}`, | |
| 58 | − }), | |
| 59 | − }), | |
| 60 | − }, | |
| 61 | − } satisfies ToolTree; | |
| 62 | −} | |
| 63 | − | |
| 64 | −let callIdSeq = 0; | |
| 65 | −function testCallId() { | |
| 66 | − return `test_${++callIdSeq}`; | |
| 67 | −} | |
| 68 | − | |
| 69 | −function alwaysApprove() { | |
| 70 | − return async (_req: unknown) => "approved" as ApprovalDecision; | |
| 71 | −} | |
| 72 | − | |
| 73 | −function alwaysDeny() { | |
| 74 | − return async (_req: unknown) => "denied" as ApprovalDecision; | |
| 75 | −} | |
| 76 | − | |
| 77 | −// --------------------------------------------------------------------------- | |
| 78 | −// Tests | |
| 79 | −// --------------------------------------------------------------------------- | |
| 80 | − | |
| 81 | −describe("runner — sandbox isolation", () => { | |
| 82 | − test("blocks fetch", async () => { | |
| 83 | − const runner = createRunner({ | |
| 84 | − tools: mathTools(), | |
| 85 | − requestApproval: alwaysApprove(), | |
| 86 | − newCallId: testCallId, | |
| 87 | − }); | |
| 88 | − | |
| 89 | − const result = await runner.run('return fetch("https://evil.com")'); | |
| 90 | − expect(result.ok).toBe(false); | |
| 91 | − expect(result.error).toContain("not available in the sandbox"); | |
| 92 | − }); | |
| 93 | − | |
| 94 | − test("blocks process", async () => { | |
| 95 | − const runner = createRunner({ | |
| 96 | − tools: mathTools(), | |
| 97 | − requestApproval: alwaysApprove(), | |
| 98 | − newCallId: testCallId, | |
| 99 | − }); | |
| 100 | − | |
| 101 | − const result = await runner.run("return process.env"); | |
| 102 | − expect(result.ok).toBe(false); | |
| 103 | − expect(result.error).toContain("not available in the sandbox"); | |
| 104 | − }); | |
| 105 | − | |
| 106 | − test("blocks require", async () => { | |
| 107 | − const runner = createRunner({ | |
| 108 | − tools: mathTools(), | |
| 109 | − requestApproval: alwaysApprove(), | |
| 110 | − newCallId: testCallId, | |
| 111 | − }); | |
| 112 | − | |
| 113 | − const result = await runner.run('return require("fs")'); | |
| 114 | − expect(result.ok).toBe(false); | |
| 115 | − expect(result.error).toContain("not available in the sandbox"); | |
| 116 | − }); | |
| 117 | − | |
| 118 | − test("blocks Bun", async () => { | |
| 119 | − const runner = createRunner({ | |
| 120 | − tools: mathTools(), | |
| 121 | − requestApproval: alwaysApprove(), | |
| 122 | − newCallId: testCallId, | |
| 123 | − }); | |
| 124 | − | |
| 125 | − const result = await runner.run("return Bun.env"); | |
| 126 | − expect(result.ok).toBe(false); | |
| 127 | − expect(result.error).toContain("not available in the sandbox"); | |
| 128 | − }); | |
| 129 | − | |
| 130 | − test("blocks dynamic import", async () => { | |
| 131 | − const runner = createRunner({ | |
| 132 | − tools: mathTools(), | |
| 133 | − requestApproval: alwaysApprove(), | |
| 134 | − newCallId: testCallId, | |
| 135 | − }); | |
| 136 | − | |
| 137 | − const result = await runner.run('return await import("node:fs")'); | |
| 138 | − expect(result.ok).toBe(false); | |
| 139 | − // The error message varies but it should fail | |
| 140 | − expect(result.error).toBeDefined(); | |
| 141 | − }); | |
| 142 | − | |
| 143 | − test("constructor chain escape returns sandbox global (no host access)", async () => { | |
| 144 | − const runner = createRunner({ | |
| 145 | − tools: mathTools(), | |
| 146 | − requestApproval: alwaysApprove(), | |
| 147 | − newCallId: testCallId, | |
| 148 | − }); | |
| 149 | − | |
| 150 | − // The escape returns the sandbox global. Accessing 'process' on it | |
| 151 | − // triggers our blocker getter which throws — that's the correct behavior. | |
| 152 | − // The key thing is: process.env.SECRET etc. are never reachable. | |
| 153 | − const result = await runner.run(` | |
| 154 | − const escaped = [].constructor.constructor("return this")(); | |
| 155 | − // escaped is the sandbox global — has no host APIs | |
| 156 | − // Only our injected properties exist on it | |
| 157 | − try { | |
| 158 | − escaped.process; | |
| 159 | − return "BAD: process was accessible"; | |
| 160 | − } catch (e) { | |
| 161 | − return "GOOD: process blocked"; | |
| 162 | − } | |
| 163 | − `); | |
| 164 | − expect(result.ok).toBe(true); | |
| 165 | − expect(result.value).toBe("GOOD: process blocked"); | |
| 166 | − }); | |
| 167 | − | |
| 168 | − test("tool function toString is hidden", async () => { | |
| 169 | − const runner = createRunner({ | |
| 170 | − tools: mathTools(), | |
| 171 | − requestApproval: alwaysApprove(), | |
| 172 | − newCallId: testCallId, | |
| 173 | − }); | |
| 174 | − | |
| 175 | − const result = await runner.run("return tools.math.add.toString()"); | |
| 176 | − expect(result.ok).toBe(true); | |
| 177 | − expect(result.value).toBe("function() { [native code] }"); | |
| 178 | − }); | |
| 179 | − | |
| 180 | − test("execution timeout", async () => { | |
| 181 | − const runner = createRunner({ | |
| 182 | − tools: mathTools(), | |
| 183 | − requestApproval: alwaysApprove(), | |
| 184 | − timeoutMs: 100, | |
| 185 | − newCallId: testCallId, | |
| 186 | − }); | |
| 187 | − | |
| 188 | − const result = await runner.run("while(true) {}"); | |
| 189 | − expect(result.ok).toBe(false); | |
| 190 | − expect(result.error).toContain("timed out"); | |
| 191 | − }); | |
| 192 | −}); | |
| 193 | − | |
| 194 | −describe("runner — auto-approved read tools", () => { | |
| 195 | − test("executes tool and returns result", async () => { | |
| 196 | − const runner = createRunner({ | |
| 197 | − tools: mathTools(), | |
| 198 | − requestApproval: alwaysDeny(), // should never be called | |
| 199 | − newCallId: testCallId, | |
| 200 | − }); | |
| 201 | − | |
| 202 | − const result = await runner.run("return await tools.math.add({ a: 3, b: 4 })"); | |
| 203 | − expect(result.ok).toBe(true); | |
| 204 | − expect(result.value).toBe(7); | |
| 205 | − expect(result.receipts).toHaveLength(1); | |
| 206 | − expect(result.receipts[0]!.decision).toBe("auto"); | |
| 207 | − expect(result.receipts[0]!.status).toBe("succeeded"); | |
| 208 | − }); | |
| 209 | − | |
| 210 | − test("multiple tool calls collect receipts", async () => { | |
| 211 | − const runner = createRunner({ | |
| 212 | − tools: mathTools(), | |
| 213 | − requestApproval: alwaysDeny(), | |
| 214 | − newCallId: testCallId, | |
| 215 | − }); | |
| 216 | − | |
| 217 | − const result = await runner.run(` | |
| 218 | − const a = await tools.math.add({ a: 1, b: 2 }); | |
| 219 | − const b = await tools.math.multiply({ a: a, b: 3 }); | |
| 220 | − return b; | |
| 221 | − `); | |
| 222 | − expect(result.ok).toBe(true); | |
| 223 | − expect(result.value).toBe(9); | |
| 224 | − expect(result.receipts).toHaveLength(2); | |
| 225 | − }); | |
| 226 | − | |
| 227 | − test("validates input with zod", async () => { | |
| 228 | − const runner = createRunner({ | |
| 229 | − tools: mathTools(), | |
| 230 | − requestApproval: alwaysApprove(), | |
| 231 | − newCallId: testCallId, | |
| 232 | − }); | |
| 233 | − | |
| 234 | − const result = await runner.run( | |
| 235 | − 'return await tools.math.add({ a: "not a number", b: 2 })', | |
| 236 | − ); | |
| 237 | − expect(result.ok).toBe(false); | |
| 238 | − expect(result.receipts).toHaveLength(1); | |
| 239 | − expect(result.receipts[0]!.status).toBe("failed"); | |
| 240 | − expect(result.receipts[0]!.error).toContain("validation failed"); | |
| 241 | − }); | |
| 242 | −}); | |
| 243 | − | |
| 244 | −describe("runner — approval flow", () => { | |
| 245 | − test("approved write tool runs", async () => { | |
| 246 | − const sideEffects: string[] = []; | |
| 247 | − const runner = createRunner({ | |
| 248 | − tools: writeTools(sideEffects), | |
| 249 | − requestApproval: alwaysApprove(), | |
| 250 | − newCallId: testCallId, | |
| 251 | − }); | |
| 252 | − | |
| 253 | − const result = await runner.run( | |
| 254 | − 'return await tools.db.insert({ table: "users", data: { name: "Alice" } })', | |
| 255 | − ); | |
| 256 | − expect(result.ok).toBe(true); | |
| 257 | − expect(result.value).toEqual({ id: "new_1" }); | |
| 258 | − expect(sideEffects).toEqual(["insert:users"]); | |
| 259 | − expect(result.receipts).toHaveLength(1); | |
| 260 | − expect(result.receipts[0]!.decision).toBe("approved"); | |
| 261 | − expect(result.receipts[0]!.status).toBe("succeeded"); | |
| 262 | − }); | |
| 263 | − | |
| 264 | − test("denied write tool returns undefined and doesn't run", async () => { | |
| 265 | − const sideEffects: string[] = []; | |
| 266 | − const runner = createRunner({ | |
| 267 | − tools: writeTools(sideEffects), | |
| 268 | − requestApproval: alwaysDeny(), | |
| 269 | − newCallId: testCallId, | |
| 270 | − }); | |
| 271 | − | |
| 272 | − const result = await runner.run( | |
| 273 | − 'return await tools.db.insert({ table: "users", data: { name: "Alice" } })', | |
| 274 | − ); | |
| 275 | − expect(result.ok).toBe(false); | |
| 276 | − expect(sideEffects).toEqual([]); // run was never called | |
| 277 | − expect(result.receipts).toHaveLength(1); | |
| 278 | − expect(result.receipts[0]!.decision).toBe("denied"); | |
| 279 | − expect(result.receipts[0]!.status).toBe("denied"); | |
| 280 | − }); | |
| 281 | − | |
| 282 | − test("continues after one denial", async () => { | |
| 283 | − const sideEffects: string[] = []; | |
| 284 | − let callCount = 0; | |
| 285 | − const approveOddDenyEven = async () => { | |
| 286 | − callCount++; | |
| 287 | − return (callCount % 2 === 1 ? "approved" : "denied") as ApprovalDecision; | |
| 288 | − }; | |
| 289 | − | |
| 290 | − const runner = createRunner({ | |
| 291 | − tools: writeTools(sideEffects), | |
| 292 | − requestApproval: approveOddDenyEven, | |
| 293 | − newCallId: testCallId, | |
| 294 | − }); | |
| 295 | − | |
| 296 | − const result = await runner.run(` | |
| 297 | − const r1 = await tools.db.insert({ table: "a", data: {} }); | |
| 298 | − const r2 = await tools.db.insert({ table: "b", data: {} }); | |
| 299 | − const r3 = await tools.db.insert({ table: "c", data: {} }); | |
| 300 | − return [r1, r2, r3]; | |
| 301 | − `); | |
| 302 | − | |
| 303 | − // r1 approved, r2 denied (undefined), r3 approved | |
| 304 | − expect(result.ok).toBe(false); // had a denial | |
| 305 | − expect(sideEffects).toEqual(["insert:a", "insert:c"]); | |
| 306 | − expect(result.receipts).toHaveLength(3); | |
| 307 | − expect(result.receipts[0]!.decision).toBe("approved"); | |
| 308 | − expect(result.receipts[1]!.decision).toBe("denied"); | |
| 309 | − expect(result.receipts[2]!.decision).toBe("approved"); | |
| 310 | − }); | |
| 311 | − | |
| 312 | − test("approval request includes formatApproval data", async () => { | |
| 313 | − const sideEffects: string[] = []; | |
| 314 | − let capturedRequest: unknown; | |
| 315 | − const runner = createRunner({ | |
| 316 | − tools: writeTools(sideEffects), | |
| 317 | − requestApproval: async (req) => { | |
| 318 | − capturedRequest = req; | |
| 319 | − return "approved"; | |
| 320 | − }, | |
| 321 | − newCallId: testCallId, | |
| 322 | − }); | |
| 323 | − | |
| 324 | − await runner.run( | |
| 325 | − 'await tools.db.delete({ table: "users", id: "user_42" })', | |
| 326 | − ); | |
| 327 | − | |
| 328 | − expect(capturedRequest).toBeDefined(); | |
| 329 | − const req = capturedRequest as { preview: { title: string } }; | |
| 330 | − expect(req.preview.title).toBe("Delete user_42 from users"); | |
| 331 | − }); | |
| 332 | − | |
| 333 | − test("approval request falls back to default preview when formatApproval is missing", async () => { | |
| 334 | − let capturedRequest: unknown; | |
| 335 | − const tools: ToolTree = { | |
| 336 | − vercel: { | |
| 337 | − projects: { | |
| 338 | − deleteProject: defineTool({ | |
| 339 | − description: "Delete a project", | |
| 340 | − approval: "required", | |
| 341 | − args: z.object({ idOrName: z.string() }), | |
| 342 | − returns: z.object({ ok: z.boolean() }), | |
| 343 | − run: async () => ({ ok: true }), | |
| 344 | − }), | |
| 345 | − }, | |
| 346 | − }, | |
| 347 | − }; | |
| 348 | − | |
| 349 | − const runner = createRunner({ | |
| 350 | − tools, | |
| 351 | − requestApproval: async (req) => { | |
| 352 | − capturedRequest = req; | |
| 353 | − return "denied"; | |
| 354 | − }, | |
| 355 | − newCallId: testCallId, | |
| 356 | − }); | |
| 357 | − | |
| 358 | − await runner.run('await tools.vercel.projects.deleteProject({ idOrName: "prj_123" })'); | |
| 359 | − | |
| 360 | − const req = capturedRequest as { | |
| 361 | − preview: { | |
| 362 | − title: string; | |
| 363 | − details?: string; | |
| 364 | − action?: string; | |
| 365 | − resourceIds?: string[]; | |
| 366 | − isDestructive?: boolean; | |
| 367 | − }; | |
| 368 | − }; | |
| 369 | − expect(req.preview.title).toBe("Delete via vercel.projects.deleteProject"); | |
| 370 | − expect(req.preview.details).toContain("Target: prj_123"); | |
| 371 | − expect(req.preview.action).toBe("delete"); | |
| 372 | − expect(req.preview.resourceIds).toEqual(["prj_123"]); | |
| 373 | − expect(req.preview.isDestructive).toBe(true); | |
| 374 | − }); | |
| 375 | −}); | |
| 376 | − | |
| 377 | −describe("runner — error handling", () => { | |
| 378 | − test("tool denied error is recorded as denied receipt", async () => { | |
| 379 | − const tools: ToolTree = { | |
| 380 | − gated: defineTool({ | |
| 381 | − description: "Denied by policy", | |
| 382 | − approval: "auto", | |
| 383 | − args: z.object({}), | |
| 384 | − returns: z.void(), | |
| 385 | − run: async () => { | |
| 386 | − throw new ToolDeniedError("Denied by broker"); | |
| 387 | − }, | |
| 388 | − }), | |
| 389 | − }; | |
| 390 | − | |
| 391 | − const runner = createRunner({ | |
| 392 | − tools, | |
| 393 | − requestApproval: alwaysApprove(), | |
| 394 | − newCallId: testCallId, | |
| 395 | − }); | |
| 396 | − | |
| 397 | − const result = await runner.run("await tools.gated({})"); | |
| 398 | − expect(result.ok).toBe(false); | |
| 399 | − expect(result.receipts).toHaveLength(1); | |
| 400 | − expect(result.receipts[0]!.status).toBe("denied"); | |
| 401 | − expect(result.receipts[0]!.decision).toBe("denied"); | |
| 402 | − }); | |
| 403 | − | |
| 404 | − test("tool run error is caught and recorded", async () => { | |
| 405 | − const tools: ToolTree = { | |
| 406 | − broken: defineTool({ | |
| 407 | − description: "Always fails", | |
| 408 | − approval: "auto", | |
| 409 | − args: z.object({}), | |
| 410 | − returns: z.void(), | |
| 411 | − run: async () => { | |
| 412 | − throw new Error("Something went wrong"); | |
| 413 | − }, | |
| 414 | − }), | |
| 415 | − }; | |
| 416 | − | |
| 417 | − const runner = createRunner({ | |
| 418 | − tools, | |
| 419 | − requestApproval: alwaysApprove(), | |
| 420 | − newCallId: testCallId, | |
| 421 | − }); | |
| 422 | − | |
| 423 | − const result = await runner.run("await tools.broken({})"); | |
| 424 | − expect(result.ok).toBe(false); | |
| 425 | − expect(result.receipts).toHaveLength(1); | |
| 426 | − expect(result.receipts[0]!.status).toBe("failed"); | |
| 427 | − expect(result.receipts[0]!.error).toBe("Something went wrong"); | |
| 428 | − }); | |
| 429 | − | |
| 430 | − test("syntax error in code is caught", async () => { | |
| 431 | − const runner = createRunner({ | |
| 432 | − tools: mathTools(), | |
| 433 | − requestApproval: alwaysApprove(), | |
| 434 | − newCallId: testCallId, | |
| 435 | − }); | |
| 436 | − | |
| 437 | − const result = await runner.run("const x = {{{"); | |
| 438 | − expect(result.ok).toBe(false); | |
| 439 | − expect(result.error).toBeDefined(); | |
| 440 | − }); | |
| 441 | − | |
| 442 | − test("runtime error in code is caught", async () => { | |
| 443 | − const runner = createRunner({ | |
| 444 | − tools: mathTools(), | |
| 445 | − requestApproval: alwaysApprove(), | |
| 446 | − newCallId: testCallId, | |
| 447 | − }); | |
| 448 | − | |
| 449 | − const result = await runner.run("null.toString()"); | |
| 450 | − expect(result.ok).toBe(false); | |
| 451 | − expect(result.error).toBeDefined(); | |
| 452 | − }); | |
| 453 | −}); | |
| @@ -1,565 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Codemode runner — executes LLM-generated TypeScript in a node:vm sandbox. | |
| 3 | − * | |
| 4 | − * The sandbox exposes only the `tools` object. No fetch, no process, no Bun, | |
| 5 | − * no require, no import. Tool functions are wrapped so toString() doesn't | |
| 6 | − * leak implementation source. | |
| 7 | − */ | |
| 8 | − | |
| 9 | −import { createContext, runInContext } from "node:vm"; | |
| 10 | −import type { | |
| 11 | − ApprovalDecision, | |
| 12 | − ApprovalPresentation, | |
| 13 | − ApprovalRequest, | |
| 14 | − ToolCallReceipt, | |
| 15 | − ToolDefinition, | |
| 16 | − ToolTree, | |
| 17 | −} from "./tools.js"; | |
| 18 | −import { isToolDefinition } from "./tools.js"; | |
| 19 | − | |
| 20 | −// --------------------------------------------------------------------------- | |
| 21 | −// Types | |
| 22 | −// --------------------------------------------------------------------------- | |
| 23 | − | |
| 24 | −export interface RunResult { | |
| 25 | − readonly ok: boolean; | |
| 26 | − readonly value?: unknown; | |
| 27 | − readonly error?: string; | |
| 28 | − readonly receipts: readonly ToolCallReceipt[]; | |
| 29 | −} | |
| 30 | − | |
| 31 | −export interface RunnerOptions { | |
| 32 | − /** The tool tree to expose in the sandbox. */ | |
| 33 | − readonly tools: ToolTree; | |
| 34 | − /** Called when a tool with `approval: "required"` is invoked. */ | |
| 35 | − readonly requestApproval: ( | |
| 36 | − request: ApprovalRequest, | |
| 37 | − ) => Promise<ApprovalDecision>; | |
| 38 | − /** Execution timeout in ms. Defaults to 30_000. */ | |
| 39 | − readonly timeoutMs?: number | undefined; | |
| 40 | − /** Injectable clock for testing. */ | |
| 41 | − readonly now?: (() => number) | undefined; | |
| 42 | − /** Injectable ID generator for testing. */ | |
| 43 | − readonly newCallId?: (() => string) | undefined; | |
| 44 | −} | |
| 45 | − | |
| 46 | −export interface Runner { | |
| 47 | − /** Execute a code string in the sandbox. Never throws. */ | |
| 48 | − run(code: string): Promise<RunResult>; | |
| 49 | −} | |
| 50 | − | |
| 51 | −export class ToolDeniedError extends Error { | |
| 52 | − constructor(message = "Tool call denied") { | |
| 53 | − super(message); | |
| 54 | − this.name = "ToolDeniedError"; | |
| 55 | − } | |
| 56 | −} | |
| 57 | − | |
| 58 | −// --------------------------------------------------------------------------- | |
| 59 | −// Helpers | |
| 60 | −// --------------------------------------------------------------------------- | |
| 61 | − | |
| 62 | −const MAX_PREVIEW = 180; | |
| 63 | −const MAX_APPROVAL_DETAILS = 500; | |
| 64 | − | |
| 65 | −/** | |
| 66 | − * Generate a human-readable preview of a tool's output value. | |
| 67 | − * Instead of raw JSON, extracts meaningful fields and formats them concisely. | |
| 68 | − */ | |
| 69 | −function preview(value: unknown): string { | |
| 70 | − if (value === undefined) return "undefined"; | |
| 71 | − if (value === null) return "null"; | |
| 72 | − if (typeof value === "string") { | |
| 73 | − return value.length > MAX_PREVIEW ? value.slice(0, MAX_PREVIEW) + "..." : value; | |
| 74 | − } | |
| 75 | − if (typeof value === "number" || typeof value === "boolean") return String(value); | |
| 76 | − | |
| 77 | − // Arrays — show count + preview of first item | |
| 78 | − if (Array.isArray(value)) { | |
| 79 | − if (value.length === 0) return "[] (empty)"; | |
| 80 | − const first = previewObject(value[0]); | |
| 81 | − return value.length === 1 ? `[${first}]` : `${value.length} items, first: ${first}`; | |
| 82 | − } | |
| 83 | − | |
| 84 | − // Objects — show key fields concisely | |
| 85 | − if (typeof value === "object" && value !== null) { | |
| 86 | − return previewObject(value); | |
| 87 | − } | |
| 88 | − | |
| 89 | − return String(value); | |
| 90 | −} | |
| 91 | − | |
| 92 | −/** | |
| 93 | − * Preview an object by extracting its most meaningful fields. | |
| 94 | − * Prioritizes fields like name, title, id, status, message, count, total. | |
| 95 | − */ | |
| 96 | −function previewObject(obj: unknown): string { | |
| 97 | − if (!obj || typeof obj !== "object") return String(obj); | |
| 98 | − | |
| 99 | − const record = obj as Record<string, unknown>; | |
| 100 | − const keys = Object.keys(record); | |
| 101 | − | |
| 102 | − // Priority fields — show these first if they exist | |
| 103 | − const priority = ["name", "title", "id", "status", "state", "message", "count", "total", "url", "login", "number"]; | |
| 104 | − const parts: string[] = []; | |
| 105 | − | |
| 106 | − for (const key of priority) { | |
| 107 | − if (key in record && record[key] != null) { | |
| 108 | − const val = record[key]; | |
| 109 | − if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") { | |
| 110 | − parts.push(`${key}: ${val}`); | |
| 111 | − } | |
| 112 | − } | |
| 113 | − if (parts.length >= 3) break; | |
| 114 | − } | |
| 115 | − | |
| 116 | − // If no priority fields matched, show the first few keys | |
| 117 | − if (parts.length === 0) { | |
| 118 | − for (const key of keys.slice(0, 3)) { | |
| 119 | − const val = record[key]; | |
| 120 | − if (val == null) continue; | |
| 121 | − if (typeof val === "string") { | |
| 122 | − parts.push(`${key}: ${val.length > 40 ? val.slice(0, 40) + "..." : val}`); | |
| 123 | − } else if (typeof val === "number" || typeof val === "boolean") { | |
| 124 | − parts.push(`${key}: ${val}`); | |
| 125 | − } else if (Array.isArray(val)) { | |
| 126 | − parts.push(`${key}: [${val.length} items]`); | |
| 127 | − } else { | |
| 128 | − parts.push(`${key}: {...}`); | |
| 129 | − } | |
| 130 | − } | |
| 131 | − } | |
| 132 | − | |
| 133 | − if (parts.length === 0) return `{${keys.length} fields}`; | |
| 134 | − | |
| 135 | − const extra = keys.length > parts.length ? ` +${keys.length - parts.length} more` : ""; | |
| 136 | − const result = parts.join(", ") + extra; | |
| 137 | − return result.length > MAX_PREVIEW ? result.slice(0, MAX_PREVIEW) + "..." : result; | |
| 138 | −} | |
| 139 | − | |
| 140 | −function isRecord(value: unknown): value is Record<string, unknown> { | |
| 141 | − return typeof value === "object" && value !== null && !Array.isArray(value); | |
| 142 | −} | |
| 143 | − | |
| 144 | −function inferApprovalAction(toolPath: string): { | |
| 145 | − action: NonNullable<ApprovalPresentation["action"]>; | |
| 146 | − isDestructive: boolean; | |
| 147 | −} { | |
| 148 | − const lower = toolPath.toLowerCase(); | |
| 149 | − if (/(delete|remove|destroy|purge)/.test(lower)) { | |
| 150 | − return { action: "delete", isDestructive: true }; | |
| 151 | − } | |
| 152 | − if (/(create|add|insert|provision)/.test(lower)) { | |
| 153 | − return { action: "create", isDestructive: false }; | |
| 154 | − } | |
| 155 | − if (/(update|set|patch|edit|rename)/.test(lower)) { | |
| 156 | − return { action: "update", isDestructive: false }; | |
| 157 | − } | |
| 158 | − if (/(get|list|search|find|read)/.test(lower)) { | |
| 159 | − return { action: "read", isDestructive: false }; | |
| 160 | − } | |
| 161 | − return { action: "execute", isDestructive: false }; | |
| 162 | −} | |
| 163 | − | |
| 164 | −function inferResourceType(toolPath: string): string | undefined { | |
| 165 | − const parts = toolPath.split(".").filter(Boolean); | |
| 166 | − const candidate = parts.at(-2) ?? parts.at(-1); | |
| 167 | − if (!candidate) return undefined; | |
| 168 | − return candidate | |
| 169 | − .replace(/_/g, " ") | |
| 170 | − .replace(/\b\w/g, (m) => m.toUpperCase()); | |
| 171 | −} | |
| 172 | − | |
| 173 | −function extractResourceIds(input: unknown): string[] { | |
| 174 | − if (!isRecord(input)) return []; | |
| 175 | − const ids: string[] = []; | |
| 176 | − const candidateKeys = ["id", "ids", "name", "slug", "key", "idOrName"]; | |
| 177 | − for (const key of candidateKeys) { | |
| 178 | − const value = input[key]; | |
| 179 | − if (typeof value === "string" || typeof value === "number") { | |
| 180 | − ids.push(String(value)); | |
| 181 | − continue; | |
| 182 | − } | |
| 183 | − if (Array.isArray(value)) { | |
| 184 | − for (const item of value) { | |
| 185 | − if (typeof item === "string" || typeof item === "number") { | |
| 186 | − ids.push(String(item)); | |
| 187 | − } | |
| 188 | − } | |
| 189 | − } | |
| 190 | − } | |
| 191 | − return ids.slice(0, 5); | |
| 192 | −} | |
| 193 | − | |
| 194 | −function extractCount(input: unknown): number | undefined { | |
| 195 | − if (!isRecord(input)) return undefined; | |
| 196 | − const raw = input["count"]; | |
| 197 | − return typeof raw === "number" && Number.isFinite(raw) ? raw : undefined; | |
| 198 | −} | |
| 199 | − | |
| 200 | −function buildDefaultApprovalPresentation( | |
| 201 | − toolPath: string, | |
| 202 | − input: unknown, | |
| 203 | −): ApprovalPresentation { | |
| 204 | − const { action, isDestructive } = inferApprovalAction(toolPath); | |
| 205 | − const actionVerb = action === "execute" | |
| 206 | − ? "Run" | |
| 207 | − : action.charAt(0).toUpperCase() + action.slice(1); | |
| 208 | − const resourceType = inferResourceType(toolPath); | |
| 209 | − const resourceIds = extractResourceIds(input); | |
| 210 | − const count = extractCount(input); | |
| 211 | − const rawPreview = preview(input); | |
| 212 | − | |
| 213 | − const idDetail = resourceIds.length > 0 ? `Target: ${resourceIds.join(", ")}` : undefined; | |
| 214 | − const argsDetail = `Arguments: ${rawPreview}`; | |
| 215 | − const details = [idDetail, argsDetail].filter(Boolean).join("\n"); | |
| 216 | − | |
| 217 | − return { | |
| 218 | − title: `${actionVerb} via ${toolPath}`, | |
| 219 | − details: details.length > MAX_APPROVAL_DETAILS | |
| 220 | − ? `${details.slice(0, MAX_APPROVAL_DETAILS)}...` | |
| 221 | − : details, | |
| 222 | − action, | |
| 223 | − isDestructive, | |
| 224 | − ...(resourceType ? { resourceType } : {}), | |
| 225 | − ...(resourceIds.length > 0 ? { resourceIds } : {}), | |
| 226 | − ...(count !== undefined ? { count } : {}), | |
| 227 | − }; | |
| 228 | −} | |
| 229 | − | |
| 230 | −function describeError(error: unknown): string { | |
| 231 | − if (error instanceof Error) { | |
| 232 | − // Include cause if present (e.g. nested API errors) | |
| 233 | − const msg = error.message; | |
| 234 | − if (error.cause) { | |
| 235 | − return `${msg} (cause: ${describeError(error.cause)})`; | |
| 236 | − } | |
| 237 | − return msg; | |
| 238 | − } | |
| 239 | − if (typeof error === "string") return error; | |
| 240 | − if (error === null || error === undefined) return String(error); | |
| 241 | − // Try to get a meaningful representation | |
| 242 | − try { | |
| 243 | − const json = JSON.stringify(error, null, 0); | |
| 244 | − // If it's just "{}", try to find something useful | |
| 245 | − if (json === "{}" || json === "[]") { | |
| 246 | − const proto = Object.getPrototypeOf(error); | |
| 247 | − const name = proto?.constructor?.name; | |
| 248 | − return name && name !== "Object" ? `[${name}]` : String(error); | |
| 249 | − } | |
| 250 | − return json; | |
| 251 | − } catch { | |
| 252 | − return String(error); | |
| 253 | − } | |
| 254 | −} | |
| 255 | − | |
| 256 | −let callIdCounter = 0; | |
| 257 | −function defaultNewCallId(): string { | |
| 258 | − return `call_${++callIdCounter}_${Date.now().toString(36)}`; | |
| 259 | −} | |
| 260 | − | |
| 261 | −/** | |
| 262 | − * Wrap a function so toString() doesn't leak the source. | |
| 263 | − */ | |
| 264 | −function wrapFn<T extends (...args: unknown[]) => unknown>(fn: T): T { | |
| 265 | − const wrapped = ((...args: unknown[]) => fn(...args)) as T; | |
| 266 | − Object.defineProperty(wrapped, "toString", { | |
| 267 | − value: () => "function() { [native code] }", | |
| 268 | − configurable: false, | |
| 269 | − writable: false, | |
| 270 | − }); | |
| 271 | − Object.defineProperty(wrapped, "name", { | |
| 272 | − value: fn.name || "tool", | |
| 273 | − configurable: false, | |
| 274 | − writable: false, | |
| 275 | − }); | |
| 276 | − return wrapped; | |
| 277 | −} | |
| 278 | − | |
| 279 | −// --------------------------------------------------------------------------- | |
| 280 | −// Materializer — converts ToolTree to plain JS objects for the sandbox | |
| 281 | −// --------------------------------------------------------------------------- | |
| 282 | − | |
| 283 | −interface MaterializeContext { | |
| 284 | − receipts: ToolCallReceipt[]; | |
| 285 | − requestApproval: RunnerOptions["requestApproval"]; | |
| 286 | − now: () => number; | |
| 287 | − newCallId: () => string; | |
| 288 | −} | |
| 289 | − | |
| 290 | −function materializeToolTree( | |
| 291 | − tree: ToolTree, | |
| 292 | − ctx: MaterializeContext, | |
| 293 | − prefix = "", | |
| 294 | −): Record<string, unknown> { | |
| 295 | − const result: Record<string, unknown> = {}; | |
| 296 | − | |
| 297 | − for (const [key, value] of Object.entries(tree)) { | |
| 298 | − const path = prefix ? `${prefix}.${key}` : key; | |
| 299 | − if (isToolDefinition(value)) { | |
| 300 | − result[key] = createToolInvoker(path, value, ctx); | |
| 301 | − } else { | |
| 302 | − result[key] = materializeToolTree(value as ToolTree, ctx, path); | |
| 303 | − } | |
| 304 | − } | |
| 305 | − | |
| 306 | − return result; | |
| 307 | −} | |
| 308 | − | |
| 309 | −function createToolInvoker( | |
| 310 | − toolPath: string, | |
| 311 | − tool: ToolDefinition, | |
| 312 | − ctx: MaterializeContext, | |
| 313 | −): (...args: unknown[]) => Promise<unknown> { | |
| 314 | − return wrapFn(async (input: unknown) => { | |
| 315 | − const callId = ctx.newCallId(); | |
| 316 | − const timestamp = ctx.now(); | |
| 317 | − const inputPreview = preview(input); | |
| 318 | − | |
| 319 | − // Validate input with Zod | |
| 320 | − const parseResult = tool.args.safeParse(input); | |
| 321 | − if (!parseResult.success) { | |
| 322 | − const errorMsg = `Input validation failed: ${parseResult.error.message}`; | |
| 323 | − ctx.receipts.push({ | |
| 324 | − callId, | |
| 325 | − toolPath, | |
| 326 | − approval: tool.approval, | |
| 327 | − decision: "auto", | |
| 328 | − status: "failed", | |
| 329 | − timestamp, | |
| 330 | − inputPreview, | |
| 331 | − error: errorMsg, | |
| 332 | − }); | |
| 333 | − throw new Error(errorMsg); | |
| 334 | − } | |
| 335 | − | |
| 336 | − const validatedInput = parseResult.data; | |
| 337 | − | |
| 338 | − // Check approval for "required" tools | |
| 339 | − if (tool.approval === "required") { | |
| 340 | − const presentation: ApprovalPresentation = tool.formatApproval | |
| 341 | − ? tool.formatApproval(validatedInput) | |
| 342 | − : buildDefaultApprovalPresentation(toolPath, validatedInput); | |
| 343 | − | |
| 344 | − const decision = await ctx.requestApproval({ | |
| 345 | − callId, | |
| 346 | − toolPath, | |
| 347 | − input: validatedInput, | |
| 348 | − preview: presentation, | |
| 349 | − }); | |
| 350 | − | |
| 351 | − if (decision === "denied") { | |
| 352 | − ctx.receipts.push({ | |
| 353 | − callId, | |
| 354 | − toolPath, | |
| 355 | − approval: "required", | |
| 356 | − decision: "denied", | |
| 357 | − status: "denied", | |
| 358 | − timestamp, | |
| 359 | − inputPreview, | |
| 360 | − }); | |
| 361 | − return undefined; | |
| 362 | − } | |
| 363 | − | |
| 364 | − // Approved — continue to execution | |
| 365 | − try { | |
| 366 | − const output = await tool.run(validatedInput); | |
| 367 | − ctx.receipts.push({ | |
| 368 | − callId, | |
| 369 | − toolPath, | |
| 370 | − approval: "required", | |
| 371 | − decision: "approved", | |
| 372 | − status: "succeeded", | |
| 373 | − timestamp, | |
| 374 | − inputPreview, | |
| 375 | − outputPreview: preview(output), | |
| 376 | − }); | |
| 377 | − return output; | |
| 378 | − } catch (error) { | |
| 379 | − const errorMsg = describeError(error); | |
| 380 | − ctx.receipts.push({ | |
| 381 | − callId, | |
| 382 | − toolPath, | |
| 383 | − approval: "required", | |
| 384 | − decision: "approved", | |
| 385 | − status: "failed", | |
| 386 | − timestamp, | |
| 387 | − inputPreview, | |
| 388 | − error: errorMsg, | |
| 389 | − }); | |
| 390 | − throw error; | |
| 391 | − } | |
| 392 | − } | |
| 393 | − | |
| 394 | − // Auto-approved tool | |
| 395 | − try { | |
| 396 | − const output = await tool.run(validatedInput); | |
| 397 | − ctx.receipts.push({ | |
| 398 | − callId, | |
| 399 | − toolPath, | |
| 400 | − approval: "auto", | |
| 401 | − decision: "auto", | |
| 402 | − status: "succeeded", | |
| 403 | − timestamp, | |
| 404 | − inputPreview, | |
| 405 | − outputPreview: preview(output), | |
| 406 | − }); | |
| 407 | − return output; | |
| 408 | − } catch (error) { | |
| 409 | − if (error instanceof ToolDeniedError) { | |
| 410 | − ctx.receipts.push({ | |
| 411 | − callId, | |
| 412 | − toolPath, | |
| 413 | − approval: "auto", | |
| 414 | − decision: "denied", | |
| 415 | − status: "denied", | |
| 416 | − timestamp, | |
| 417 | − inputPreview, | |
| 418 | − error: error.message, | |
| 419 | − }); | |
| 420 | − return undefined; | |
| 421 | − } | |
| 422 | − const errorMsg = describeError(error); | |
| 423 | − ctx.receipts.push({ | |
| 424 | − callId, | |
| 425 | − toolPath, | |
| 426 | − approval: "auto", | |
| 427 | − decision: "auto", | |
| 428 | − status: "failed", | |
| 429 | − timestamp, | |
| 430 | − inputPreview, | |
| 431 | − error: errorMsg, | |
| 432 | − }); | |
| 433 | − throw error; | |
| 434 | − } | |
| 435 | − }); | |
| 436 | −} | |
| 437 | − | |
| 438 | −// --------------------------------------------------------------------------- | |
| 439 | −// Sandbox | |
| 440 | −// --------------------------------------------------------------------------- | |
| 441 | − | |
| 442 | −function createSandboxContext(tools: Record<string, unknown>): object { | |
| 443 | − // Object.create(null) as base — the VM gets its own built-in globals | |
| 444 | − // (Array, Object, Promise, etc.) but nothing from the host. | |
| 445 | − const ctx = createContext(Object.create(null)); | |
| 446 | − const g: Record<string, unknown> = runInContext("globalThis", ctx); | |
| 447 | − | |
| 448 | − // Inject tools — the only external capability | |
| 449 | − g["tools"] = tools; | |
| 450 | − | |
| 451 | − // No-op console (sandbox already has no console) | |
| 452 | − g["console"] = { | |
| 453 | − log: () => {}, | |
| 454 | − warn: () => {}, | |
| 455 | − error: () => {}, | |
| 456 | − info: () => {}, | |
| 457 | − debug: () => {}, | |
| 458 | − }; | |
| 459 | − | |
| 460 | − // Explicitly block dangerous things with clear error messages. | |
| 461 | − // The VM's own globals (Array, Object, Math, JSON, Date, Promise, etc.) | |
| 462 | − // are already present and safe — they're sandbox-scoped copies. | |
| 463 | − const blocked = (name: string) => { | |
| 464 | − Object.defineProperty(g, name, { | |
| 465 | − get() { | |
| 466 | − throw new Error( | |
| 467 | − `${name} is not available in the sandbox. Use tools.* to interact with external services.`, | |
| 468 | − ); | |
| 469 | − }, | |
| 470 | − configurable: false, | |
| 471 | − }); | |
| 472 | − }; | |
| 473 | − blocked("fetch"); | |
| 474 | − blocked("require"); | |
| 475 | − blocked("process"); | |
| 476 | − blocked("Bun"); | |
| 477 | − blocked("setTimeout"); | |
| 478 | − blocked("setInterval"); | |
| 479 | − | |
| 480 | − return ctx; | |
| 481 | −} | |
| 482 | − | |
| 483 | −// --------------------------------------------------------------------------- | |
| 484 | −// TypeScript → JavaScript transpilation | |
| 485 | −// --------------------------------------------------------------------------- | |
| 486 | − | |
| 487 | −/** | |
| 488 | − * Strip TypeScript syntax (type annotations, `as` casts, interfaces, etc.) | |
| 489 | − * from LLM-generated code so it can run in the `node:vm` sandbox which | |
| 490 | − * only executes JavaScript. | |
| 491 | − * | |
| 492 | − * Uses Bun's built-in transpiler. Falls back to the original code if | |
| 493 | − * transpilation fails (the code might already be valid JS). | |
| 494 | − */ | |
| 495 | −function transpileToJS(code: string): string { | |
| 496 | − try { | |
| 497 | − const transpiler = new Bun.Transpiler({ loader: "ts", target: "browser" }); | |
| 498 | − // Wrap in an async function so `return` and `await` are valid syntax | |
| 499 | − const wrapped = `async function __run(tools: any) {\n${code}\n}`; | |
| 500 | − const jsWrapped = transpiler.transformSync(wrapped); | |
| 501 | − // Extract the function body | |
| 502 | − const bodyStart = jsWrapped.indexOf("{"); | |
| 503 | − const bodyEnd = jsWrapped.lastIndexOf("}"); | |
| 504 | − if (bodyStart === -1 || bodyEnd === -1) return code; | |
| 505 | − return jsWrapped.slice(bodyStart + 1, bodyEnd).trim(); | |
| 506 | − } catch { | |
| 507 | − // If transpilation fails, return original code — it may already be JS | |
| 508 | − return code; | |
| 509 | − } | |
| 510 | −} | |
| 511 | − | |
| 512 | −// --------------------------------------------------------------------------- | |
| 513 | −// Runner | |
| 514 | −// --------------------------------------------------------------------------- | |
| 515 | − | |
| 516 | −export function createRunner(options: RunnerOptions): Runner { | |
| 517 | − const { | |
| 518 | − tools, | |
| 519 | − requestApproval, | |
| 520 | − timeoutMs = 30_000, | |
| 521 | − now = Date.now, | |
| 522 | − newCallId = defaultNewCallId, | |
| 523 | − } = options; | |
| 524 | − | |
| 525 | − return { | |
| 526 | − async run(code: string): Promise<RunResult> { | |
| 527 | − const receipts: ToolCallReceipt[] = []; | |
| 528 | − | |
| 529 | − const materialCtx: MaterializeContext = { | |
| 530 | − receipts, | |
| 531 | − requestApproval, | |
| 532 | − now, | |
| 533 | − newCallId, | |
| 534 | − }; | |
| 535 | − | |
| 536 | − const materializedTools = materializeToolTree(tools, materialCtx); | |
| 537 | − const sandbox = createSandboxContext(materializedTools); | |
| 538 | − | |
| 539 | − try { | |
| 540 | − // Transpile TS → JS (strips type annotations, `as` casts, interfaces, etc.) | |
| 541 | − const jsCode = transpileToJS(code); | |
| 542 | − const wrappedCode = `(async (tools) => {\n"use strict";\n${jsCode}\n})(tools)`; | |
| 543 | − const resultPromise = runInContext(wrappedCode, sandbox, { | |
| 544 | − timeout: timeoutMs, | |
| 545 | − }); | |
| 546 | − | |
| 547 | − // runInContext returns a Promise for async code | |
| 548 | − const value = await resultPromise; | |
| 549 | − | |
| 550 | − const hasDenial = receipts.some((r) => r.status === "denied"); | |
| 551 | − if (hasDenial) { | |
| 552 | − return { ok: false, value, receipts, error: "One or more tool calls were denied" }; | |
| 553 | − } | |
| 554 | − | |
| 555 | − return { ok: true, value, receipts }; | |
| 556 | − } catch (error) { | |
| 557 | − return { | |
| 558 | − ok: false, | |
| 559 | − error: describeError(error), | |
| 560 | − receipts, | |
| 561 | − }; | |
| 562 | − } | |
| 563 | − }, | |
| 564 | − }; | |
| 565 | −} | |
| @@ -1,143 +0,0 @@ | ||
| 1 | −import { describe, test, expect } from "bun:test"; | |
| 2 | −import { z } from "zod"; | |
| 3 | −import { | |
| 4 | − defineTool, | |
| 5 | − isToolDefinition, | |
| 6 | − walkToolTree, | |
| 7 | − mergeToolTrees, | |
| 8 | − type ToolTree, | |
| 9 | −} from "./tools.js"; | |
| 10 | − | |
| 11 | −describe("defineTool", () => { | |
| 12 | − test("creates a tool with _tag", () => { | |
| 13 | − const tool = defineTool({ | |
| 14 | − description: "Add two numbers", | |
| 15 | − approval: "auto", | |
| 16 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 17 | − returns: z.number(), | |
| 18 | − run: async (input) => input.a + input.b, | |
| 19 | − }); | |
| 20 | − | |
| 21 | − expect(tool._tag).toBe("ToolDefinition"); | |
| 22 | − expect(tool.description).toBe("Add two numbers"); | |
| 23 | − expect(tool.approval).toBe("auto"); | |
| 24 | − }); | |
| 25 | − | |
| 26 | − test("run function works", async () => { | |
| 27 | − const tool = defineTool({ | |
| 28 | − description: "Add", | |
| 29 | − approval: "auto", | |
| 30 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 31 | − returns: z.number(), | |
| 32 | − run: async (input) => input.a + input.b, | |
| 33 | − }); | |
| 34 | − | |
| 35 | − expect(await tool.run({ a: 1, b: 2 })).toBe(3); | |
| 36 | − }); | |
| 37 | −}); | |
| 38 | − | |
| 39 | −describe("isToolDefinition", () => { | |
| 40 | − test("identifies tool definitions", () => { | |
| 41 | − const tool = defineTool({ | |
| 42 | − description: "test", | |
| 43 | − approval: "auto", | |
| 44 | − args: z.object({}), | |
| 45 | − returns: z.void(), | |
| 46 | − run: async () => {}, | |
| 47 | − }); | |
| 48 | − | |
| 49 | − expect(isToolDefinition(tool)).toBe(true); | |
| 50 | − expect(isToolDefinition({ _tag: "ToolDefinition" })).toBe(true); | |
| 51 | − expect(isToolDefinition({})).toBe(false); | |
| 52 | − expect(isToolDefinition(null)).toBe(false); | |
| 53 | − expect(isToolDefinition("string")).toBe(false); | |
| 54 | − }); | |
| 55 | −}); | |
| 56 | − | |
| 57 | −describe("walkToolTree", () => { | |
| 58 | − test("walks flat tree", () => { | |
| 59 | − const add = defineTool({ | |
| 60 | − description: "add", | |
| 61 | − approval: "auto", | |
| 62 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 63 | − returns: z.number(), | |
| 64 | − run: async (input) => input.a + input.b, | |
| 65 | − }); | |
| 66 | − | |
| 67 | − const paths: string[] = []; | |
| 68 | − walkToolTree({ add }, (path) => paths.push(path)); | |
| 69 | − | |
| 70 | − expect(paths).toEqual(["add"]); | |
| 71 | − }); | |
| 72 | − | |
| 73 | − test("walks nested tree", () => { | |
| 74 | − const tool = defineTool({ | |
| 75 | − description: "test", | |
| 76 | − approval: "auto", | |
| 77 | − args: z.object({}), | |
| 78 | − returns: z.void(), | |
| 79 | − run: async () => {}, | |
| 80 | − }); | |
| 81 | − | |
| 82 | − const tree: ToolTree = { | |
| 83 | − github: { | |
| 84 | − issues: { | |
| 85 | − list: tool, | |
| 86 | − close: tool, | |
| 87 | − }, | |
| 88 | − }, | |
| 89 | − calendar: { | |
| 90 | − update: tool, | |
| 91 | − }, | |
| 92 | − }; | |
| 93 | − | |
| 94 | − const paths: string[] = []; | |
| 95 | − walkToolTree(tree, (path) => paths.push(path)); | |
| 96 | − | |
| 97 | − expect(paths.sort()).toEqual([ | |
| 98 | − "calendar.update", | |
| 99 | − "github.issues.close", | |
| 100 | − "github.issues.list", | |
| 101 | − ]); | |
| 102 | − }); | |
| 103 | −}); | |
| 104 | − | |
| 105 | −describe("mergeToolTrees", () => { | |
| 106 | − test("merges non-overlapping trees", () => { | |
| 107 | − const tool = defineTool({ | |
| 108 | − description: "test", | |
| 109 | − approval: "auto", | |
| 110 | − args: z.object({}), | |
| 111 | − returns: z.void(), | |
| 112 | − run: async () => {}, | |
| 113 | − }); | |
| 114 | − | |
| 115 | − const a: ToolTree = { github: { list: tool } }; | |
| 116 | − const b: ToolTree = { calendar: { update: tool } }; | |
| 117 | − const merged = mergeToolTrees(a, b); | |
| 118 | − | |
| 119 | − const paths: string[] = []; | |
| 120 | − walkToolTree(merged, (path) => paths.push(path)); | |
| 121 | − | |
| 122 | − expect(paths.sort()).toEqual(["calendar.update", "github.list"]); | |
| 123 | − }); | |
| 124 | − | |
| 125 | − test("merges overlapping sub-trees", () => { | |
| 126 | − const tool = defineTool({ | |
| 127 | − description: "test", | |
| 128 | − approval: "auto", | |
| 129 | − args: z.object({}), | |
| 130 | − returns: z.void(), | |
| 131 | − run: async () => {}, | |
| 132 | − }); | |
| 133 | − | |
| 134 | − const a: ToolTree = { github: { issues: { list: tool } } }; | |
| 135 | − const b: ToolTree = { github: { issues: { close: tool } } }; | |
| 136 | − const merged = mergeToolTrees(a, b); | |
| 137 | − | |
| 138 | − const paths: string[] = []; | |
| 139 | − walkToolTree(merged, (path) => paths.push(path)); | |
| 140 | − | |
| 141 | − expect(paths.sort()).toEqual(["github.issues.close", "github.issues.list"]); | |
| 142 | − }); | |
| 143 | −}); | |
| @@ -1,226 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Tool definition system. | |
| 3 | − * | |
| 4 | − * Tools are the capabilities exposed to LLM-generated code in the sandbox. | |
| 5 | − * They can come from hand-written plugins, MCP servers, or OpenAPI specs — | |
| 6 | − * all produce the same ToolTree + TypeScript declarations. | |
| 7 | − */ | |
| 8 | − | |
| 9 | −import type { z } from "zod"; | |
| 10 | − | |
| 11 | −// --------------------------------------------------------------------------- | |
| 12 | −// Types | |
| 13 | −// --------------------------------------------------------------------------- | |
| 14 | − | |
| 15 | −export type ApprovalMode = "auto" | "required"; | |
| 16 | − | |
| 17 | −export type ApprovalDecision = "approved" | "denied"; | |
| 18 | − | |
| 19 | −export type ReceiptDecision = "auto" | ApprovalDecision; | |
| 20 | − | |
| 21 | −export type ReceiptStatus = "succeeded" | "failed" | "denied"; | |
| 22 | − | |
| 23 | −/** | |
| 24 | − * Presentation data for rendering an approval request in any client. | |
| 25 | − */ | |
| 26 | −export interface ApprovalPresentation { | |
| 27 | − title: string; | |
| 28 | − details?: string; | |
| 29 | − link?: string; | |
| 30 | − /** | |
| 31 | − * Optional structured metadata for policy and richer UIs. | |
| 32 | − * These fields are additive and backward-compatible. | |
| 33 | − */ | |
| 34 | − action?: "create" | "update" | "delete" | "read" | "execute" | "other"; | |
| 35 | − resourceType?: string; | |
| 36 | − resourceIds?: string[]; | |
| 37 | − count?: number; | |
| 38 | − isDestructive?: boolean; | |
| 39 | − selector?: string; | |
| 40 | −} | |
| 41 | − | |
| 42 | −/** | |
| 43 | − * Immutable record of a single tool invocation. | |
| 44 | − */ | |
| 45 | −export interface ToolCallReceipt { | |
| 46 | − readonly callId: string; | |
| 47 | − readonly toolPath: string; | |
| 48 | − readonly approval: ApprovalMode; | |
| 49 | − readonly decision: ReceiptDecision; | |
| 50 | − readonly status: ReceiptStatus; | |
| 51 | − readonly timestamp: number; | |
| 52 | − readonly inputPreview: string; | |
| 53 | − readonly outputPreview?: string; | |
| 54 | − readonly error?: string; | |
| 55 | −} | |
| 56 | − | |
| 57 | −/** | |
| 58 | − * Request sent to the approval callback for "required" tools. | |
| 59 | − */ | |
| 60 | −export interface ApprovalRequest { | |
| 61 | − readonly callId: string; | |
| 62 | − readonly toolPath: string; | |
| 63 | − readonly input: unknown; | |
| 64 | − readonly preview: ApprovalPresentation; | |
| 65 | −} | |
| 66 | − | |
| 67 | −/** | |
| 68 | − * A single tool definition. The public API for tool authors. | |
| 69 | − * | |
| 70 | − * - `args` and `returns` are Zod schemas used for: | |
| 71 | − * - Input validation at invocation time | |
| 72 | − * - TypeScript declaration generation for the typechecker | |
| 73 | − * - LLM prompt guidance generation | |
| 74 | − * | |
| 75 | − * - `metadata.argsType` and `metadata.returnsType` are optional pre-computed | |
| 76 | − * TypeScript type strings. When present, the typechecker and discovery index | |
| 77 | − * use these instead of deriving types from Zod schemas. This is critical for | |
| 78 | − * OpenAPI tools where `returns` is `z.any()` but the spec defines a rich | |
| 79 | − * response schema. | |
| 80 | − * | |
| 81 | − * - `run` is a plain async function (Effect is an internal detail) | |
| 82 | − * | |
| 83 | − * - `formatApproval` provides rich presentation for approval UIs | |
| 84 | − */ | |
| 85 | −export interface ToolDefinition< | |
| 86 | − TArgs extends z.ZodType = z.ZodType, | |
| 87 | − TReturns extends z.ZodType = z.ZodType, | |
| 88 | −> { | |
| 89 | − readonly _tag: "ToolDefinition"; | |
| 90 | − readonly description: string; | |
| 91 | − readonly approval: ApprovalMode; | |
| 92 | − readonly args: TArgs; | |
| 93 | − readonly returns: TReturns; | |
| 94 | − readonly run: (input: z.output<TArgs>) => Promise<z.output<TReturns>>; | |
| 95 | − readonly formatApproval?: (( | |
| 96 | − input: z.output<TArgs>, | |
| 97 | − ) => ApprovalPresentation) | undefined; | |
| 98 | − /** Pre-computed type metadata. Preferred over Zod-derived types when present. */ | |
| 99 | − readonly metadata?: { | |
| 100 | − /** TypeScript type string for the input args (e.g. "{ owner: string; repo: string }") */ | |
| 101 | − readonly argsType?: string | undefined; | |
| 102 | − /** TypeScript type string for the return value (e.g. "{ id: number; title: string }") */ | |
| 103 | − readonly returnsType?: string | undefined; | |
| 104 | − } | undefined; | |
| 105 | −} | |
| 106 | − | |
| 107 | −/** | |
| 108 | − * Recursive tree of tools. Allows nested namespaces like `tools.github.issues.close`. | |
| 109 | − */ | |
| 110 | −export type ToolTree = { | |
| 111 | − readonly [name: string]: ToolTree | ToolDefinition; | |
| 112 | −}; | |
| 113 | − | |
| 114 | −// --------------------------------------------------------------------------- | |
| 115 | −// Constructors | |
| 116 | −// --------------------------------------------------------------------------- | |
| 117 | − | |
| 118 | −export interface DefineToolInput< | |
| 119 | − TArgs extends z.ZodType, | |
| 120 | − TReturns extends z.ZodType, | |
| 121 | −> { | |
| 122 | − description: string; | |
| 123 | − approval: ApprovalMode; | |
| 124 | − args: TArgs; | |
| 125 | − returns: TReturns; | |
| 126 | − run: (input: z.output<TArgs>) => Promise<z.output<TReturns>>; | |
| 127 | − formatApproval?: (input: z.output<TArgs>) => ApprovalPresentation; | |
| 128 | − /** Pre-computed type metadata. Preferred over Zod-derived types when present. */ | |
| 129 | − metadata?: { | |
| 130 | − argsType?: string; | |
| 131 | − returnsType?: string; | |
| 132 | − }; | |
| 133 | −} | |
| 134 | − | |
| 135 | −/** | |
| 136 | − * Create a tool definition. This is the primary public API for tool authors. | |
| 137 | − * | |
| 138 | − * @example | |
| 139 | − * ```ts | |
| 140 | − * const closeTool = defineTool({ | |
| 141 | − * description: "Close a GitHub issue", | |
| 142 | − * approval: "required", | |
| 143 | − * args: z.object({ owner: z.string(), repo: z.string(), issueNumber: z.number() }), | |
| 144 | − * returns: z.object({ number: z.number(), title: z.string(), state: z.string() }), | |
| 145 | − * run: async (input) => { ... }, | |
| 146 | − * formatApproval: (input) => ({ title: `Close ${input.owner}/${input.repo}#${input.issueNumber}` }), | |
| 147 | − * }); | |
| 148 | − * ``` | |
| 149 | − */ | |
| 150 | −export function defineTool< | |
| 151 | − TArgs extends z.ZodType, | |
| 152 | − TReturns extends z.ZodType, | |
| 153 | −>(input: DefineToolInput<TArgs, TReturns>): ToolDefinition<TArgs, TReturns> { | |
| 154 | − return { | |
| 155 | − _tag: "ToolDefinition", | |
| 156 | − description: input.description, | |
| 157 | − approval: input.approval, | |
| 158 | − args: input.args, | |
| 159 | − returns: input.returns, | |
| 160 | − run: input.run, | |
| 161 | − formatApproval: input.formatApproval, | |
| 162 | − metadata: input.metadata, | |
| 163 | − }; | |
| 164 | −} | |
| 165 | − | |
| 166 | −// --------------------------------------------------------------------------- | |
| 167 | −// ToolTree Utilities | |
| 168 | −// --------------------------------------------------------------------------- | |
| 169 | − | |
| 170 | −/** | |
| 171 | − * Check if a value is a ToolDefinition. | |
| 172 | − */ | |
| 173 | −export function isToolDefinition( | |
| 174 | − value: unknown, | |
| 175 | −): value is ToolDefinition { | |
| 176 | − return ( | |
| 177 | − typeof value === "object" && | |
| 178 | − value !== null && | |
| 179 | − "_tag" in value && | |
| 180 | − (value as { _tag: unknown })._tag === "ToolDefinition" | |
| 181 | − ); | |
| 182 | −} | |
| 183 | − | |
| 184 | −/** | |
| 185 | − * Walk a ToolTree and call `fn` for each tool with its dot-separated path. | |
| 186 | − */ | |
| 187 | −export function walkToolTree( | |
| 188 | − tree: ToolTree, | |
| 189 | − fn: (path: string, tool: ToolDefinition) => void, | |
| 190 | − prefix = "", | |
| 191 | −): void { | |
| 192 | − for (const [key, value] of Object.entries(tree)) { | |
| 193 | − const path = prefix ? `${prefix}.${key}` : key; | |
| 194 | − if (isToolDefinition(value)) { | |
| 195 | − fn(path, value); | |
| 196 | − } else { | |
| 197 | − walkToolTree(value as ToolTree, fn, path); | |
| 198 | − } | |
| 199 | − } | |
| 200 | −} | |
| 201 | − | |
| 202 | −/** | |
| 203 | − * Merge multiple ToolTrees into one. Later trees override earlier ones on conflict. | |
| 204 | − */ | |
| 205 | −export function mergeToolTrees(...trees: readonly ToolTree[]): ToolTree { | |
| 206 | − const result: Record<string, ToolTree | ToolDefinition> = {}; | |
| 207 | − for (const tree of trees) { | |
| 208 | − for (const [key, value] of Object.entries(tree)) { | |
| 209 | − const existing = result[key]; | |
| 210 | − if ( | |
| 211 | − existing && | |
| 212 | − !isToolDefinition(existing) && | |
| 213 | − !isToolDefinition(value) | |
| 214 | − ) { | |
| 215 | − // Both are sub-trees — merge recursively | |
| 216 | − result[key] = mergeToolTrees( | |
| 217 | − existing as ToolTree, | |
| 218 | − value as ToolTree, | |
| 219 | − ); | |
| 220 | − } else { | |
| 221 | − result[key] = value; | |
| 222 | − } | |
| 223 | − } | |
| 224 | − } | |
| 225 | − return result; | |
| 226 | −} | |
| @@ -1,160 +0,0 @@ | ||
| 1 | −import { describe, test, expect } from "bun:test"; | |
| 2 | −import { z } from "zod"; | |
| 3 | −import { defineTool, type ToolTree } from "./tools.js"; | |
| 4 | −import { | |
| 5 | − zodToTypeString, | |
| 6 | − generateToolDeclarations, | |
| 7 | − generatePromptGuidance, | |
| 8 | − typecheckCode, | |
| 9 | −} from "./typechecker.js"; | |
| 10 | − | |
| 11 | −describe("zodToTypeString", () => { | |
| 12 | − test("primitives", () => { | |
| 13 | − expect(zodToTypeString(z.string())).toBe("string"); | |
| 14 | − expect(zodToTypeString(z.number())).toBe("number"); | |
| 15 | − expect(zodToTypeString(z.boolean())).toBe("boolean"); | |
| 16 | − expect(zodToTypeString(z.void())).toBe("void"); | |
| 17 | − expect(zodToTypeString(z.null())).toBe("null"); | |
| 18 | − expect(zodToTypeString(z.undefined())).toBe("undefined"); | |
| 19 | − }); | |
| 20 | − | |
| 21 | − test("objects", () => { | |
| 22 | − const schema = z.object({ name: z.string(), age: z.number() }); | |
| 23 | − expect(zodToTypeString(schema)).toBe("{ name: string; age: number }"); | |
| 24 | − }); | |
| 25 | − | |
| 26 | − test("optional fields", () => { | |
| 27 | − const schema = z.object({ name: z.string(), age: z.number().optional() }); | |
| 28 | − const result = zodToTypeString(schema); | |
| 29 | − expect(result).toContain("name: string"); | |
| 30 | − expect(result).toContain("age?:"); | |
| 31 | − }); | |
| 32 | − | |
| 33 | − test("arrays", () => { | |
| 34 | − const schema = z.array(z.string()); | |
| 35 | − expect(zodToTypeString(schema)).toBe("Array<string>"); | |
| 36 | − }); | |
| 37 | − | |
| 38 | − test("nested objects", () => { | |
| 39 | − const schema = z.object({ | |
| 40 | − user: z.object({ name: z.string() }), | |
| 41 | − }); | |
| 42 | − expect(zodToTypeString(schema)).toBe("{ user: { name: string } }"); | |
| 43 | − }); | |
| 44 | − | |
| 45 | − test("enums", () => { | |
| 46 | − const schema = z.enum(["a", "b", "c"]); | |
| 47 | − expect(zodToTypeString(schema)).toBe('"a" | "b" | "c"'); | |
| 48 | − }); | |
| 49 | − | |
| 50 | − test("records", () => { | |
| 51 | − const schema = z.record(z.string(), z.number()); | |
| 52 | − expect(zodToTypeString(schema)).toBe("Record<string, number>"); | |
| 53 | − }); | |
| 54 | −}); | |
| 55 | − | |
| 56 | −describe("generateToolDeclarations", () => { | |
| 57 | − test("generates declarations for a tool tree", () => { | |
| 58 | − const tree: ToolTree = { | |
| 59 | − math: { | |
| 60 | − add: defineTool({ | |
| 61 | − description: "Add two numbers", | |
| 62 | − approval: "auto", | |
| 63 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 64 | − returns: z.number(), | |
| 65 | − run: async (input) => input.a + input.b, | |
| 66 | − }), | |
| 67 | − }, | |
| 68 | − github: { | |
| 69 | − issues: { | |
| 70 | − close: defineTool({ | |
| 71 | − description: "Close an issue", | |
| 72 | − approval: "required", | |
| 73 | − args: z.object({ owner: z.string(), repo: z.string(), issueNumber: z.number() }), | |
| 74 | − returns: z.object({ state: z.string() }), | |
| 75 | − run: async () => ({ state: "closed" }), | |
| 76 | − }), | |
| 77 | − }, | |
| 78 | − }, | |
| 79 | − }; | |
| 80 | − | |
| 81 | − const declarations = generateToolDeclarations(tree); | |
| 82 | − | |
| 83 | − expect(declarations).toContain("declare const tools:"); | |
| 84 | − expect(declarations).toContain("add(input: { a: number; b: number }): Promise<number>;"); | |
| 85 | − expect(declarations).toContain("close(input: { owner: string; repo: string; issueNumber: number }): Promise<{ state: string }>;"); | |
| 86 | − }); | |
| 87 | −}); | |
| 88 | − | |
| 89 | −describe("generatePromptGuidance", () => { | |
| 90 | − test("generates guidance for tools", () => { | |
| 91 | − const tree: ToolTree = { | |
| 92 | − math: { | |
| 93 | − add: defineTool({ | |
| 94 | − description: "Add two numbers", | |
| 95 | − approval: "auto", | |
| 96 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 97 | − returns: z.number(), | |
| 98 | − run: async (input) => input.a + input.b, | |
| 99 | − }), | |
| 100 | − }, | |
| 101 | − }; | |
| 102 | − | |
| 103 | − const guidance = generatePromptGuidance(tree); | |
| 104 | − expect(guidance).toContain("tools.math.add"); | |
| 105 | − expect(guidance).toContain("Add two numbers"); | |
| 106 | − expect(guidance).toContain("auto-approved"); | |
| 107 | − }); | |
| 108 | −}); | |
| 109 | − | |
| 110 | −describe("typecheckCode", () => { | |
| 111 | − const tree: ToolTree = { | |
| 112 | − math: { | |
| 113 | − add: defineTool({ | |
| 114 | − description: "Add two numbers", | |
| 115 | − approval: "auto", | |
| 116 | − args: z.object({ a: z.number(), b: z.number() }), | |
| 117 | − returns: z.number(), | |
| 118 | − run: async (input) => input.a + input.b, | |
| 119 | − }), | |
| 120 | − }, | |
| 121 | − }; | |
| 122 | − | |
| 123 | − const declarations = generateToolDeclarations(tree); | |
| 124 | − | |
| 125 | − test("valid code passes", () => { | |
| 126 | − const result = typecheckCode( | |
| 127 | − 'const result = await tools.math.add({ a: 1, b: 2 });\nreturn result;', | |
| 128 | − declarations, | |
| 129 | − ); | |
| 130 | − expect(result.ok).toBe(true); | |
| 131 | − expect(result.errors).toHaveLength(0); | |
| 132 | − }); | |
| 133 | − | |
| 134 | − test("wrong argument type fails", () => { | |
| 135 | − const result = typecheckCode( | |
| 136 | − 'const result = await tools.math.add({ a: "not a number", b: 2 });', | |
| 137 | − declarations, | |
| 138 | − ); | |
| 139 | − expect(result.ok).toBe(false); | |
| 140 | − expect(result.errors.length).toBeGreaterThan(0); | |
| 141 | − }); | |
| 142 | − | |
| 143 | − test("non-existent tool fails", () => { | |
| 144 | − const result = typecheckCode( | |
| 145 | − "const result = await tools.math.subtract({ a: 1, b: 2 });", | |
| 146 | − declarations, | |
| 147 | − ); | |
| 148 | − expect(result.ok).toBe(false); | |
| 149 | − expect(result.errors.length).toBeGreaterThan(0); | |
| 150 | − }); | |
| 151 | − | |
| 152 | − test("non-existent namespace fails", () => { | |
| 153 | − const result = typecheckCode( | |
| 154 | − 'const result = await tools.calendar.create({ title: "test" });', | |
| 155 | − declarations, | |
| 156 | − ); | |
| 157 | − expect(result.ok).toBe(false); | |
| 158 | − expect(result.errors.length).toBeGreaterThan(0); | |
| 159 | − }); | |
| 160 | −}); | |
| @@ -1,286 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * TypeScript typechecker for LLM-generated code. | |
| 3 | − * | |
| 4 | − * Takes a ToolTree, generates TypeScript declarations from Zod schemas, | |
| 5 | − * and validates code strings against them using the TypeScript compiler API. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import type { z } from "zod"; | |
| 9 | −import type { ToolDefinition, ToolTree } from "./tools.js"; | |
| 10 | −import { isToolDefinition } from "./tools.js"; | |
| 11 | − | |
| 12 | −// --------------------------------------------------------------------------- | |
| 13 | −// Zod → TypeScript declaration generation | |
| 14 | −// --------------------------------------------------------------------------- | |
| 15 | − | |
| 16 | −// Zod v4 internal def accessor | |
| 17 | −interface ZodDef { | |
| 18 | − type: string; | |
| 19 | − // object | |
| 20 | − shape?: Record<string, z.ZodType>; | |
| 21 | − // array | |
| 22 | − element?: z.ZodType; | |
| 23 | − // optional / nullable | |
| 24 | − innerType?: z.ZodType; | |
| 25 | − // record | |
| 26 | − keyType?: z.ZodType; | |
| 27 | − valueType?: z.ZodType; | |
| 28 | − // union | |
| 29 | − options?: z.ZodType[]; | |
| 30 | − // enum (v4 uses entries: { a: "a", b: "b" }) | |
| 31 | − entries?: Record<string, string>; | |
| 32 | − // literal | |
| 33 | − value?: unknown; | |
| 34 | − // tuple | |
| 35 | − items?: z.ZodType[]; | |
| 36 | −} | |
| 37 | − | |
| 38 | −function getDef(schema: z.ZodType): ZodDef | undefined { | |
| 39 | − // Zod v4: schema._zod.def | |
| 40 | − const zod = (schema as unknown as { _zod?: { def?: ZodDef } })._zod; | |
| 41 | − return zod?.def; | |
| 42 | −} | |
| 43 | − | |
| 44 | −/** | |
| 45 | − * Convert a Zod schema to a TypeScript type string. | |
| 46 | − * Handles the common cases for tool input/output types. | |
| 47 | − * Compatible with Zod v4. | |
| 48 | − */ | |
| 49 | −export function zodToTypeString(schema: z.ZodType): string { | |
| 50 | − const def = getDef(schema); | |
| 51 | − if (!def) return "unknown"; | |
| 52 | − | |
| 53 | − switch (def.type) { | |
| 54 | − case "string": | |
| 55 | − return "string"; | |
| 56 | − case "number": | |
| 57 | − return "number"; | |
| 58 | − case "boolean": | |
| 59 | − return "boolean"; | |
| 60 | − case "null": | |
| 61 | − return "null"; | |
| 62 | − case "undefined": | |
| 63 | − return "undefined"; | |
| 64 | − case "void": | |
| 65 | − return "void"; | |
| 66 | − case "any": | |
| 67 | − return "any"; | |
| 68 | − case "unknown": | |
| 69 | − return "unknown"; | |
| 70 | − case "never": | |
| 71 | − return "never"; | |
| 72 | − case "literal": { | |
| 73 | − const value = def.value; | |
| 74 | − return typeof value === "string" ? `"${value}"` : String(value); | |
| 75 | − } | |
| 76 | − case "array": { | |
| 77 | − if (!def.element) return "Array<unknown>"; | |
| 78 | − return `Array<${zodToTypeString(def.element)}>`; | |
| 79 | − } | |
| 80 | − case "object": { | |
| 81 | − const shape = def.shape; | |
| 82 | − if (!shape || Object.keys(shape).length === 0) return "{}"; | |
| 83 | − | |
| 84 | − const entries = Object.entries(shape).map(([key, value]) => { | |
| 85 | − const innerDef = getDef(value); | |
| 86 | − const isOptional = innerDef?.type === "optional"; | |
| 87 | − const innerType = isOptional && innerDef?.innerType ? innerDef.innerType : value; | |
| 88 | − const optMark = isOptional ? "?" : ""; | |
| 89 | − return `${key}${optMark}: ${zodToTypeString(innerType)}`; | |
| 90 | − }); | |
| 91 | − return `{ ${entries.join("; ")} }`; | |
| 92 | − } | |
| 93 | − case "record": { | |
| 94 | − const keyType = def.keyType ? zodToTypeString(def.keyType) : "string"; | |
| 95 | − const valueType = def.valueType ? zodToTypeString(def.valueType) : "unknown"; | |
| 96 | − return `Record<${keyType}, ${valueType}>`; | |
| 97 | − } | |
| 98 | − case "union": { | |
| 99 | − if (!def.options) return "unknown"; | |
| 100 | − return def.options.map((o) => zodToTypeString(o)).join(" | "); | |
| 101 | − } | |
| 102 | − case "optional": { | |
| 103 | − if (!def.innerType) return "unknown | undefined"; | |
| 104 | − return `${zodToTypeString(def.innerType)} | undefined`; | |
| 105 | − } | |
| 106 | − case "nullable": { | |
| 107 | − if (!def.innerType) return "unknown | null"; | |
| 108 | − return `${zodToTypeString(def.innerType)} | null`; | |
| 109 | − } | |
| 110 | − case "enum": { | |
| 111 | − if (!def.entries) return "unknown"; | |
| 112 | − return Object.keys(def.entries).map((v) => `"${v}"`).join(" | "); | |
| 113 | − } | |
| 114 | − case "tuple": { | |
| 115 | − if (!def.items) return "[]"; | |
| 116 | − return `[${def.items.map((i) => zodToTypeString(i)).join(", ")}]`; | |
| 117 | − } | |
| 118 | − case "promise": { | |
| 119 | − // Zod v4 may use 'innerType' or similar for promise | |
| 120 | − if (!def.innerType) return "Promise<unknown>"; | |
| 121 | − return `Promise<${zodToTypeString(def.innerType)}>`; | |
| 122 | − } | |
| 123 | − default: | |
| 124 | − return "unknown"; | |
| 125 | − } | |
| 126 | −} | |
| 127 | − | |
| 128 | −// --------------------------------------------------------------------------- | |
| 129 | −// Type string resolution | |
| 130 | −// --------------------------------------------------------------------------- | |
| 131 | − | |
| 132 | −/** | |
| 133 | − * Get the TypeScript type string for a tool's args. | |
| 134 | − * Prefers metadata.argsType when available, falls back to Zod-derived type. | |
| 135 | − */ | |
| 136 | −export function getArgsTypeString(tool: ToolDefinition): string { | |
| 137 | − return tool.metadata?.argsType ?? zodToTypeString(tool.args); | |
| 138 | −} | |
| 139 | − | |
| 140 | −/** | |
| 141 | − * Get the TypeScript type string for a tool's return value. | |
| 142 | − * Prefers metadata.returnsType when available, falls back to Zod-derived type. | |
| 143 | − */ | |
| 144 | −export function getReturnsTypeString(tool: ToolDefinition): string { | |
| 145 | − return tool.metadata?.returnsType ?? zodToTypeString(tool.returns); | |
| 146 | −} | |
| 147 | − | |
| 148 | −// --------------------------------------------------------------------------- | |
| 149 | −// ToolTree → TypeScript declaration | |
| 150 | −// --------------------------------------------------------------------------- | |
| 151 | − | |
| 152 | −/** | |
| 153 | − * Generate a TypeScript declaration string for a ToolTree. | |
| 154 | − * This is what goes into the typechecker to validate LLM-generated code. | |
| 155 | − */ | |
| 156 | −export function generateToolDeclarations(tree: ToolTree): string { | |
| 157 | − return `declare const tools: {\n${generateTreeType(tree, 1)}\n};`; | |
| 158 | −} | |
| 159 | − | |
| 160 | −function generateTreeType(tree: ToolTree, indent: number): string { | |
| 161 | − const pad = " ".repeat(indent); | |
| 162 | − const lines: string[] = []; | |
| 163 | − | |
| 164 | − for (const [key, value] of Object.entries(tree)) { | |
| 165 | − if (isToolDefinition(value)) { | |
| 166 | − const tool = value as ToolDefinition; | |
| 167 | − const argsType = getArgsTypeString(tool); | |
| 168 | − const returnsType = getReturnsTypeString(tool); | |
| 169 | − lines.push(`${pad}${key}(input: ${argsType}): Promise<${returnsType}>;`); | |
| 170 | − } else { | |
| 171 | − lines.push(`${pad}${key}: {`); | |
| 172 | − lines.push(generateTreeType(value as ToolTree, indent + 1)); | |
| 173 | − lines.push(`${pad}};`); | |
| 174 | − } | |
| 175 | − } | |
| 176 | − | |
| 177 | − return lines.join("\n"); | |
| 178 | −} | |
| 179 | − | |
| 180 | −// --------------------------------------------------------------------------- | |
| 181 | −// Prompt guidance generation | |
| 182 | −// --------------------------------------------------------------------------- | |
| 183 | − | |
| 184 | −/** | |
| 185 | − * Generate LLM prompt guidance from a ToolTree. | |
| 186 | − * Describes each tool with its path, description, and signature. | |
| 187 | − */ | |
| 188 | −export function generatePromptGuidance(tree: ToolTree): string { | |
| 189 | − const lines: string[] = []; | |
| 190 | − | |
| 191 | − function walk(node: ToolTree, prefix: string) { | |
| 192 | − for (const [key, value] of Object.entries(node)) { | |
| 193 | − const path = prefix ? `${prefix}.${key}` : key; | |
| 194 | − if (isToolDefinition(value)) { | |
| 195 | − const tool = value as ToolDefinition; | |
| 196 | − const argsType = getArgsTypeString(tool); | |
| 197 | − const returnsType = getReturnsTypeString(tool); | |
| 198 | − const approvalNote = | |
| 199 | − tool.approval === "required" ? " (approval required)" : " (auto-approved)"; | |
| 200 | − lines.push( | |
| 201 | − `- tools.${path}(${argsType}): Promise<${returnsType}>${approvalNote} — ${tool.description}`, | |
| 202 | − ); | |
| 203 | − } else { | |
| 204 | − walk(value as ToolTree, path); | |
| 205 | − } | |
| 206 | − } | |
| 207 | − } | |
| 208 | − | |
| 209 | − walk(tree, ""); | |
| 210 | − return lines.join("\n"); | |
| 211 | −} | |
| 212 | − | |
| 213 | −// --------------------------------------------------------------------------- | |
| 214 | −// TypeScript typechecking | |
| 215 | −// --------------------------------------------------------------------------- | |
| 216 | − | |
| 217 | −export interface TypecheckResult { | |
| 218 | − readonly ok: boolean; | |
| 219 | − readonly errors: readonly string[]; | |
| 220 | −} | |
| 221 | − | |
| 222 | −/** | |
| 223 | − * Typecheck a code snippet against tool declarations using the TypeScript compiler. | |
| 224 | − */ | |
| 225 | −export function typecheckCode( | |
| 226 | − code: string, | |
| 227 | − toolDeclarations: string, | |
| 228 | −): TypecheckResult { | |
| 229 | − // We use the TypeScript compiler API directly | |
| 230 | − let ts: typeof import("typescript"); | |
| 231 | − try { | |
| 232 | − ts = require("typescript"); | |
| 233 | − } catch { | |
| 234 | − // If typescript isn't available, skip typechecking | |
| 235 | − return { ok: true, errors: [] }; | |
| 236 | − } | |
| 237 | − | |
| 238 | − // Use a separate type alias to avoid "referenced in its own annotation" error | |
| 239 | − const wrappedCode = `${toolDeclarations}\ntype __Tools = typeof tools;\nasync function __generated(__tools: __Tools) {\nconst tools = __tools;\n${code}\n}`; | |
| 240 | − | |
| 241 | − const sourceFile = ts.createSourceFile( | |
| 242 | − "generated.ts", | |
| 243 | − wrappedCode, | |
| 244 | − ts.ScriptTarget.ESNext, | |
| 245 | − true, | |
| 246 | − ts.ScriptKind.TS, | |
| 247 | − ); | |
| 248 | − | |
| 249 | − const compilerOptions: import("typescript").CompilerOptions = { | |
| 250 | − target: ts.ScriptTarget.ESNext, | |
| 251 | − module: ts.ModuleKind.ESNext, | |
| 252 | − strict: true, | |
| 253 | − noEmit: true, | |
| 254 | − lib: ["lib.es2022.d.ts"], | |
| 255 | − }; | |
| 256 | − | |
| 257 | − // Create a minimal compiler host | |
| 258 | − const host = ts.createCompilerHost(compilerOptions); | |
| 259 | − const originalGetSourceFile = host.getSourceFile.bind(host); | |
| 260 | − host.getSourceFile = (fileName, languageVersion) => { | |
| 261 | − if (fileName === "generated.ts") { | |
| 262 | − return sourceFile; | |
| 263 | − } | |
| 264 | − return originalGetSourceFile(fileName, languageVersion); | |
| 265 | − }; | |
| 266 | − | |
| 267 | − const program = ts.createProgram(["generated.ts"], compilerOptions, host); | |
| 268 | − const diagnostics = program.getSemanticDiagnostics(sourceFile); | |
| 269 | − | |
| 270 | − if (diagnostics.length === 0) { | |
| 271 | − return { ok: true, errors: [] }; | |
| 272 | − } | |
| 273 | − | |
| 274 | − const errors = diagnostics.map((d) => { | |
| 275 | − const message = ts.flattenDiagnosticMessageText(d.messageText, "\n"); | |
| 276 | − if (d.start !== undefined && d.file) { | |
| 277 | − const { line } = d.file.getLineAndCharacterOfPosition(d.start); | |
| 278 | − // Adjust line number to account for the declarations wrapper | |
| 279 | − const declLines = toolDeclarations.split("\n").length + 2; // +2 for blank line + function header | |
| 280 | − return `Line ${line + 1 - declLines}: ${message}`; | |
| 281 | − } | |
| 282 | − return message; | |
| 283 | − }); | |
| 284 | − | |
| 285 | − return { ok: false, errors }; | |
| 286 | −} | |
| @@ -1,11 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "extends": "../../tsconfig.base.json", | |
| 3 | − "compilerOptions": { | |
| 4 | − "rootDir": "src", | |
| 5 | − "outDir": "dist", | |
| 6 | − "composite": true | |
| 7 | − }, | |
| 8 | − "include": [ | |
| 9 | − "src/**/*.ts" | |
| 10 | − ] | |
| 11 | −} | |
| @@ -1,24 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "name": "@openassistant/tool-gen", | |
| 3 | − "private": true, | |
| 4 | − "type": "module", | |
| 5 | − "exports": { | |
| 6 | − ".": "./src/index.ts", | |
| 7 | − "./mcp": "./src/mcp.ts", | |
| 8 | − "./openapi": "./src/openapi.ts" | |
| 9 | − }, | |
| 10 | − "scripts": { | |
| 11 | − "test": "bun test", | |
| 12 | − "typecheck": "tsgo --noEmit --incremental false -p tsconfig.json" | |
| 13 | − }, | |
| 14 | − "dependencies": { | |
| 15 | − "@apidevtools/swagger-parser": "^12.1.0", | |
| 16 | − "@mariozechner/pi-ai": "^0.52.6", | |
| 17 | − "@modelcontextprotocol/sdk": "^1.26.0", | |
| 18 | − "@openassistant/core": "workspace:*", | |
| 19 | − "@sinclair/typebox": "^0.34.48", | |
| 20 | − "openapi-typescript": "^7.10.1", | |
| 21 | − "yaml": "^2.8.2", | |
| 22 | − "zod": "^4.3.6" | |
| 23 | − } | |
| 24 | −} | |
| @@ -1,109 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * End-to-end test: Real Claude → generates code → typechecks → runs in sandbox → calls real MCP tools. | |
| 3 | − * | |
| 4 | − * This test uses: | |
| 5 | − * - pi-ai with Claude (via ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY) | |
| 6 | − * - MCP tools from AnswerOverflow | |
| 7 | − * - The full agent loop: prompt → Claude → run_code → typecheck → sandbox → receipts → response | |
| 8 | − * | |
| 9 | − * Requires: ANTHROPIC_OAUTH_TOKEN or ANTHROPIC_API_KEY env var to be set. | |
| 10 | − */ | |
| 11 | − | |
| 12 | −import { describe, test, expect } from "bun:test"; | |
| 13 | −import { createAgent, createPiAiModel, type TaskEvent } from "@openassistant/core"; | |
| 14 | −import { generateMcpTools } from "./mcp.js"; | |
| 15 | − | |
| 16 | −/** | |
| 17 | − * Resolve an Anthropic API key from (in priority order): | |
| 18 | − * 1. ANTHROPIC_OAUTH_TOKEN env var | |
| 19 | − * 2. ANTHROPIC_API_KEY env var | |
| 20 | − * 3. Claude Code's credential file (~/.claude/.credentials.json) | |
| 21 | − */ | |
| 22 | −function getAnthropicApiKey(): string | undefined { | |
| 23 | − if (process.env["ANTHROPIC_OAUTH_TOKEN"]) return process.env["ANTHROPIC_OAUTH_TOKEN"]; | |
| 24 | − if (process.env["ANTHROPIC_API_KEY"]) return process.env["ANTHROPIC_API_KEY"]; | |
| 25 | − | |
| 26 | − // Try Claude Code's credential store | |
| 27 | − try { | |
| 28 | − const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? ""; | |
| 29 | − const text = require("fs").readFileSync(`${home}/.claude/.credentials.json`, "utf-8"); | |
| 30 | − const creds = JSON.parse(text); | |
| 31 | − const token = creds?.claudeAiOauth?.accessToken; | |
| 32 | − if (typeof token === "string" && token.startsWith("sk-ant-")) { | |
| 33 | − return token; | |
| 34 | − } | |
| 35 | − } catch { | |
| 36 | − // No credentials file | |
| 37 | − } | |
| 38 | − | |
| 39 | − return undefined; | |
| 40 | −} | |
| 41 | − | |
| 42 | −const apiKey = getAnthropicApiKey(); | |
| 43 | −const hasApiKey = !!apiKey; | |
| 44 | − | |
| 45 | −describe.skipIf(!hasApiKey)("e2e — Claude + MCP + sandbox", () => { | |
| 46 | − test("agent generates code to search AnswerOverflow and returns results", async () => { | |
| 47 | − // 1. Generate tools from AnswerOverflow MCP | |
| 48 | − const mcpResult = await generateMcpTools({ | |
| 49 | − name: "answeroverflow", | |
| 50 | − url: "https://www.answeroverflow.com/mcp", | |
| 51 | − }); | |
| 52 | − | |
| 53 | − try { | |
| 54 | − // 2. Create real Claude model via pi-ai | |
| 55 | − const model = createPiAiModel({ | |
| 56 | − provider: "anthropic", | |
| 57 | − modelId: "claude-sonnet-4-5", | |
| 58 | − apiKey, | |
| 59 | − }); | |
| 60 | − | |
| 61 | − // 3. Collect events | |
| 62 | − const events: TaskEvent[] = []; | |
| 63 | − | |
| 64 | − // 4. Create agent with real tools + real model | |
| 65 | − const agent = createAgent({ | |
| 66 | − tools: mcpResult.tools, | |
| 67 | − model, | |
| 68 | − requestApproval: async () => "approved", | |
| 69 | − onEvent: (e) => events.push(e), | |
| 70 | − maxCodeRuns: 3, | |
| 71 | − timeoutMs: 20_000, | |
| 72 | − }); | |
| 73 | − | |
| 74 | − // 5. Run a real prompt | |
| 75 | − const result = await agent.run( | |
| 76 | − "Search AnswerOverflow for 'react hooks' and tell me how many results you found.", | |
| 77 | − ); | |
| 78 | − | |
| 79 | − // 6. Verify the agent actually ran code | |
| 80 | − expect(result.runs.length).toBeGreaterThan(0); | |
| 81 | − expect(result.runs[0]!.result.ok).toBe(true); | |
| 82 | − | |
| 83 | − // 7. Verify MCP tool was called | |
| 84 | − expect(result.allReceipts.length).toBeGreaterThan(0); | |
| 85 | − const mcpReceipt = result.allReceipts.find( | |
| 86 | − (r) => r.toolPath === "answeroverflow.search_answeroverflow", | |
| 87 | − ); | |
| 88 | − expect(mcpReceipt).toBeDefined(); | |
| 89 | − expect(mcpReceipt!.status).toBe("succeeded"); | |
| 90 | − | |
| 91 | − // 8. Verify Claude produced a final text response | |
| 92 | − expect(result.text).toBeDefined(); | |
| 93 | − expect(result.text.length).toBeGreaterThan(0); | |
| 94 | − | |
| 95 | − // 9. Verify events were emitted | |
| 96 | − const eventTypes = events.map((e) => e.type); | |
| 97 | − expect(eventTypes).toContain("status"); | |
| 98 | − expect(eventTypes).toContain("code_generated"); | |
| 99 | − expect(eventTypes).toContain("tool_result"); | |
| 100 | − expect(eventTypes).toContain("agent_message"); | |
| 101 | − expect(eventTypes).toContain("completed"); | |
| 102 | − | |
| 103 | − console.log("Agent response:", result.text); | |
| 104 | − console.log("Receipts:", result.allReceipts.map((r) => `${r.toolPath}: ${r.status}`)); | |
| 105 | − } finally { | |
| 106 | − await mcpResult.close(); | |
| 107 | − } | |
| 108 | − }, { timeout: 60_000 }); | |
| 109 | −}); | |
| @@ -1,18 +0,0 @@ | ||
| 1 | −export { | |
| 2 | − generateMcpTools, | |
| 3 | − type McpToolSource, | |
| 4 | − type McpGenerateResult, | |
| 5 | −} from "./mcp.js"; | |
| 6 | − | |
| 7 | −export { | |
| 8 | − generateOpenApiTools, | |
| 9 | − type OpenApiToolSource, | |
| 10 | − type OpenApiAuth, | |
| 11 | − type OpenApiGenerateResult, | |
| 12 | −} from "./openapi.js"; | |
| 13 | − | |
| 14 | −export { | |
| 15 | − jsonSchemaToTypeString, | |
| 16 | − jsonSchemaToZod, | |
| 17 | − type JsonSchema, | |
| 18 | −} from "./json-schema-to-ts.js"; | |
| @@ -1,105 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Integration test: MCP tools → runner sandbox execution. | |
| 3 | − * | |
| 4 | − * Generates tools from a real MCP server, wires them into the runner, | |
| 5 | − * and executes code in the sandbox that calls the MCP tools. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import { describe, test, expect } from "bun:test"; | |
| 9 | −import { | |
| 10 | − createRunner, | |
| 11 | − generateToolDeclarations, | |
| 12 | − typecheckCode, | |
| 13 | −} from "@openassistant/core"; | |
| 14 | −import { generateMcpTools } from "./mcp.js"; | |
| 15 | − | |
| 16 | −describe("MCP → runner integration", () => { | |
| 17 | − test("generated MCP tools are callable from the sandbox", async () => { | |
| 18 | − // 1. Generate tools from the real AnswerOverflow MCP server | |
| 19 | − const mcpResult = await generateMcpTools({ | |
| 20 | − name: "answeroverflow", | |
| 21 | − url: "https://www.answeroverflow.com/mcp", | |
| 22 | − }); | |
| 23 | − | |
| 24 | − try { | |
| 25 | − // 2. Create a runner with the generated tools | |
| 26 | − const runner = createRunner({ | |
| 27 | − tools: mcpResult.tools, | |
| 28 | − requestApproval: async () => "approved", | |
| 29 | − timeoutMs: 15_000, | |
| 30 | − }); | |
| 31 | − | |
| 32 | − // 3. Execute code in the sandbox that calls the MCP tool | |
| 33 | − const result = await runner.run( | |
| 34 | − `const servers = await tools.answeroverflow.search_servers({ query: "discord" });\nreturn servers;`, | |
| 35 | − ); | |
| 36 | − | |
| 37 | − // 4. Verify it worked | |
| 38 | − expect(result.ok).toBe(true); | |
| 39 | − expect(result.value).toBeDefined(); | |
| 40 | − expect(result.receipts).toHaveLength(1); | |
| 41 | − expect(result.receipts[0]!.toolPath).toBe("answeroverflow.search_servers"); | |
| 42 | − expect(result.receipts[0]!.status).toBe("succeeded"); | |
| 43 | − expect(result.receipts[0]!.decision).toBe("auto"); | |
| 44 | − } finally { | |
| 45 | − await mcpResult.close(); | |
| 46 | − } | |
| 47 | − }, { timeout: 30_000 }); | |
| 48 | − | |
| 49 | − test("generated type declarations pass the typechecker", async () => { | |
| 50 | − const mcpResult = await generateMcpTools({ | |
| 51 | − name: "answeroverflow", | |
| 52 | − url: "https://www.answeroverflow.com/mcp", | |
| 53 | − }); | |
| 54 | − | |
| 55 | − try { | |
| 56 | − // Generate full declarations from the tool tree | |
| 57 | − const declarations = generateToolDeclarations(mcpResult.tools); | |
| 58 | − | |
| 59 | − // Valid code should pass (limit is required by the MCP schema) | |
| 60 | − const validResult = typecheckCode( | |
| 61 | − `const servers = await tools.answeroverflow.search_servers({ query: "test", limit: 10 });\nreturn servers;`, | |
| 62 | − declarations, | |
| 63 | − ); | |
| 64 | − expect(validResult.ok).toBe(true); | |
| 65 | − | |
| 66 | − // Invalid tool name should fail | |
| 67 | − const invalidResult = typecheckCode( | |
| 68 | − `const r = await tools.answeroverflow.nonexistent_tool({ query: "test" });`, | |
| 69 | − declarations, | |
| 70 | − ); | |
| 71 | − expect(invalidResult.ok).toBe(false); | |
| 72 | − } finally { | |
| 73 | − await mcpResult.close(); | |
| 74 | − } | |
| 75 | − }, { timeout: 30_000 }); | |
| 76 | − | |
| 77 | − test("chained MCP calls work in the sandbox", async () => { | |
| 78 | − const mcpResult = await generateMcpTools({ | |
| 79 | − name: "answeroverflow", | |
| 80 | − url: "https://www.answeroverflow.com/mcp", | |
| 81 | − }); | |
| 82 | − | |
| 83 | − try { | |
| 84 | − const runner = createRunner({ | |
| 85 | − tools: mcpResult.tools, | |
| 86 | − requestApproval: async () => "approved", | |
| 87 | − timeoutMs: 20_000, | |
| 88 | − }); | |
| 89 | − | |
| 90 | − // Chain two MCP calls — search for servers, then search content | |
| 91 | − const result = await runner.run(` | |
| 92 | − const searchResult = await tools.answeroverflow.search_answeroverflow({ query: "react hooks" }); | |
| 93 | − const servers = await tools.answeroverflow.search_servers({ query: "javascript" }); | |
| 94 | − return { searchResult: typeof searchResult, servers: typeof servers }; | |
| 95 | − `); | |
| 96 | − | |
| 97 | − expect(result.ok).toBe(true); | |
| 98 | − expect(result.receipts).toHaveLength(2); | |
| 99 | − expect(result.receipts[0]!.toolPath).toBe("answeroverflow.search_answeroverflow"); | |
| 100 | − expect(result.receipts[1]!.toolPath).toBe("answeroverflow.search_servers"); | |
| 101 | − } finally { | |
| 102 | − await mcpResult.close(); | |
| 103 | − } | |
| 104 | − }, { timeout: 30_000 }); | |
| 105 | −}); | |
| @@ -1,126 +0,0 @@ | ||
| 1 | −import { describe, test, expect } from "bun:test"; | |
| 2 | −import { jsonSchemaToTypeString, jsonSchemaToZod, type JsonSchema } from "./json-schema-to-ts.js"; | |
| 3 | − | |
| 4 | −describe("jsonSchemaToTypeString", () => { | |
| 5 | − test("primitives", () => { | |
| 6 | − expect(jsonSchemaToTypeString({ type: "string" })).toBe("string"); | |
| 7 | − expect(jsonSchemaToTypeString({ type: "number" })).toBe("number"); | |
| 8 | − expect(jsonSchemaToTypeString({ type: "integer" })).toBe("number"); | |
| 9 | − expect(jsonSchemaToTypeString({ type: "boolean" })).toBe("boolean"); | |
| 10 | − expect(jsonSchemaToTypeString({ type: "null" })).toBe("null"); | |
| 11 | − }); | |
| 12 | − | |
| 13 | − test("object with properties", () => { | |
| 14 | − const schema: JsonSchema = { | |
| 15 | − type: "object", | |
| 16 | − properties: { | |
| 17 | − name: { type: "string" }, | |
| 18 | − age: { type: "number" }, | |
| 19 | − }, | |
| 20 | − required: ["name"], | |
| 21 | − }; | |
| 22 | − const result = jsonSchemaToTypeString(schema); | |
| 23 | − expect(result).toContain("name: string"); | |
| 24 | − expect(result).toContain("age?: number"); | |
| 25 | − }); | |
| 26 | − | |
| 27 | − test("object with no properties", () => { | |
| 28 | − const result = jsonSchemaToTypeString({ type: "object" }); | |
| 29 | − // openapi-typescript produces Record<string, never> for empty objects | |
| 30 | − expect(result).toBe("Record<string, never>"); | |
| 31 | − }); | |
| 32 | − | |
| 33 | − test("array", () => { | |
| 34 | − const schema: JsonSchema = { | |
| 35 | − type: "array", | |
| 36 | − items: { type: "string" }, | |
| 37 | − }; | |
| 38 | − // openapi-typescript uses T[] syntax instead of Array<T> | |
| 39 | − expect(jsonSchemaToTypeString(schema)).toBe("string[]"); | |
| 40 | − }); | |
| 41 | − | |
| 42 | − test("enum", () => { | |
| 43 | − const schema: JsonSchema = { | |
| 44 | − enum: ["a", "b", "c"], | |
| 45 | − }; | |
| 46 | − expect(jsonSchemaToTypeString(schema)).toBe('"a" | "b" | "c"'); | |
| 47 | − }); | |
| 48 | − | |
| 49 | − test("nested objects", () => { | |
| 50 | − const schema: JsonSchema = { | |
| 51 | − type: "object", | |
| 52 | − properties: { | |
| 53 | − user: { | |
| 54 | − type: "object", | |
| 55 | − properties: { | |
| 56 | − name: { type: "string" }, | |
| 57 | − }, | |
| 58 | − required: ["name"], | |
| 59 | − }, | |
| 60 | − }, | |
| 61 | − required: ["user"], | |
| 62 | − }; | |
| 63 | − const result = jsonSchemaToTypeString(schema); | |
| 64 | − expect(result).toContain("user: {"); | |
| 65 | − expect(result).toContain("name: string"); | |
| 66 | − }); | |
| 67 | − | |
| 68 | − test("implicit object (no type but has properties)", () => { | |
| 69 | − const schema: JsonSchema = { | |
| 70 | − properties: { | |
| 71 | − query: { type: "string" }, | |
| 72 | − }, | |
| 73 | − required: ["query"], | |
| 74 | − }; | |
| 75 | − const result = jsonSchemaToTypeString(schema); | |
| 76 | − expect(result).toContain("query: string"); | |
| 77 | − }); | |
| 78 | −}); | |
| 79 | − | |
| 80 | −describe("jsonSchemaToZod", () => { | |
| 81 | − test("string validates correctly", () => { | |
| 82 | − const schema = jsonSchemaToZod({ type: "string" }); | |
| 83 | − expect(schema.safeParse("hello").success).toBe(true); | |
| 84 | − expect(schema.safeParse(123).success).toBe(false); | |
| 85 | − }); | |
| 86 | − | |
| 87 | − test("number validates correctly", () => { | |
| 88 | − const schema = jsonSchemaToZod({ type: "number" }); | |
| 89 | − expect(schema.safeParse(42).success).toBe(true); | |
| 90 | − expect(schema.safeParse("hello").success).toBe(false); | |
| 91 | − }); | |
| 92 | − | |
| 93 | − test("object with required fields", () => { | |
| 94 | − const schema = jsonSchemaToZod({ | |
| 95 | − type: "object", | |
| 96 | − properties: { | |
| 97 | − name: { type: "string" }, | |
| 98 | − age: { type: "number" }, | |
| 99 | − }, | |
| 100 | − required: ["name"], | |
| 101 | − }); | |
| 102 | − | |
| 103 | − expect(schema.safeParse({ name: "Alice" }).success).toBe(true); | |
| 104 | − expect(schema.safeParse({ name: "Alice", age: 30 }).success).toBe(true); | |
| 105 | − expect(schema.safeParse({}).success).toBe(false); // missing required 'name' | |
| 106 | − }); | |
| 107 | − | |
| 108 | − test("array of strings", () => { | |
| 109 | − const schema = jsonSchemaToZod({ | |
| 110 | − type: "array", | |
| 111 | − items: { type: "string" }, | |
| 112 | − }); | |
| 113 | − | |
| 114 | − expect(schema.safeParse(["a", "b"]).success).toBe(true); | |
| 115 | − expect(schema.safeParse([1, 2]).success).toBe(false); | |
| 116 | − }); | |
| 117 | − | |
| 118 | − test("enum", () => { | |
| 119 | − const schema = jsonSchemaToZod({ | |
| 120 | − enum: ["a", "b", "c"], | |
| 121 | − }); | |
| 122 | − | |
| 123 | − expect(schema.safeParse("a").success).toBe(true); | |
| 124 | − expect(schema.safeParse("d").success).toBe(false); | |
| 125 | − }); | |
| 126 | −}); | |
| @@ -1,164 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Convert JSON Schema to TypeScript type strings. | |
| 3 | − * | |
| 4 | − * Both MCP tools and OpenAPI specs use JSON Schema for their type definitions. | |
| 5 | − * This module converts them to TypeScript type strings for the typechecker, | |
| 6 | − * and also creates Zod schemas for runtime validation. | |
| 7 | − * | |
| 8 | − * Type string generation uses openapi-typescript's `transformSchemaObject` for | |
| 9 | − * high-fidelity conversion (handles allOf, oneOf, enums, nullable, etc.). | |
| 10 | − * Falls back to our simpler hand-rolled converter for edge cases. | |
| 11 | − */ | |
| 12 | − | |
| 13 | −import { astToString, transformSchemaObject } from "openapi-typescript"; | |
| 14 | −import { z } from "zod"; | |
| 15 | − | |
| 16 | −// --------------------------------------------------------------------------- | |
| 17 | −// openapi-typescript context (for individual schema conversion) | |
| 18 | −// --------------------------------------------------------------------------- | |
| 19 | − | |
| 20 | −function makeTransformCtx() { | |
| 21 | − return { | |
| 22 | − additionalProperties: false, | |
| 23 | − alphabetize: false, | |
| 24 | − arrayLength: false, | |
| 25 | − defaultNonNullable: true, | |
| 26 | − discriminators: { refsHandled: [] as string[], objects: {} as Record<string, unknown> }, | |
| 27 | − emptyObjectsUnknown: false, | |
| 28 | − enum: false, | |
| 29 | − enumValues: false, | |
| 30 | − excludeDeprecated: false, | |
| 31 | − exportType: false, | |
| 32 | − immutable: false, | |
| 33 | − indentLv: 0, | |
| 34 | − pathParamsAsTypes: false, | |
| 35 | − postTransform: undefined, | |
| 36 | − propertiesRequiredByDefault: false, | |
| 37 | − redoc: undefined, | |
| 38 | − silent: true, | |
| 39 | − resolve(_ref: string) { return undefined as unknown; }, | |
| 40 | − }; | |
| 41 | −} | |
| 42 | − | |
| 43 | −// --------------------------------------------------------------------------- | |
| 44 | −// JSON Schema types (minimal subset we need) | |
| 45 | −// --------------------------------------------------------------------------- | |
| 46 | − | |
| 47 | −export interface JsonSchema { | |
| 48 | − type?: string | string[] | undefined; | |
| 49 | − properties?: Record<string, JsonSchema> | undefined; | |
| 50 | − required?: string[] | undefined; | |
| 51 | − items?: JsonSchema | undefined; | |
| 52 | − enum?: unknown[] | undefined; | |
| 53 | − const?: unknown; | |
| 54 | − oneOf?: JsonSchema[] | undefined; | |
| 55 | − anyOf?: JsonSchema[] | undefined; | |
| 56 | − allOf?: JsonSchema[] | undefined; | |
| 57 | − $ref?: string | undefined; | |
| 58 | − description?: string | undefined; | |
| 59 | − format?: string | undefined; | |
| 60 | − additionalProperties?: boolean | JsonSchema | undefined; | |
| 61 | −} | |
| 62 | − | |
| 63 | −// --------------------------------------------------------------------------- | |
| 64 | −// JSON Schema → TypeScript string (via openapi-typescript) | |
| 65 | −// --------------------------------------------------------------------------- | |
| 66 | − | |
| 67 | −/** | |
| 68 | − * Convert a JSON Schema to a TypeScript type string. | |
| 69 | − * | |
| 70 | − * Uses openapi-typescript's `transformSchemaObject` for high-fidelity conversion | |
| 71 | − * that handles allOf, oneOf, enums, nullable, additionalProperties, etc. | |
| 72 | − * Falls back to "any" for schemas that can't be converted (e.g. circular refs). | |
| 73 | − */ | |
| 74 | −export function jsonSchemaToTypeString(schema: JsonSchema): string { | |
| 75 | − if (!schema) return "any"; | |
| 76 | − try { | |
| 77 | − const node = transformSchemaObject(schema as never, { | |
| 78 | − path: "#", | |
| 79 | − ctx: makeTransformCtx() as never, | |
| 80 | − }); | |
| 81 | − return astToString(node).trim(); | |
| 82 | − } catch { | |
| 83 | − return "any"; | |
| 84 | − } | |
| 85 | −} | |
| 86 | − | |
| 87 | −// --------------------------------------------------------------------------- | |
| 88 | −// JSON Schema → Zod schema | |
| 89 | −// --------------------------------------------------------------------------- | |
| 90 | − | |
| 91 | −export function jsonSchemaToZod(schema: JsonSchema): z.ZodType { | |
| 92 | − if (!schema.type && schema.properties) { | |
| 93 | − return objectToZod(schema); | |
| 94 | − } | |
| 95 | − | |
| 96 | − if (schema.enum) { | |
| 97 | − if ( | |
| 98 | − schema.enum.length > 0 && | |
| 99 | − schema.enum.every((v): v is string => typeof v === "string") | |
| 100 | − ) { | |
| 101 | − return z.enum(schema.enum as [string, ...string[]]); | |
| 102 | − } | |
| 103 | − return z.any(); | |
| 104 | − } | |
| 105 | − | |
| 106 | − if (schema.const !== undefined) { | |
| 107 | − return z.literal(schema.const as string | number | boolean); | |
| 108 | − } | |
| 109 | − | |
| 110 | − if (schema.oneOf) { | |
| 111 | − const schemas = schema.oneOf.map(jsonSchemaToZod); | |
| 112 | − if (schemas.length === 0) return z.any(); | |
| 113 | − if (schemas.length === 1) return schemas[0]!; | |
| 114 | − return z.union([schemas[0]!, schemas[1]!, ...schemas.slice(2)]); | |
| 115 | − } | |
| 116 | − | |
| 117 | − if (schema.anyOf) { | |
| 118 | − const schemas = schema.anyOf.map(jsonSchemaToZod); | |
| 119 | − if (schemas.length === 0) return z.any(); | |
| 120 | − if (schemas.length === 1) return schemas[0]!; | |
| 121 | − return z.union([schemas[0]!, schemas[1]!, ...schemas.slice(2)]); | |
| 122 | − } | |
| 123 | − | |
| 124 | − const type = Array.isArray(schema.type) ? schema.type[0] : schema.type; | |
| 125 | − | |
| 126 | − switch (type) { | |
| 127 | − case "string": | |
| 128 | − return z.string(); | |
| 129 | − case "number": | |
| 130 | − case "integer": | |
| 131 | − return z.number(); | |
| 132 | − case "boolean": | |
| 133 | − return z.boolean(); | |
| 134 | − case "null": | |
| 135 | − return z.null(); | |
| 136 | − case "array": { | |
| 137 | − if (schema.items) { | |
| 138 | − return z.array(jsonSchemaToZod(schema.items)); | |
| 139 | − } | |
| 140 | − return z.array(z.unknown()); | |
| 141 | − } | |
| 142 | − case "object": | |
| 143 | − return objectToZod(schema); | |
| 144 | − default: | |
| 145 | − return z.any(); | |
| 146 | − } | |
| 147 | −} | |
| 148 | − | |
| 149 | −function objectToZod(schema: JsonSchema): z.ZodType { | |
| 150 | − const props = schema.properties; | |
| 151 | − if (!props || Object.keys(props).length === 0) { | |
| 152 | − return z.record(z.string(), z.unknown()); | |
| 153 | − } | |
| 154 | − | |
| 155 | − const required = new Set(schema.required ?? []); | |
| 156 | − const shape: Record<string, z.ZodType> = {}; | |
| 157 | − | |
| 158 | − for (const [key, propSchema] of Object.entries(props)) { | |
| 159 | − const zodType = jsonSchemaToZod(propSchema); | |
| 160 | − shape[key] = required.has(key) ? zodType : zodType.optional(); | |
| 161 | − } | |
| 162 | − | |
| 163 | − return z.object(shape); | |
| 164 | −} | |
| @@ -1,121 +0,0 @@ | ||
| 1 | −import { describe, test, expect } from "bun:test"; | |
| 2 | −import { walkToolTree, isToolDefinition } from "@openassistant/core"; | |
| 3 | −import { generateMcpTools } from "./mcp.js"; | |
| 4 | − | |
| 5 | −describe("generateMcpTools — AnswerOverflow MCP", () => { | |
| 6 | − test("connects and generates tools from AnswerOverflow MCP", async () => { | |
| 7 | − const result = await generateMcpTools({ | |
| 8 | − name: "answeroverflow", | |
| 9 | − url: "https://www.answeroverflow.com/mcp", | |
| 10 | − }); | |
| 11 | − | |
| 12 | − try { | |
| 13 | − // Should produce a tool tree under "answeroverflow" | |
| 14 | − expect(result.tools["answeroverflow"]).toBeDefined(); | |
| 15 | − | |
| 16 | − // Collect all tool paths | |
| 17 | − const paths: string[] = []; | |
| 18 | − walkToolTree(result.tools, (path) => paths.push(path)); | |
| 19 | − | |
| 20 | − // AnswerOverflow has 4 known tools | |
| 21 | − expect(paths.length).toBe(4); | |
| 22 | − expect(paths).toContain("answeroverflow.search_answeroverflow"); | |
| 23 | − expect(paths).toContain("answeroverflow.search_servers"); | |
| 24 | − expect(paths).toContain("answeroverflow.get_thread_messages"); | |
| 25 | − expect(paths).toContain("answeroverflow.find_similar_threads"); | |
| 26 | − | |
| 27 | − // Each tool should be a valid ToolDefinition | |
| 28 | − walkToolTree(result.tools, (_path, tool) => { | |
| 29 | − expect(isToolDefinition(tool)).toBe(true); | |
| 30 | − expect(tool.description).toBeDefined(); | |
| 31 | − expect(tool.description.length).toBeGreaterThan(0); | |
| 32 | − expect(tool.approval).toBe("auto"); // default | |
| 33 | − expect(typeof tool.formatApproval).toBe("function"); | |
| 34 | − }); | |
| 35 | − | |
| 36 | − // TypeScript declarations should be generated | |
| 37 | − expect(result.typeDeclaration).toContain("answeroverflow:"); | |
| 38 | − expect(result.typeDeclaration).toContain("search_answeroverflow"); | |
| 39 | − expect(result.typeDeclaration).toContain("query: string"); | |
| 40 | − | |
| 41 | − // Prompt guidance should be generated | |
| 42 | − expect(result.promptGuidance).toContain("tools.answeroverflow.search_answeroverflow"); | |
| 43 | − } finally { | |
| 44 | − await result.close(); | |
| 45 | − } | |
| 46 | − }, { timeout: 30_000 }); | |
| 47 | − | |
| 48 | − test("respects approval overrides", async () => { | |
| 49 | − const result = await generateMcpTools({ | |
| 50 | − name: "answeroverflow", | |
| 51 | − url: "https://www.answeroverflow.com/mcp", | |
| 52 | − overrides: { | |
| 53 | − search_answeroverflow: { approval: "required" }, | |
| 54 | − }, | |
| 55 | − }); | |
| 56 | − | |
| 57 | − try { | |
| 58 | − let found = false; | |
| 59 | − walkToolTree(result.tools, (path, tool) => { | |
| 60 | − if (path === "answeroverflow.search_answeroverflow") { | |
| 61 | − expect(tool.approval).toBe("required"); | |
| 62 | − found = true; | |
| 63 | − } | |
| 64 | − }); | |
| 65 | − expect(found).toBe(true); | |
| 66 | − } finally { | |
| 67 | − await result.close(); | |
| 68 | − } | |
| 69 | − }, { timeout: 30_000 }); | |
| 70 | − | |
| 71 | − test("generated tools can actually call the MCP server", async () => { | |
| 72 | − const result = await generateMcpTools({ | |
| 73 | − name: "answeroverflow", | |
| 74 | − url: "https://www.answeroverflow.com/mcp", | |
| 75 | − }); | |
| 76 | − | |
| 77 | − try { | |
| 78 | − // Find the search tool | |
| 79 | − let searchTool: ReturnType<typeof isToolDefinition extends (v: unknown) => v is infer T ? () => T : never> | undefined; | |
| 80 | − walkToolTree(result.tools, (path, tool) => { | |
| 81 | − if (path === "answeroverflow.search_servers") { | |
| 82 | − searchTool = tool; | |
| 83 | − } | |
| 84 | − }); | |
| 85 | − | |
| 86 | − expect(searchTool).toBeDefined(); | |
| 87 | − | |
| 88 | − // Actually call the MCP tool | |
| 89 | − const searchResult = await searchTool!.run({ query: "discord" }); | |
| 90 | − expect(searchResult).toBeDefined(); | |
| 91 | − } finally { | |
| 92 | − await result.close(); | |
| 93 | − } | |
| 94 | − }, { timeout: 30_000 }); | |
| 95 | − | |
| 96 | − test("generated tools include approval preview formatter", async () => { | |
| 97 | − const result = await generateMcpTools({ | |
| 98 | − name: "answeroverflow", | |
| 99 | − url: "https://www.answeroverflow.com/mcp", | |
| 100 | − overrides: { | |
| 101 | − search_servers: { approval: "required" }, | |
| 102 | − }, | |
| 103 | − }); | |
| 104 | − | |
| 105 | − try { | |
| 106 | − let foundPreview = false; | |
| 107 | − walkToolTree(result.tools, (path, tool) => { | |
| 108 | − if (path === "answeroverflow.search_servers") { | |
| 109 | − const preview = tool.formatApproval?.({ query: "bun" }); | |
| 110 | − expect(preview).toBeDefined(); | |
| 111 | − expect(preview?.title).toBe("Run search_servers"); | |
| 112 | − expect(preview?.details).toContain("query"); | |
| 113 | − foundPreview = true; | |
| 114 | − } | |
| 115 | − }); | |
| 116 | − expect(foundPreview).toBe(true); | |
| 117 | − } finally { | |
| 118 | − await result.close(); | |
| 119 | − } | |
| 120 | − }, { timeout: 30_000 }); | |
| 121 | −}); | |
| @@ -1,271 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * MCP tool generator — introspects an MCP server and produces a ToolTree. | |
| 3 | − * | |
| 4 | − * Connects to an MCP server via SSE or Streamable HTTP transport, | |
| 5 | − * fetches the tool list, and converts each tool into a defineTool() | |
| 6 | − * with Zod schemas derived from the JSON Schema in the tool definitions. | |
| 7 | − */ | |
| 8 | − | |
| 9 | −import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | |
| 10 | −import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; | |
| 11 | −import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; | |
| 12 | −import { defineTool, type ApprovalMode, type ToolTree } from "@openassistant/core"; | |
| 13 | −import { jsonSchemaToZod, jsonSchemaToTypeString, type JsonSchema } from "./json-schema-to-ts.js"; | |
| 14 | −import { z } from "zod"; | |
| 15 | − | |
| 16 | −// --------------------------------------------------------------------------- | |
| 17 | −// Config | |
| 18 | −// --------------------------------------------------------------------------- | |
| 19 | − | |
| 20 | −export interface McpToolSource { | |
| 21 | − /** Namespace in the tool tree: `tools.<name>.<toolName>` */ | |
| 22 | − readonly name: string; | |
| 23 | − /** URL of the MCP server (SSE or Streamable HTTP endpoint) */ | |
| 24 | − readonly url: string; | |
| 25 | − /** Transport type. Defaults to trying streamable HTTP first, falling back to SSE. */ | |
| 26 | − readonly transport?: "sse" | "streamable-http" | undefined; | |
| 27 | − /** Per-tool overrides for approval mode. Key is the MCP tool name. */ | |
| 28 | − readonly overrides?: Readonly<Record<string, { | |
| 29 | − readonly approval?: ApprovalMode | undefined; | |
| 30 | − }>> | undefined; | |
| 31 | − /** Default approval mode for tools. Defaults to "auto". */ | |
| 32 | − readonly defaultApproval?: ApprovalMode | undefined; | |
| 33 | −} | |
| 34 | − | |
| 35 | −// --------------------------------------------------------------------------- | |
| 36 | −// Result | |
| 37 | −// --------------------------------------------------------------------------- | |
| 38 | − | |
| 39 | −export interface McpGenerateResult { | |
| 40 | − /** The generated tool tree, namespaced under the source name. */ | |
| 41 | − readonly tools: ToolTree; | |
| 42 | − /** TypeScript declarations for the typechecker. */ | |
| 43 | − readonly typeDeclaration: string; | |
| 44 | − /** Human-readable descriptions for the LLM prompt. */ | |
| 45 | − readonly promptGuidance: string; | |
| 46 | − /** The MCP client (keep alive for making tool calls). */ | |
| 47 | − readonly client: Client; | |
| 48 | − /** Cleanup function to close the connection. */ | |
| 49 | − readonly close: () => Promise<void>; | |
| 50 | −} | |
| 51 | − | |
| 52 | −// --------------------------------------------------------------------------- | |
| 53 | −// Connection | |
| 54 | −// --------------------------------------------------------------------------- | |
| 55 | − | |
| 56 | −const MAX_APPROVAL_PREVIEW = 240; | |
| 57 | − | |
| 58 | −function isRecord(value: unknown): value is Record<string, unknown> { | |
| 59 | − return typeof value === "object" && value !== null && !Array.isArray(value); | |
| 60 | −} | |
| 61 | − | |
| 62 | −function toPreview(value: unknown): string { | |
| 63 | − if (value === undefined) return "undefined"; | |
| 64 | − if (value === null) return "null"; | |
| 65 | − try { | |
| 66 | − const text = typeof value === "string" ? value : JSON.stringify(value); | |
| 67 | − return text.length > MAX_APPROVAL_PREVIEW | |
| 68 | − ? `${text.slice(0, MAX_APPROVAL_PREVIEW)}...` | |
| 69 | − : text; | |
| 70 | − } catch { | |
| 71 | − return String(value); | |
| 72 | − } | |
| 73 | −} | |
| 74 | − | |
| 75 | −function inferMcpAction(toolName: string): { | |
| 76 | − action: "create" | "update" | "delete" | "read" | "execute" | "other"; | |
| 77 | − isDestructive: boolean; | |
| 78 | −} { | |
| 79 | − const lower = toolName.toLowerCase(); | |
| 80 | − if (/(delete|remove|destroy|purge)/.test(lower)) { | |
| 81 | − return { action: "delete", isDestructive: true }; | |
| 82 | − } | |
| 83 | − if (/(create|add|insert)/.test(lower)) { | |
| 84 | − return { action: "create", isDestructive: false }; | |
| 85 | − } | |
| 86 | − if (/(update|set|patch|edit)/.test(lower)) { | |
| 87 | − return { action: "update", isDestructive: false }; | |
| 88 | − } | |
| 89 | − if (/(get|list|search|find|read)/.test(lower)) { | |
| 90 | − return { action: "read", isDestructive: false }; | |
| 91 | − } | |
| 92 | − return { action: "execute", isDestructive: false }; | |
| 93 | −} | |
| 94 | − | |
| 95 | −function extractIds(input: unknown): string[] { | |
| 96 | − if (!isRecord(input)) return []; | |
| 97 | − const ids: string[] = []; | |
| 98 | − for (const key of ["id", "ids", "idOrName", "name", "slug", "key"]) { | |
| 99 | − const value = input[key]; | |
| 100 | − if (typeof value === "string" || typeof value === "number") { | |
| 101 | − ids.push(String(value)); | |
| 102 | − continue; | |
| 103 | − } | |
| 104 | − if (Array.isArray(value)) { | |
| 105 | − for (const item of value) { | |
| 106 | − if (typeof item === "string" || typeof item === "number") { | |
| 107 | − ids.push(String(item)); | |
| 108 | − } | |
| 109 | − } | |
| 110 | − } | |
| 111 | − } | |
| 112 | − return ids.slice(0, 5); | |
| 113 | −} | |
| 114 | − | |
| 115 | −function buildMcpApprovalPreview(toolName: string, input: unknown) { | |
| 116 | − const { action, isDestructive } = inferMcpAction(toolName); | |
| 117 | − const ids = extractIds(input); | |
| 118 | − const details = [ | |
| 119 | − ids.length > 0 ? `Target: ${ids.join(", ")}` : undefined, | |
| 120 | − `Arguments: ${toPreview(input)}`, | |
| 121 | − ].filter(Boolean).join("\n"); | |
| 122 | − return { | |
| 123 | − title: `Run ${toolName}`, | |
| 124 | − details, | |
| 125 | − action, | |
| 126 | − resourceType: toolName.replace(/[_-]/g, " "), | |
| 127 | − resourceIds: ids.length > 0 ? ids : undefined, | |
| 128 | − isDestructive, | |
| 129 | − } as const; | |
| 130 | −} | |
| 131 | − | |
| 132 | −async function connectMcp( | |
| 133 | − url: string, | |
| 134 | − transport?: "sse" | "streamable-http", | |
| 135 | −): Promise<{ client: Client; close: () => Promise<void> }> { | |
| 136 | − const client = new Client( | |
| 137 | − { name: "openassistant-tool-gen", version: "0.1.0" }, | |
| 138 | − { capabilities: {} }, | |
| 139 | − ); | |
| 140 | − | |
| 141 | − const parsedUrl = new URL(url); | |
| 142 | − | |
| 143 | − if (transport === "sse") { | |
| 144 | − const sseTransport = new SSEClientTransport(parsedUrl); | |
| 145 | − await client.connect(sseTransport); | |
| 146 | − return { client, close: () => client.close() }; | |
| 147 | − } | |
| 148 | − | |
| 149 | − if (transport === "streamable-http") { | |
| 150 | − const httpTransport = new StreamableHTTPClientTransport(parsedUrl); | |
| 151 | − await client.connect(httpTransport as Parameters<typeof client.connect>[0]); | |
| 152 | − return { client, close: () => client.close() }; | |
| 153 | − } | |
| 154 | − | |
| 155 | − // Try streamable HTTP first, fall back to SSE | |
| 156 | − try { | |
| 157 | − const httpTransport = new StreamableHTTPClientTransport(parsedUrl); | |
| 158 | − await client.connect(httpTransport as Parameters<typeof client.connect>[0]); | |
| 159 | − return { client, close: () => client.close() }; | |
| 160 | − } catch { | |
| 161 | − const sseTransport = new SSEClientTransport(parsedUrl); | |
| 162 | − await client.connect(sseTransport); | |
| 163 | − return { client, close: () => client.close() }; | |
| 164 | − } | |
| 165 | −} | |
| 166 | − | |
| 167 | −// --------------------------------------------------------------------------- | |
| 168 | −// Generator | |
| 169 | −// --------------------------------------------------------------------------- | |
| 170 | − | |
| 171 | −export async function generateMcpTools( | |
| 172 | − source: McpToolSource, | |
| 173 | −): Promise<McpGenerateResult> { | |
| 174 | − let connection = await connectMcp(source.url, source.transport); | |
| 175 | − | |
| 176 | − /** | |
| 177 | − * Call an MCP tool with automatic reconnection on socket errors. | |
| 178 | − * If the connection dropped, reconnect and retry once. | |
| 179 | − */ | |
| 180 | − async function callToolWithReconnect( | |
| 181 | − toolName: string, | |
| 182 | − args: Record<string, unknown>, | |
| 183 | − ) { | |
| 184 | − try { | |
| 185 | − return await connection.client.callTool({ name: toolName, arguments: args }); | |
| 186 | − } catch (error) { | |
| 187 | − const msg = error instanceof Error ? error.message : String(error); | |
| 188 | − // Detect connection errors and retry with a fresh connection | |
| 189 | − if (msg.includes("socket") || msg.includes("closed") || msg.includes("ECONNRESET") || msg.includes("fetch failed")) { | |
| 190 | − console.warn(`[MCP ${source.name}] Connection lost, reconnecting...`); | |
| 191 | − try { await connection.close(); } catch { /* ignore */ } | |
| 192 | − connection = await connectMcp(source.url, source.transport); | |
| 193 | − return await connection.client.callTool({ name: toolName, arguments: args }); | |
| 194 | − } | |
| 195 | − throw error; | |
| 196 | − } | |
| 197 | − } | |
| 198 | − | |
| 199 | − const { tools: mcpTools } = await connection.client.listTools(); | |
| 200 | − | |
| 201 | − const toolTree: Record<string, ReturnType<typeof defineTool>> = {}; | |
| 202 | − const typeLines: string[] = []; | |
| 203 | − const guidanceLines: string[] = []; | |
| 204 | − | |
| 205 | − for (const mcpTool of mcpTools) { | |
| 206 | − const toolName = mcpTool.name; | |
| 207 | − const description = mcpTool.description ?? `MCP tool: ${toolName}`; | |
| 208 | − const inputSchema = (mcpTool.inputSchema as JsonSchema | undefined) ?? { type: "object" }; | |
| 209 | − | |
| 210 | − const approval = | |
| 211 | − source.overrides?.[toolName]?.approval ?? | |
| 212 | − source.defaultApproval ?? | |
| 213 | − "auto"; | |
| 214 | − | |
| 215 | − // Generate Zod schema from JSON Schema | |
| 216 | − const argsZod = jsonSchemaToZod(inputSchema); | |
| 217 | − | |
| 218 | − // Generate TypeScript type string using openapi-typescript | |
| 219 | − const argsTypeString = jsonSchemaToTypeString(inputSchema); | |
| 220 | − | |
| 221 | − // Create the tool definition | |
| 222 | − toolTree[toolName] = defineTool({ | |
| 223 | − description, | |
| 224 | − approval, | |
| 225 | − args: argsZod, | |
| 226 | − returns: z.any(), // MCP tools return opaque content | |
| 227 | − metadata: { | |
| 228 | − argsType: argsTypeString, | |
| 229 | − // MCP tools don't declare output schemas, so no returnsType | |
| 230 | − }, | |
| 231 | − run: async (input: unknown) => { | |
| 232 | − const result = await callToolWithReconnect( | |
| 233 | − toolName, | |
| 234 | − input as Record<string, unknown>, | |
| 235 | − ); | |
| 236 | − // MCP returns { content: [{ type, text, ... }] } | |
| 237 | − // Extract text content for simplicity | |
| 238 | − if (result.content && Array.isArray(result.content)) { | |
| 239 | − const textParts = result.content | |
| 240 | − .filter((c: { type: string }) => c.type === "text") | |
| 241 | − .map((c: { text?: string }) => c.text) | |
| 242 | − .filter(Boolean); | |
| 243 | − if (textParts.length === 1) return textParts[0]; | |
| 244 | − if (textParts.length > 1) return textParts; | |
| 245 | − } | |
| 246 | − return result.content; | |
| 247 | − }, | |
| 248 | − formatApproval: (input) => buildMcpApprovalPreview(toolName, input), | |
| 249 | − }); | |
| 250 | − | |
| 251 | − // Build TypeScript declaration | |
| 252 | − const approvalNote = approval === "required" ? " (approval required)" : " (auto-approved)"; | |
| 253 | − typeLines.push(` /** ${description} */`); | |
| 254 | − typeLines.push(` ${toolName}(input: ${argsTypeString}): Promise<any>;`); | |
| 255 | − | |
| 256 | − guidanceLines.push( | |
| 257 | − `- tools.${source.name}.${toolName}(${argsTypeString}): Promise<any>${approvalNote} — ${description}`, | |
| 258 | − ); | |
| 259 | − } | |
| 260 | − | |
| 261 | − const typeDeclaration = `${source.name}: {\n${typeLines.join("\n")}\n}`; | |
| 262 | − const promptGuidance = guidanceLines.join("\n"); | |
| 263 | − | |
| 264 | − return { | |
| 265 | − tools: { [source.name]: toolTree }, | |
| 266 | − typeDeclaration, | |
| 267 | − promptGuidance, | |
| 268 | − client: connection.client, | |
| 269 | − close: () => connection.close(), | |
| 270 | − }; | |
| 271 | −} | |
| @@ -1,283 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Integration test: OpenAPI tools → runner sandbox execution. | |
| 3 | − * | |
| 4 | − * Uses JSONPlaceholder (https://jsonplaceholder.typicode.com) — a free, | |
| 5 | − * no-auth REST API — with an inline OpenAPI spec. | |
| 6 | − */ | |
| 7 | − | |
| 8 | −import { describe, test, expect } from "bun:test"; | |
| 9 | −import { | |
| 10 | − createRunner, | |
| 11 | − generateToolDeclarations, | |
| 12 | − typecheckCode, | |
| 13 | −} from "@openassistant/core"; | |
| 14 | −import { generateOpenApiTools } from "./openapi.js"; | |
| 15 | − | |
| 16 | −const jsonPlaceholderSpec = { | |
| 17 | − openapi: "3.0.0", | |
| 18 | − info: { title: "JSONPlaceholder", version: "1.0.0" }, | |
| 19 | − servers: [{ url: "https://jsonplaceholder.typicode.com" }], | |
| 20 | − paths: { | |
| 21 | − "/posts": { | |
| 22 | − get: { | |
| 23 | − tags: ["posts"], | |
| 24 | − operationId: "listPosts", | |
| 25 | − summary: "List all posts", | |
| 26 | − parameters: [ | |
| 27 | − { | |
| 28 | − name: "_limit", | |
| 29 | − in: "query", | |
| 30 | − required: false, | |
| 31 | − schema: { type: "integer" }, | |
| 32 | − description: "Limit the number of results", | |
| 33 | − }, | |
| 34 | − { | |
| 35 | − name: "userId", | |
| 36 | − in: "query", | |
| 37 | − required: false, | |
| 38 | − schema: { type: "integer" }, | |
| 39 | − description: "Filter by user ID", | |
| 40 | − }, | |
| 41 | − ], | |
| 42 | − responses: { | |
| 43 | − "200": { | |
| 44 | − description: "A list of posts", | |
| 45 | − content: { | |
| 46 | − "application/json": { | |
| 47 | − schema: { | |
| 48 | − type: "array", | |
| 49 | − items: { | |
| 50 | − type: "object", | |
| 51 | − properties: { | |
| 52 | − userId: { type: "integer" }, | |
| 53 | − id: { type: "integer" }, | |
| 54 | − title: { type: "string" }, | |
| 55 | − body: { type: "string" }, | |
| 56 | − }, | |
| 57 | − required: ["userId", "id", "title", "body"], | |
| 58 | − }, | |
| 59 | − }, | |
| 60 | − }, | |
| 61 | − }, | |
| 62 | − }, | |
| 63 | − }, | |
| 64 | − }, | |
| 65 | − }, | |
| 66 | − "/posts/{id}": { | |
| 67 | − get: { | |
| 68 | − tags: ["posts"], | |
| 69 | − operationId: "getPost", | |
| 70 | − summary: "Get a post by ID", | |
| 71 | − parameters: [ | |
| 72 | − { | |
| 73 | − name: "id", | |
| 74 | − in: "path", | |
| 75 | − required: true, | |
| 76 | − schema: { type: "integer" }, | |
| 77 | − description: "Post ID", | |
| 78 | − }, | |
| 79 | − ], | |
| 80 | − responses: { | |
| 81 | − "200": { | |
| 82 | − description: "A post", | |
| 83 | − content: { | |
| 84 | − "application/json": { | |
| 85 | − schema: { | |
| 86 | − type: "object", | |
| 87 | − properties: { | |
| 88 | − userId: { type: "integer" }, | |
| 89 | − id: { type: "integer" }, | |
| 90 | − title: { type: "string" }, | |
| 91 | − body: { type: "string" }, | |
| 92 | − }, | |
| 93 | − required: ["userId", "id", "title", "body"], | |
| 94 | − }, | |
| 95 | − }, | |
| 96 | − }, | |
| 97 | − }, | |
| 98 | − }, | |
| 99 | − }, | |
| 100 | − }, | |
| 101 | − "/users": { | |
| 102 | − get: { | |
| 103 | − tags: ["users"], | |
| 104 | − operationId: "listUsers", | |
| 105 | − summary: "List all users", | |
| 106 | − parameters: [], | |
| 107 | − responses: { | |
| 108 | − "200": { | |
| 109 | − description: "A list of users", | |
| 110 | − content: { | |
| 111 | − "application/json": { | |
| 112 | − schema: { | |
| 113 | − type: "array", | |
| 114 | − items: { | |
| 115 | − type: "object", | |
| 116 | − properties: { | |
| 117 | − id: { type: "integer" }, | |
| 118 | − name: { type: "string" }, | |
| 119 | − email: { type: "string" }, | |
| 120 | − }, | |
| 121 | − required: ["id", "name", "email"], | |
| 122 | − }, | |
| 123 | − }, | |
| 124 | − }, | |
| 125 | − }, | |
| 126 | − }, | |
| 127 | − }, | |
| 128 | − }, | |
| 129 | − }, | |
| 130 | − "/users/{id}": { | |
| 131 | − get: { | |
| 132 | − tags: ["users"], | |
| 133 | − operationId: "getUser", | |
| 134 | − summary: "Get a user by ID", | |
| 135 | − parameters: [ | |
| 136 | − { | |
| 137 | − name: "id", | |
| 138 | − in: "path", | |
| 139 | − required: true, | |
| 140 | − schema: { type: "integer" }, | |
| 141 | − }, | |
| 142 | − ], | |
| 143 | − responses: { | |
| 144 | − "200": { | |
| 145 | − description: "A user", | |
| 146 | − content: { | |
| 147 | − "application/json": { | |
| 148 | − schema: { | |
| 149 | − type: "object", | |
| 150 | − properties: { | |
| 151 | − id: { type: "integer" }, | |
| 152 | − name: { type: "string" }, | |
| 153 | − email: { type: "string" }, | |
| 154 | − }, | |
| 155 | − required: ["id", "name", "email"], | |
| 156 | − }, | |
| 157 | − }, | |
| 158 | − }, | |
| 159 | − }, | |
| 160 | − }, | |
| 161 | − }, | |
| 162 | − }, | |
| 163 | − }, | |
| 164 | −}; | |
| 165 | − | |
| 166 | −describe("OpenAPI → runner integration", () => { | |
| 167 | − test("generated OpenAPI tools are callable from the sandbox", async () => { | |
| 168 | − const result = await generateOpenApiTools({ | |
| 169 | − name: "placeholder", | |
| 170 | − spec: jsonPlaceholderSpec as unknown as string, | |
| 171 | − }); | |
| 172 | − | |
| 173 | − const runner = createRunner({ | |
| 174 | − tools: result.tools, | |
| 175 | − requestApproval: async () => "approved", | |
| 176 | − timeoutMs: 15_000, | |
| 177 | − }); | |
| 178 | − | |
| 179 | − // Call the real JSONPlaceholder API from the sandbox | |
| 180 | − const runResult = await runner.run( | |
| 181 | − `const post = await tools.placeholder.posts.getPost({ id: 1 });\nreturn post;`, | |
| 182 | − ); | |
| 183 | − | |
| 184 | − expect(runResult.ok).toBe(true); | |
| 185 | − expect(runResult.value).toBeDefined(); | |
| 186 | − | |
| 187 | − const post = runResult.value as { id: number; userId: number; title: string }; | |
| 188 | − expect(post.id).toBe(1); | |
| 189 | − expect(post.userId).toBe(1); | |
| 190 | − expect(typeof post.title).toBe("string"); | |
| 191 | − | |
| 192 | − expect(runResult.receipts).toHaveLength(1); | |
| 193 | − expect(runResult.receipts[0]!.toolPath).toBe("placeholder.posts.getPost"); | |
| 194 | − expect(runResult.receipts[0]!.status).toBe("succeeded"); | |
| 195 | − }, { timeout: 15_000 }); | |
| 196 | − | |
| 197 | − test("chained OpenAPI calls work in the sandbox", async () => { | |
| 198 | − const result = await generateOpenApiTools({ | |
| 199 | − name: "placeholder", | |
| 200 | − spec: jsonPlaceholderSpec as unknown as string, | |
| 201 | − }); | |
| 202 | − | |
| 203 | − const runner = createRunner({ | |
| 204 | − tools: result.tools, | |
| 205 | − requestApproval: async () => "approved", | |
| 206 | − timeoutMs: 15_000, | |
| 207 | − }); | |
| 208 | − | |
| 209 | − // Get a user, then get their posts | |
| 210 | − const runResult = await runner.run(` | |
| 211 | − const user = await tools.placeholder.users.getUser({ id: 1 }); | |
| 212 | − const posts = await tools.placeholder.posts.listPosts({ userId: 1, _limit: 3 }); | |
| 213 | − return { userName: user.name, postCount: posts.length }; | |
| 214 | − `); | |
| 215 | − | |
| 216 | − expect(runResult.ok).toBe(true); | |
| 217 | − | |
| 218 | − const value = runResult.value as { userName: string; postCount: number }; | |
| 219 | − expect(value.userName).toBe("Leanne Graham"); | |
| 220 | − expect(value.postCount).toBe(3); | |
| 221 | − | |
| 222 | − expect(runResult.receipts).toHaveLength(2); | |
| 223 | − expect(runResult.receipts[0]!.toolPath).toBe("placeholder.users.getUser"); | |
| 224 | − expect(runResult.receipts[1]!.toolPath).toBe("placeholder.posts.listPosts"); | |
| 225 | − }, { timeout: 15_000 }); | |
| 226 | − | |
| 227 | − test("type declarations + typechecker work for generated OpenAPI tools", async () => { | |
| 228 | − const result = await generateOpenApiTools({ | |
| 229 | − name: "placeholder", | |
| 230 | − spec: jsonPlaceholderSpec as unknown as string, | |
| 231 | − }); | |
| 232 | − | |
| 233 | − const declarations = generateToolDeclarations(result.tools); | |
| 234 | − | |
| 235 | − // Valid code passes | |
| 236 | − const validResult = typecheckCode( | |
| 237 | − `const post = await tools.placeholder.posts.getPost({ id: 1 });\nreturn post;`, | |
| 238 | − declarations, | |
| 239 | − ); | |
| 240 | − expect(validResult.ok).toBe(true); | |
| 241 | − | |
| 242 | − // Invalid tool name fails | |
| 243 | − const invalidResult = typecheckCode( | |
| 244 | − `await tools.placeholder.posts.deleteSomething({ id: 1 });`, | |
| 245 | − declarations, | |
| 246 | − ); | |
| 247 | − expect(invalidResult.ok).toBe(false); | |
| 248 | − }, { timeout: 15_000 }); | |
| 249 | − | |
| 250 | − test("loop over API results in the sandbox", async () => { | |
| 251 | − const result = await generateOpenApiTools({ | |
| 252 | − name: "placeholder", | |
| 253 | − spec: jsonPlaceholderSpec as unknown as string, | |
| 254 | − }); | |
| 255 | − | |
| 256 | − const runner = createRunner({ | |
| 257 | − tools: result.tools, | |
| 258 | − requestApproval: async () => "approved", | |
| 259 | − timeoutMs: 15_000, | |
| 260 | − }); | |
| 261 | − | |
| 262 | − // Fetch 3 posts, then fetch the user for each one | |
| 263 | − const runResult = await runner.run(` | |
| 264 | − const posts = await tools.placeholder.posts.listPosts({ _limit: 3 }); | |
| 265 | − const results = []; | |
| 266 | − for (const post of posts) { | |
| 267 | − const user = await tools.placeholder.users.getUser({ id: post.userId }); | |
| 268 | − results.push({ postTitle: post.title, authorName: user.name }); | |
| 269 | − } | |
| 270 | − return results; | |
| 271 | − `); | |
| 272 | − | |
| 273 | − expect(runResult.ok).toBe(true); | |
| 274 | − | |
| 275 | − const value = runResult.value as Array<{ postTitle: string; authorName: string }>; | |
| 276 | − expect(value).toHaveLength(3); | |
| 277 | − expect(typeof value[0]!.postTitle).toBe("string"); | |
| 278 | − expect(typeof value[0]!.authorName).toBe("string"); | |
| 279 | − | |
| 280 | − // 1 listPosts + 3 getUser = 4 receipts | |
| 281 | − expect(runResult.receipts).toHaveLength(4); | |
| 282 | − }, { timeout: 15_000 }); | |
| 283 | −}); | |
| @@ -1,143 +0,0 @@ | ||
| 1 | −import { describe, test, expect } from "bun:test"; | |
| 2 | −import { walkToolTree, isToolDefinition } from "@openassistant/core"; | |
| 3 | −import { generateOpenApiTools } from "./openapi.js"; | |
| 4 | − | |
| 5 | −describe("generateOpenApiTools — FastSpring spec", () => { | |
| 6 | − test("generated write tools include approval preview formatter", async () => { | |
| 7 | − const result = await generateOpenApiTools({ | |
| 8 | − name: "vercel", | |
| 9 | − spec: { | |
| 10 | − openapi: "3.0.0", | |
| 11 | − info: { title: "Test", version: "1.0.0" }, | |
| 12 | − servers: [{ url: "https://api.example.com" }], | |
| 13 | − paths: { | |
| 14 | − "/v1/projects/{idOrName}": { | |
| 15 | − delete: { | |
| 16 | − tags: ["projects"], | |
| 17 | − operationId: "deleteProject", | |
| 18 | − summary: "Delete project", | |
| 19 | − parameters: [ | |
| 20 | − { | |
| 21 | − name: "idOrName", | |
| 22 | − in: "path", | |
| 23 | − required: true, | |
| 24 | − schema: { type: "string" }, | |
| 25 | − }, | |
| 26 | − ], | |
| 27 | − responses: { | |
| 28 | − "200": { | |
| 29 | − description: "ok", | |
| 30 | − content: { | |
| 31 | − "application/json": { | |
| 32 | − schema: { type: "object", properties: { ok: { type: "boolean" } } }, | |
| 33 | − }, | |
| 34 | − }, | |
| 35 | − }, | |
| 36 | − }, | |
| 37 | − }, | |
| 38 | − }, | |
| 39 | − }, | |
| 40 | − }, | |
| 41 | − }); | |
| 42 | − | |
| 43 | − const ns = result.tools["vercel"] as Record<string, unknown>; | |
| 44 | − const projects = ns["projects"] as Record<string, unknown>; | |
| 45 | − const deleteTool = projects["deleteProject"]; | |
| 46 | − | |
| 47 | − expect(isToolDefinition(deleteTool)).toBe(true); | |
| 48 | − const preview = deleteTool.formatApproval?.({ idOrName: "prj_123" }); | |
| 49 | − expect(preview).toBeDefined(); | |
| 50 | − expect(preview?.title).toBe("DELETE /v1/projects/{idOrName}"); | |
| 51 | − expect(preview?.details).toContain("Target: prj_123"); | |
| 52 | − expect(preview?.action).toBe("delete"); | |
| 53 | − expect(preview?.isDestructive).toBe(true); | |
| 54 | − }); | |
| 55 | − | |
| 56 | − test("parses the FastSpring OpenAPI spec and generates tools", async () => { | |
| 57 | − const result = await generateOpenApiTools({ | |
| 58 | − name: "fastspring", | |
| 59 | − spec: "https://raw.githubusercontent.com/konfig-sdks/openapi-examples/refs/heads/main/fastspring/openapi.yaml", | |
| 60 | − }); | |
| 61 | − | |
| 62 | − // Should produce a nested tool tree under "fastspring" | |
| 63 | − expect(result.tools["fastspring"]).toBeDefined(); | |
| 64 | − | |
| 65 | − // Collect all tool paths | |
| 66 | − const paths: string[] = []; | |
| 67 | − walkToolTree(result.tools, (path) => paths.push(path)); | |
| 68 | − | |
| 69 | − // Should have a meaningful number of tools | |
| 70 | − expect(paths.length).toBeGreaterThan(10); | |
| 71 | − | |
| 72 | − // Should have common FastSpring endpoints | |
| 73 | − const hasAccounts = paths.some((p) => p.includes("accounts")); | |
| 74 | − const hasProducts = paths.some((p) => p.includes("products")); | |
| 75 | − const hasOrders = paths.some((p) => p.includes("orders")); | |
| 76 | − const hasSubscriptions = paths.some((p) => p.includes("subscriptions")); | |
| 77 | − | |
| 78 | − expect(hasAccounts).toBe(true); | |
| 79 | − expect(hasProducts).toBe(true); | |
| 80 | − expect(hasOrders).toBe(true); | |
| 81 | − expect(hasSubscriptions).toBe(true); | |
| 82 | − | |
| 83 | − // GET operations should be auto-approved by default | |
| 84 | − const accountsTree = result.tools["fastspring"] as Record<string, unknown>; | |
| 85 | − const accountsGroup = accountsTree["accounts"] as Record<string, unknown>; | |
| 86 | − // Find a GET operation | |
| 87 | − for (const [_name, tool] of Object.entries(accountsGroup)) { | |
| 88 | − if (isToolDefinition(tool)) { | |
| 89 | − // At least one tool should exist in accounts | |
| 90 | − expect(tool.description).toBeDefined(); | |
| 91 | − break; | |
| 92 | − } | |
| 93 | − } | |
| 94 | − | |
| 95 | − // TypeScript declarations should be generated | |
| 96 | − expect(result.typeDeclaration).toContain("fastspring:"); | |
| 97 | − expect(result.typeDeclaration.length).toBeGreaterThan(100); | |
| 98 | − | |
| 99 | − // Prompt guidance should be generated | |
| 100 | − expect(result.promptGuidance).toContain("tools.fastspring"); | |
| 101 | − expect(result.promptGuidance.length).toBeGreaterThan(100); | |
| 102 | − }, { timeout: 30_000 }); | |
| 103 | − | |
| 104 | − test("respects approval overrides", async () => { | |
| 105 | − const result = await generateOpenApiTools({ | |
| 106 | − name: "fastspring", | |
| 107 | − spec: "https://raw.githubusercontent.com/konfig-sdks/openapi-examples/refs/heads/main/fastspring/openapi.yaml", | |
| 108 | − overrides: { | |
| 109 | − "Accounts_getAccountById": { approval: "required" }, | |
| 110 | − }, | |
| 111 | − }); | |
| 112 | − | |
| 113 | − // Find the overridden tool | |
| 114 | − let found = false; | |
| 115 | − walkToolTree(result.tools, (path, tool) => { | |
| 116 | − if (path.includes("Accounts_getAccountById")) { | |
| 117 | − expect(tool.approval).toBe("required"); | |
| 118 | − found = true; | |
| 119 | − } | |
| 120 | − }); | |
| 121 | − | |
| 122 | − expect(found).toBe(true); | |
| 123 | − }, { timeout: 30_000 }); | |
| 124 | − | |
| 125 | − test("write operations default to required approval", async () => { | |
| 126 | − const result = await generateOpenApiTools({ | |
| 127 | − name: "fastspring", | |
| 128 | − spec: "https://raw.githubusercontent.com/konfig-sdks/openapi-examples/refs/heads/main/fastspring/openapi.yaml", | |
| 129 | − }); | |
| 130 | − | |
| 131 | − // POST/PUT/DELETE operations should require approval | |
| 132 | − let foundWriteWithApproval = false; | |
| 133 | − walkToolTree(result.tools, (path, tool) => { | |
| 134 | − // createNewAccount is a POST | |
| 135 | − if (path.includes("createNewAccount") || path.includes("createAndUpdate")) { | |
| 136 | − expect(tool.approval).toBe("required"); | |
| 137 | − foundWriteWithApproval = true; | |
| 138 | − } | |
| 139 | − }); | |
| 140 | − | |
| 141 | − expect(foundWriteWithApproval).toBe(true); | |
| 142 | − }, { timeout: 30_000 }); | |
| 143 | −}); | |
| @@ -1,514 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * OpenAPI tool generator — parses an OpenAPI spec and produces a ToolTree. | |
| 3 | − * | |
| 4 | − * Takes an OpenAPI 3.x spec (URL, file path, or inline object), | |
| 5 | − * groups operations by tag, and converts each into a defineTool() | |
| 6 | − * with Zod schemas derived from the JSON Schema in the spec. | |
| 7 | − */ | |
| 8 | − | |
| 9 | −import SwaggerParser from "@apidevtools/swagger-parser"; | |
| 10 | −import { defineTool, type ApprovalMode, type ToolTree } from "@openassistant/core"; | |
| 11 | −import { jsonSchemaToZod, jsonSchemaToTypeString, type JsonSchema } from "./json-schema-to-ts.js"; | |
| 12 | −import { z } from "zod"; | |
| 13 | − | |
| 14 | −// --------------------------------------------------------------------------- | |
| 15 | −// Config | |
| 16 | −// --------------------------------------------------------------------------- | |
| 17 | − | |
| 18 | −export interface OpenApiToolSource { | |
| 19 | − /** Namespace in the tool tree: `tools.<name>.<tag>.<operation>` */ | |
| 20 | − readonly name: string; | |
| 21 | − /** URL or file path to the OpenAPI spec, or an inline spec object. */ | |
| 22 | − readonly spec: string | Record<string, unknown>; | |
| 23 | − /** Authentication configuration. */ | |
| 24 | − readonly auth?: OpenApiAuth | undefined; | |
| 25 | − /** Per-operation overrides. Key is the operationId. */ | |
| 26 | − readonly overrides?: Readonly<Record<string, { | |
| 27 | − readonly approval?: ApprovalMode | undefined; | |
| 28 | − }>> | undefined; | |
| 29 | − /** Default approval mode for read operations (GET/HEAD/OPTIONS). Defaults to "auto". */ | |
| 30 | − readonly defaultReadApproval?: ApprovalMode | undefined; | |
| 31 | − /** Default approval mode for write operations (POST/PUT/DELETE/PATCH). Defaults to "required". */ | |
| 32 | − readonly defaultWriteApproval?: ApprovalMode | undefined; | |
| 33 | − /** Override the base URL from the spec. Useful when the spec has no `servers` or you're targeting a different environment. */ | |
| 34 | − readonly baseUrl?: string | undefined; | |
| 35 | −} | |
| 36 | − | |
| 37 | −export type OpenApiAuth = | |
| 38 | − | { readonly type: "basic"; readonly username: string; readonly password: string } | |
| 39 | − | { readonly type: "bearer"; readonly token: string } | |
| 40 | − | { readonly type: "apiKey"; readonly header: string; readonly value: string }; | |
| 41 | − | |
| 42 | −// --------------------------------------------------------------------------- | |
| 43 | −// Result | |
| 44 | −// --------------------------------------------------------------------------- | |
| 45 | − | |
| 46 | −export interface OpenApiGenerateResult { | |
| 47 | − /** The generated tool tree, namespaced under the source name. */ | |
| 48 | − readonly tools: ToolTree; | |
| 49 | − /** TypeScript declarations for the typechecker. */ | |
| 50 | − readonly typeDeclaration: string; | |
| 51 | − /** Human-readable descriptions for the LLM prompt. */ | |
| 52 | − readonly promptGuidance: string; | |
| 53 | −} | |
| 54 | − | |
| 55 | −// --------------------------------------------------------------------------- | |
| 56 | −// Internal types | |
| 57 | −// --------------------------------------------------------------------------- | |
| 58 | − | |
| 59 | −interface ParsedOperation { | |
| 60 | − tag: string; | |
| 61 | − operationId: string; | |
| 62 | − method: string; | |
| 63 | − path: string; | |
| 64 | − summary: string; | |
| 65 | − description: string; | |
| 66 | − parameters: ParsedParameter[]; | |
| 67 | − requestBodySchema: JsonSchema | undefined; | |
| 68 | − responseSchema: JsonSchema | undefined; | |
| 69 | −} | |
| 70 | − | |
| 71 | −interface ParsedParameter { | |
| 72 | − name: string; | |
| 73 | − in: "path" | "query" | "header" | "cookie"; | |
| 74 | − required: boolean; | |
| 75 | − schema: JsonSchema; | |
| 76 | − description?: string | undefined; | |
| 77 | −} | |
| 78 | − | |
| 79 | −// --------------------------------------------------------------------------- | |
| 80 | −// Helpers | |
| 81 | −// --------------------------------------------------------------------------- | |
| 82 | − | |
| 83 | −const READ_METHODS = new Set(["get", "head", "options"]); | |
| 84 | −const MAX_APPROVAL_PREVIEW = 240; | |
| 85 | − | |
| 86 | − | |
| 87 | −function buildAuthHeaders(auth: OpenApiAuth | undefined): Record<string, string> { | |
| 88 | − if (!auth) return {}; | |
| 89 | − switch (auth.type) { | |
| 90 | − case "basic": | |
| 91 | − return { Authorization: `Basic ${btoa(`${auth.username}:${auth.password}`)}` }; | |
| 92 | − case "bearer": | |
| 93 | − return { Authorization: `Bearer ${auth.token}` }; | |
| 94 | − case "apiKey": | |
| 95 | − return { [auth.header]: auth.value }; | |
| 96 | − } | |
| 97 | −} | |
| 98 | − | |
| 99 | −function buildUrl( | |
| 100 | − baseUrl: string, | |
| 101 | − path: string, | |
| 102 | − params: ParsedParameter[], | |
| 103 | − input: Record<string, unknown>, | |
| 104 | −): { url: string; remainingInput: Record<string, unknown> } { | |
| 105 | − let resolvedPath = path; | |
| 106 | − const queryParams: string[] = []; | |
| 107 | − const remainingInput = { ...input }; | |
| 108 | − | |
| 109 | − for (const param of params) { | |
| 110 | − const value = input[param.name]; | |
| 111 | − if (value === undefined) continue; | |
| 112 | − | |
| 113 | − if (param.in === "path") { | |
| 114 | − resolvedPath = resolvedPath.replace(`{${param.name}}`, encodeURIComponent(String(value))); | |
| 115 | − delete remainingInput[param.name]; | |
| 116 | − } else if (param.in === "query") { | |
| 117 | − queryParams.push(`${encodeURIComponent(param.name)}=${encodeURIComponent(String(value))}`); | |
| 118 | − delete remainingInput[param.name]; | |
| 119 | − } | |
| 120 | − } | |
| 121 | − | |
| 122 | − const queryString = queryParams.length > 0 ? `?${queryParams.join("&")}` : ""; | |
| 123 | − return { | |
| 124 | − url: `${baseUrl}${resolvedPath}${queryString}`, | |
| 125 | − remainingInput, | |
| 126 | − }; | |
| 127 | −} | |
| 128 | − | |
| 129 | −function sanitizeOperationId(operationId: string): string { | |
| 130 | − // Convert to camelCase-safe identifier | |
| 131 | − return operationId | |
| 132 | − .replace(/[^a-zA-Z0-9_]/g, "_") | |
| 133 | − .replace(/_+/g, "_") | |
| 134 | − .replace(/^_|_$/g, ""); | |
| 135 | −} | |
| 136 | − | |
| 137 | −function sanitizeTag(tag: string): string { | |
| 138 | − return tag | |
| 139 | − .toLowerCase() | |
| 140 | − .replace(/[^a-zA-Z0-9_]/g, "_") | |
| 141 | − .replace(/_+/g, "_") | |
| 142 | − .replace(/^_|_$/g, "") || "default"; | |
| 143 | −} | |
| 144 | − | |
| 145 | −function isRecord(value: unknown): value is Record<string, unknown> { | |
| 146 | − return typeof value === "object" && value !== null && !Array.isArray(value); | |
| 147 | −} | |
| 148 | − | |
| 149 | −function toPreview(value: unknown): string { | |
| 150 | − if (value === undefined) return "undefined"; | |
| 151 | − if (value === null) return "null"; | |
| 152 | − try { | |
| 153 | − const text = typeof value === "string" ? value : JSON.stringify(value); | |
| 154 | − return text.length > MAX_APPROVAL_PREVIEW | |
| 155 | − ? `${text.slice(0, MAX_APPROVAL_PREVIEW)}...` | |
| 156 | − : text; | |
| 157 | − } catch { | |
| 158 | − return String(value); | |
| 159 | − } | |
| 160 | −} | |
| 161 | − | |
| 162 | −function actionFromMethod(method: string): "create" | "update" | "delete" | "read" | "execute" { | |
| 163 | − switch (method.toLowerCase()) { | |
| 164 | − case "get": | |
| 165 | − case "head": | |
| 166 | − case "options": | |
| 167 | − return "read"; | |
| 168 | − case "post": | |
| 169 | − return "create"; | |
| 170 | − case "put": | |
| 171 | − case "patch": | |
| 172 | − return "update"; | |
| 173 | − case "delete": | |
| 174 | − return "delete"; | |
| 175 | − default: | |
| 176 | − return "execute"; | |
| 177 | − } | |
| 178 | −} | |
| 179 | − | |
| 180 | −function resourceTypeFromPath(path: string): string | undefined { | |
| 181 | − const segments = path.split("/").filter(Boolean).filter((segment) => !segment.startsWith("{")); | |
| 182 | − const raw = segments.at(-1); | |
| 183 | − if (!raw) return undefined; | |
| 184 | − const normalized = raw.endsWith("s") ? raw.slice(0, -1) : raw; | |
| 185 | − return normalized.replace(/[_-]/g, " "); | |
| 186 | −} | |
| 187 | − | |
| 188 | −function extractIds(input: unknown): string[] { | |
| 189 | − if (!isRecord(input)) return []; | |
| 190 | − const ids: string[] = []; | |
| 191 | − for (const key of ["id", "ids", "idOrName", "name", "slug", "projectId"]) { | |
| 192 | − const value = input[key]; | |
| 193 | − if (typeof value === "string" || typeof value === "number") { | |
| 194 | − ids.push(String(value)); | |
| 195 | − continue; | |
| 196 | − } | |
| 197 | − if (Array.isArray(value)) { | |
| 198 | − for (const item of value) { | |
| 199 | − if (typeof item === "string" || typeof item === "number") { | |
| 200 | − ids.push(String(item)); | |
| 201 | − } | |
| 202 | − } | |
| 203 | − } | |
| 204 | − } | |
| 205 | − return ids.slice(0, 5); | |
| 206 | −} | |
| 207 | − | |
| 208 | −function buildSelector(input: unknown): string | undefined { | |
| 209 | − if (!isRecord(input)) return undefined; | |
| 210 | − const selectors: string[] = []; | |
| 211 | − for (const [key, value] of Object.entries(input)) { | |
| 212 | − if (key === "id" || key === "ids" || key === "idOrName") continue; | |
| 213 | − if (value === undefined || value === null) continue; | |
| 214 | − if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { | |
| 215 | − selectors.push(`${key}=${String(value)}`); | |
| 216 | − if (selectors.length >= 3) break; | |
| 217 | − } | |
| 218 | − } | |
| 219 | − return selectors.length > 0 ? selectors.join(", ") : undefined; | |
| 220 | −} | |
| 221 | − | |
| 222 | −function buildOpenApiApprovalPreview( | |
| 223 | − op: Pick<ParsedOperation, "method" | "path" | "operationId">, | |
| 224 | − input: unknown, | |
| 225 | −) { | |
| 226 | − const action = actionFromMethod(op.method); | |
| 227 | − const ids = extractIds(input); | |
| 228 | − const selector = buildSelector(input); | |
| 229 | − const resourceType = resourceTypeFromPath(op.path); | |
| 230 | − const title = `${op.method.toUpperCase()} ${op.path}`; | |
| 231 | − const detailsParts = [ | |
| 232 | − `Operation: ${op.operationId}`, | |
| 233 | − ids.length > 0 ? `Target: ${ids.join(", ")}` : undefined, | |
| 234 | − selector ? `Selector: ${selector}` : undefined, | |
| 235 | − `Arguments: ${toPreview(input)}`, | |
| 236 | − ].filter(Boolean); | |
| 237 | − return { | |
| 238 | − title, | |
| 239 | − details: detailsParts.join("\n"), | |
| 240 | − action, | |
| 241 | − resourceType, | |
| 242 | − resourceIds: ids.length > 0 ? ids : undefined, | |
| 243 | − isDestructive: action === "delete", | |
| 244 | − selector, | |
| 245 | − } as const; | |
| 246 | −} | |
| 247 | − | |
| 248 | −// --------------------------------------------------------------------------- | |
| 249 | −// Spec parsing | |
| 250 | −// --------------------------------------------------------------------------- | |
| 251 | − | |
| 252 | −function parseOperations(api: Record<string, unknown>): ParsedOperation[] { | |
| 253 | − const paths = (api as { paths?: Record<string, Record<string, unknown>> }).paths ?? {}; | |
| 254 | − const operations: ParsedOperation[] = []; | |
| 255 | − | |
| 256 | − for (const [path, pathItem] of Object.entries(paths)) { | |
| 257 | − if (!pathItem || typeof pathItem !== "object") continue; | |
| 258 | − | |
| 259 | − for (const method of ["get", "post", "put", "delete", "patch", "head", "options"]) { | |
| 260 | − const operation = (pathItem as Record<string, unknown>)[method] as Record<string, unknown> | undefined; | |
| 261 | − if (!operation) continue; | |
| 262 | − | |
| 263 | − const tags = (operation["tags"] as string[] | undefined) ?? ["default"]; | |
| 264 | − const tag = sanitizeTag(tags[0] ?? "default"); | |
| 265 | − const operationId = sanitizeOperationId( | |
| 266 | − (operation["operationId"] as string | undefined) ?? `${method}_${path.replace(/\//g, "_")}`, | |
| 267 | − ); | |
| 268 | − const summary = (operation["summary"] as string | undefined) ?? ""; | |
| 269 | − const description = (operation["description"] as string | undefined) ?? summary; | |
| 270 | − | |
| 271 | − // Parse parameters | |
| 272 | − const rawParams = [ | |
| 273 | − ...((pathItem["parameters"] as unknown[] | undefined) ?? []), | |
| 274 | − ...((operation["parameters"] as unknown[] | undefined) ?? []), | |
| 275 | − ]; | |
| 276 | − const parameters: ParsedParameter[] = rawParams | |
| 277 | − .filter((p): p is Record<string, unknown> => !!p && typeof p === "object") | |
| 278 | − .map((p) => ({ | |
| 279 | − name: String(p["name"] ?? ""), | |
| 280 | − in: String(p["in"] ?? "query") as ParsedParameter["in"], | |
| 281 | − required: Boolean(p["required"]), | |
| 282 | − schema: (p["schema"] as JsonSchema | undefined) ?? { type: "string" }, | |
| 283 | − description: p["description"] as string | undefined, | |
| 284 | − })); | |
| 285 | − | |
| 286 | − // Parse request body | |
| 287 | − let requestBodySchema: JsonSchema | undefined; | |
| 288 | − const requestBody = operation["requestBody"] as Record<string, unknown> | undefined; | |
| 289 | − if (requestBody) { | |
| 290 | − const content = requestBody["content"] as Record<string, unknown> | undefined; | |
| 291 | − if (content) { | |
| 292 | − const jsonContent = (content["application/json"] ?? content["*/*"]) as Record<string, unknown> | undefined; | |
| 293 | − if (jsonContent) { | |
| 294 | − requestBodySchema = jsonContent["schema"] as JsonSchema | undefined; | |
| 295 | − } | |
| 296 | − } | |
| 297 | − } | |
| 298 | − | |
| 299 | − // Parse response schema (from first 2xx response) | |
| 300 | − let responseSchema: JsonSchema | undefined; | |
| 301 | − const responses = operation["responses"] as Record<string, unknown> | undefined; | |
| 302 | − if (responses) { | |
| 303 | − for (const statusCode of Object.keys(responses)) { | |
| 304 | − if (statusCode.startsWith("2")) { | |
| 305 | − const response = responses[statusCode] as Record<string, unknown> | undefined; | |
| 306 | − if (response) { | |
| 307 | − const content = response["content"] as Record<string, unknown> | undefined; | |
| 308 | − if (content) { | |
| 309 | − const jsonContent = (content["application/json"] ?? content["*/*"]) as Record<string, unknown> | undefined; | |
| 310 | − if (jsonContent) { | |
| 311 | − responseSchema = jsonContent["schema"] as JsonSchema | undefined; | |
| 312 | − break; | |
| 313 | − } | |
| 314 | − } | |
| 315 | − } | |
| 316 | − } | |
| 317 | − } | |
| 318 | − } | |
| 319 | − | |
| 320 | − operations.push({ | |
| 321 | − tag, | |
| 322 | − operationId, | |
| 323 | − method, | |
| 324 | − path, | |
| 325 | − summary, | |
| 326 | − description: description.slice(0, 200), // Truncate long descriptions | |
| 327 | − parameters, | |
| 328 | − requestBodySchema, | |
| 329 | − responseSchema, | |
| 330 | − }); | |
| 331 | − } | |
| 332 | − } | |
| 333 | − | |
| 334 | − return operations; | |
| 335 | −} | |
| 336 | − | |
| 337 | −// --------------------------------------------------------------------------- | |
| 338 | −// Generator | |
| 339 | −// --------------------------------------------------------------------------- | |
| 340 | − | |
| 341 | −export async function generateOpenApiTools( | |
| 342 | − source: OpenApiToolSource, | |
| 343 | −): Promise<OpenApiGenerateResult> { | |
| 344 | − // Parse and dereference the spec | |
| 345 | − const api = await SwaggerParser.dereference(source.spec as string) as Record<string, unknown>; | |
| 346 | − | |
| 347 | − // Get base URL (config override > spec servers > empty) | |
| 348 | − const servers = (api["servers"] as Array<{ url: string }> | undefined) ?? []; | |
| 349 | − const baseUrl = source.baseUrl ?? servers[0]?.url ?? ""; | |
| 350 | − | |
| 351 | − const operations = parseOperations(api); | |
| 352 | − const authHeaders = buildAuthHeaders(source.auth); | |
| 353 | − | |
| 354 | − // Group operations by tag | |
| 355 | − const byTag = new Map<string, ParsedOperation[]>(); | |
| 356 | − for (const op of operations) { | |
| 357 | − const existing = byTag.get(op.tag) ?? []; | |
| 358 | − existing.push(op); | |
| 359 | − byTag.set(op.tag, existing); | |
| 360 | − } | |
| 361 | − | |
| 362 | − // Build tool tree | |
| 363 | − const namespacedTree: Record<string, Record<string, ReturnType<typeof defineTool>>> = {}; | |
| 364 | − const typeLines: string[] = []; | |
| 365 | − const guidanceLines: string[] = []; | |
| 366 | − | |
| 367 | − for (const [tag, ops] of byTag) { | |
| 368 | − namespacedTree[tag] ??= {}; | |
| 369 | − const tagTypeLines: string[] = []; | |
| 370 | − | |
| 371 | − for (const op of ops) { | |
| 372 | − const isRead = READ_METHODS.has(op.method); | |
| 373 | − const approval = | |
| 374 | − source.overrides?.[op.operationId]?.approval ?? | |
| 375 | − (isRead | |
| 376 | − ? (source.defaultReadApproval ?? "auto") | |
| 377 | − : (source.defaultWriteApproval ?? "required")); | |
| 378 | − | |
| 379 | − // Build combined input schema from parameters + request body | |
| 380 | − const inputProperties: Record<string, JsonSchema> = {}; | |
| 381 | − const inputRequired: string[] = []; | |
| 382 | − | |
| 383 | − for (const param of op.parameters) { | |
| 384 | − inputProperties[param.name] = { | |
| 385 | − ...param.schema, | |
| 386 | − description: param.description ?? param.schema.description, | |
| 387 | − }; | |
| 388 | − if (param.required) inputRequired.push(param.name); | |
| 389 | − } | |
| 390 | − | |
| 391 | − if (op.requestBodySchema) { | |
| 392 | − // Merge request body properties into input | |
| 393 | − const bodyProps = (op.requestBodySchema as { properties?: Record<string, JsonSchema> }).properties; | |
| 394 | − const bodyRequired = (op.requestBodySchema as { required?: string[] }).required ?? []; | |
| 395 | − if (bodyProps) { | |
| 396 | − for (const [key, value] of Object.entries(bodyProps)) { | |
| 397 | − inputProperties[key] = value; | |
| 398 | − } | |
| 399 | − inputRequired.push(...bodyRequired); | |
| 400 | − } else { | |
| 401 | − // If the body is a single schema without properties, use "body" as the key | |
| 402 | − inputProperties["body"] = op.requestBodySchema; | |
| 403 | − } | |
| 404 | − } | |
| 405 | − | |
| 406 | − const combinedInputSchema: JsonSchema = { | |
| 407 | − type: "object", | |
| 408 | − properties: inputProperties, | |
| 409 | − required: inputRequired.length > 0 ? inputRequired : undefined, | |
| 410 | − }; | |
| 411 | − | |
| 412 | − let argsZod: z.ZodType; | |
| 413 | − | |
| 414 | − try { | |
| 415 | − argsZod = jsonSchemaToZod(combinedInputSchema); | |
| 416 | − } catch { | |
| 417 | − // Recursive/circular schemas — fall back to z.any() | |
| 418 | − argsZod = z.any(); | |
| 419 | − } | |
| 420 | − | |
| 421 | − // Use openapi-typescript for type strings (handles allOf, oneOf, enums, etc.) | |
| 422 | − const argsTypeString = jsonSchemaToTypeString(combinedInputSchema); | |
| 423 | − const returnsTypeString = op.responseSchema | |
| 424 | − ? jsonSchemaToTypeString(op.responseSchema) | |
| 425 | − : "any"; | |
| 426 | − | |
| 427 | − const description = op.summary || op.description || `${op.method.toUpperCase()} ${op.path}`; | |
| 428 | − | |
| 429 | − namespacedTree[tag]![op.operationId] = defineTool({ | |
| 430 | − description, | |
| 431 | − approval, | |
| 432 | − args: argsZod, | |
| 433 | − returns: z.any(), // OpenAPI responses are complex; use any for runtime | |
| 434 | − metadata: { | |
| 435 | − argsType: argsTypeString, | |
| 436 | − returnsType: returnsTypeString, | |
| 437 | − }, | |
| 438 | − run: async (input: unknown) => { | |
| 439 | − const { url, remainingInput } = buildUrl( | |
| 440 | − baseUrl, | |
| 441 | − op.path, | |
| 442 | − op.parameters, | |
| 443 | − input as Record<string, unknown>, | |
| 444 | − ); | |
| 445 | − | |
| 446 | − const hasBody = !isRead && Object.keys(remainingInput).length > 0; | |
| 447 | − | |
| 448 | − const fetchInit: RequestInit = { | |
| 449 | − method: op.method.toUpperCase(), | |
| 450 | − headers: { | |
| 451 | − ...authHeaders, | |
| 452 | − ...(hasBody ? { "Content-Type": "application/json" } : {}), | |
| 453 | − "User-Agent": "openassistant/0.1.0", | |
| 454 | − }, | |
| 455 | − }; | |
| 456 | − if (hasBody) { | |
| 457 | − fetchInit.body = JSON.stringify(remainingInput); | |
| 458 | − } | |
| 459 | − | |
| 460 | − // Retry once on any fetch error (stale keep-alive sockets, DNS, etc.). | |
| 461 | − // On retry, force Connection: close to bypass dead pooled sockets. | |
| 462 | − let response: Response; | |
| 463 | − try { | |
| 464 | − response = await fetch(url, fetchInit); | |
| 465 | − } catch (firstError) { | |
| 466 | − try { | |
| 467 | − const retryInit = { | |
| 468 | − ...fetchInit, | |
| 469 | − headers: { ...(fetchInit.headers as Record<string, string>), Connection: "close" }, | |
| 470 | − }; | |
| 471 | − response = await fetch(url, retryInit); | |
| 472 | − } catch { | |
| 473 | − throw firstError; // retry failed too, throw the original | |
| 474 | − } | |
| 475 | − } | |
| 476 | − | |
| 477 | − if (!response.ok) { | |
| 478 | − const text = await response.text().catch(() => ""); | |
| 479 | − throw new Error(`HTTP ${response.status} ${response.statusText} (${op.method.toUpperCase()} ${url}): ${text.slice(0, 500)}`); | |
| 480 | − } | |
| 481 | − | |
| 482 | − const contentType = response.headers.get("content-type") ?? ""; | |
| 483 | − if (contentType.includes("json")) { | |
| 484 | − return response.json(); | |
| 485 | − } | |
| 486 | − return response.text(); | |
| 487 | − }, | |
| 488 | − formatApproval: (input) => buildOpenApiApprovalPreview(op, input), | |
| 489 | − }); | |
| 490 | − | |
| 491 | − // Build TypeScript declaration | |
| 492 | − const approvalNote = approval === "required" ? " (approval required)" : " (auto-approved)"; | |
| 493 | − tagTypeLines.push(` /** ${description} */`); | |
| 494 | − tagTypeLines.push(` ${op.operationId}(input: ${argsTypeString}): Promise<${returnsTypeString}>;`); | |
| 495 | − | |
| 496 | − guidanceLines.push( | |
| 497 | − `- tools.${source.name}.${tag}.${op.operationId}(${argsTypeString}): Promise<${returnsTypeString}>${approvalNote} — ${description}`, | |
| 498 | − ); | |
| 499 | − } | |
| 500 | − | |
| 501 | − typeLines.push(` ${tag}: {`); | |
| 502 | − typeLines.push(tagTypeLines.join("\n")); | |
| 503 | − typeLines.push(` };`); | |
| 504 | − } | |
| 505 | − | |
| 506 | − const typeDeclaration = `${source.name}: {\n${typeLines.join("\n")}\n}`; | |
| 507 | − const promptGuidance = guidanceLines.join("\n"); | |
| 508 | − | |
| 509 | − return { | |
| 510 | − tools: { [source.name]: namespacedTree }, | |
| 511 | − typeDeclaration, | |
| 512 | − promptGuidance, | |
| 513 | − }; | |
| 514 | −} | |
| @@ -1,12 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "extends": "../../tsconfig.base.json", | |
| 3 | − "compilerOptions": { | |
| 4 | − "composite": true, | |
| 5 | − "rootDir": "src", | |
| 6 | − "outDir": "dist" | |
| 7 | − }, | |
| 8 | − "include": ["src"], | |
| 9 | − "references": [ | |
| 10 | − { "path": "../core" } | |
| 11 | − ] | |
| 12 | −} | |
| @@ -1,22 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "compilerOptions": { | |
| 3 | − "target": "ES2022", | |
| 4 | − "module": "NodeNext", | |
| 5 | − "moduleResolution": "NodeNext", | |
| 6 | − "moduleDetection": "force", | |
| 7 | − "verbatimModuleSyntax": true, | |
| 8 | − "rewriteRelativeImportExtensions": true, | |
| 9 | − "strict": true, | |
| 10 | − "exactOptionalPropertyTypes": true, | |
| 11 | − "noUncheckedIndexedAccess": true, | |
| 12 | − "noImplicitOverride": true, | |
| 13 | − "noUnusedLocals": true, | |
| 14 | − "noUnusedParameters": true, | |
| 15 | − "skipLibCheck": true, | |
| 16 | − "plugins": [ | |
| 17 | − { | |
| 18 | − "name": "@effect/language-service" | |
| 19 | − } | |
| 20 | − ] | |
| 21 | − } | |
| 22 | −} | |
| @@ -1,9 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "files": [], | |
| 3 | − "references": [ | |
| 4 | − { "path": "./packages/core" }, | |
| 5 | − { "path": "./packages/tool-gen" }, | |
| 6 | − { "path": "./apps/server" }, | |
| 7 | − { "path": "./apps/executor" } | |
| 8 | − ] | |
| 9 | −} | |
| @@ -1,8 +0,0 @@ | ||
| 1 | −import { defineConfig } from "vitest/config"; | |
| 2 | − | |
| 3 | −export default defineConfig({ | |
| 4 | − test: { | |
| 5 | − include: ["packages/**/*.test.ts", "apps/**/*.test.ts"], | |
| 6 | − exclude: ["**/node_modules/**", ".context/**", ".reference/**"], | |
| 7 | − }, | |
| 8 | −}); | |
| @@ -3,7 +3,7 @@ · 2 unmodified lines | ||
| 3 | 3 | "private": true, |
| 4 | 4 | "type": "module", |
| 5 | 5 | "scripts": { |
| 6 | − "test": "bun test packages/core/src/ packages/agent-executor-adapter/src/", | |
| 6 | + "test": "bun test packages/core/src/", | |
| 7 | 7 | "dev:server": "bun run --filter @assistant/server dev", |
| 8 | 8 | "dev:bot": "bun run --filter @assistant/bot dev" |
| 9 | 9 | }, |
| @@ -1,18 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "name": "@assistant/agent-executor-adapter", | |
| 3 | − "private": true, | |
| 4 | − "type": "module", | |
| 5 | − "exports": { | |
| 6 | − ".": "./src/index.ts" | |
| 7 | − }, | |
| 8 | − "scripts": { | |
| 9 | − "test": "bun test src/" | |
| 10 | − }, | |
| 11 | − "dependencies": { | |
| 12 | − "@elysiajs/eden": "^1.4.6", | |
| 13 | − "elysia": "^1.4.22" | |
| 14 | − }, | |
| 15 | − "devDependencies": { | |
| 16 | − "@types/bun": "latest" | |
| 17 | − } | |
| 18 | −} | |
| @@ -1,87 +0,0 @@ | ||
| 1 | −import { expect, test } from "bun:test"; | |
| 2 | −import { Elysia, t } from "elysia"; | |
| 3 | −import { treaty } from "@elysiajs/eden"; | |
| 4 | − | |
| 5 | −// Minimal Elysia server that mimics the executor's task creation route | |
| 6 | −function createTestServer() { | |
| 7 | − return new Elysia() | |
| 8 | − .get("/api/health", () => ({ ok: true as const, tools: 5 })) | |
| 9 | − .post("/api/tasks", ({ body }) => ({ | |
| 10 | − taskId: "task_test_1", | |
| 11 | − status: "queued" as const, | |
| 12 | − }), { | |
| 13 | − body: t.Object({ | |
| 14 | − code: t.String(), | |
| 15 | − timeoutMs: t.Optional(t.Number()), | |
| 16 | − runtimeId: t.Optional(t.String()), | |
| 17 | − metadata: t.Optional(t.Record(t.String(), t.Unknown())), | |
| 18 | − workspaceId: t.String(), | |
| 19 | − actorId: t.String(), | |
| 20 | − clientId: t.Optional(t.String()), | |
| 21 | − }), | |
| 22 | − }) | |
| 23 | − .post("/api/auth/anonymous/bootstrap", ({ body }) => ({ | |
| 24 | − sessionId: body.sessionId ?? "sess_test", | |
| 25 | − workspaceId: "ws_anon", | |
| 26 | − actorId: "actor_anon", | |
| 27 | − clientId: "client_anon", | |
| 28 | − createdAt: Date.now(), | |
| 29 | − lastSeenAt: Date.now(), | |
| 30 | − }), { | |
| 31 | − body: t.Object({ | |
| 32 | − sessionId: t.Optional(t.String()), | |
| 33 | − }), | |
| 34 | − }) | |
| 35 | − .listen(0); | |
| 36 | −} | |
| 37 | − | |
| 38 | −test("Eden Treaty client can create a task with full type safety", async () => { | |
| 39 | − const server = createTestServer(); | |
| 40 | − const client = treaty<typeof server>(`http://127.0.0.1:${server.server!.port}`); | |
| 41 | − | |
| 42 | − try { | |
| 43 | − const { data, error } = await client.api.tasks.post({ | |
| 44 | − code: "console.log('hello')", | |
| 45 | − workspaceId: "ws_test", | |
| 46 | − actorId: "actor_test", | |
| 47 | − }); | |
| 48 | − | |
| 49 | − expect(error).toBeNull(); | |
| 50 | − expect(data!.taskId).toBe("task_test_1"); | |
| 51 | − expect(data!.status).toBe("queued"); | |
| 52 | − } finally { | |
| 53 | − server.stop(true); | |
| 54 | − } | |
| 55 | −}); | |
| 56 | − | |
| 57 | −test("Eden Treaty client can check health", async () => { | |
| 58 | − const server = createTestServer(); | |
| 59 | − const client = treaty<typeof server>(`http://127.0.0.1:${server.server!.port}`); | |
| 60 | − | |
| 61 | − try { | |
| 62 | − const { data, error } = await client.api.health.get(); | |
| 63 | − | |
| 64 | − expect(error).toBeNull(); | |
| 65 | − expect(data!.ok).toBe(true); | |
| 66 | − expect(data!.tools).toBe(5); | |
| 67 | − } finally { | |
| 68 | − server.stop(true); | |
| 69 | − } | |
| 70 | −}); | |
| 71 | − | |
| 72 | −test("Eden Treaty client can bootstrap anonymous context", async () => { | |
| 73 | − const server = createTestServer(); | |
| 74 | − const client = treaty<typeof server>(`http://127.0.0.1:${server.server!.port}`); | |
| 75 | − | |
| 76 | − try { | |
| 77 | − const { data, error } = await client.api.auth.anonymous.bootstrap.post({ | |
| 78 | − sessionId: "my-session", | |
| 79 | − }); | |
| 80 | − | |
| 81 | − expect(error).toBeNull(); | |
| 82 | − expect(data!.workspaceId).toBe("ws_anon"); | |
| 83 | − expect(data!.sessionId).toBe("my-session"); | |
| 84 | − } finally { | |
| 85 | − server.stop(true); | |
| 86 | − } | |
| 87 | −}); | |
| @@ -1,8 +0,0 @@ | ||
| 1 | −import { treaty } from "@elysiajs/eden"; | |
| 2 | −import type { App } from "@executor/server/src/index"; | |
| 3 | − | |
| 4 | −export function createExecutorClient(baseUrl: string) { | |
| 5 | − return treaty<App>(baseUrl); | |
| 6 | −} | |
| 7 | − | |
| 8 | −export type ExecutorClient = ReturnType<typeof createExecutorClient>; | |
| @@ -1,14 +0,0 @@ | ||
| 1 | −{ | |
| 2 | − "compilerOptions": { | |
| 3 | − "target": "ESNext", | |
| 4 | − "module": "ESNext", | |
| 5 | − "moduleResolution": "bundler", | |
| 6 | − "strict": true, | |
| 7 | − "esModuleInterop": true, | |
| 8 | − "skipLibCheck": true, | |
| 9 | − "paths": { | |
| 10 | − "@executor/server/*": ["../../../executor/apps/server/*"] | |
| 11 | − } | |
| 12 | − }, | |
| 13 | − "include": ["src"] | |
| 14 | −} | |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
| @@ -1,10 +1,16 @@ | ||
| 1 | 1 | { |
| 2 | − "extends": "../../tsconfig.base.json", | |
| 3 | 2 | "compilerOptions": { |
| 4 | − "exactOptionalPropertyTypes": false, | |
| 5 | − "jsx": "react-jsx", | |
| 3 | + "target": "ES2022", | |
| 6 | 4 | "module": "ESNext", |
| 7 | 5 | "moduleResolution": "Bundler", |
| 6 | + "moduleDetection": "force", | |
| 7 | + "verbatimModuleSyntax": true, | |
| 8 | + "jsx": "react-jsx", | |
| 9 | + "strict": true, | |
| 10 | + "exactOptionalPropertyTypes": false, | |
| 11 | + "noUncheckedIndexedAccess": true, | |
| 12 | + "noImplicitOverride": true, | |
| 13 | + "skipLibCheck": true, | |
| 8 | 14 | "rootDir": "src" |
| 9 | 15 | }, |
| 10 | 16 | "include": [ |
| @@ -12,31 +12,6 @@ · 11 unmodified lines | ||
| 12 | 12 | "typescript": "latest", |
| 13 | 13 | }, |
| 14 | 14 | }, |
| 15 | − "assistant/legacy/openassistant/packages/reacord": { | |
| 16 | − "name": "@openassistant/reacord", | |
| 17 | − "dependencies": { | |
| 18 | − "@effect-atom/atom": "^0.4.11", | |
| 19 | − "@effect-atom/atom-react": "^0.4.4", | |
| 20 | − "discord.js": "^14.17.3", | |
| 21 | − "effect": "latest", | |
| 22 | − "react": "^19.0.0", | |
| 23 | − "react-reconciler": "^0.31.0", | |
| 24 | − }, | |
| 25 | − "devDependencies": { | |
| 26 | − "@types/react": "^19.0.0", | |
| 27 | − "@types/react-reconciler": "^0.31.0", | |
| 28 | − }, | |
| 29 | − }, | |
| 30 | − "assistant/packages/agent-executor-adapter": { | |
| 31 | − "name": "@assistant/agent-executor-adapter", | |
| 32 | − "dependencies": { | |
| 33 | − "@elysiajs/eden": "^1.4.6", | |
| 34 | − "elysia": "^1.4.22", | |
| 35 | − }, | |
| 36 | − "devDependencies": { | |
| 37 | − "@types/bun": "latest", | |
| 38 | − }, | |
| 39 | − }, | |
| 40 | 15 | "assistant/packages/bot": { |
| 41 | 16 | "name": "@assistant/bot", |
| 42 | 17 | "dependencies": { |
| @@ -61,11 +36,24 @@ · 18 unmodified lines | ||
| 61 | 36 | "@modelcontextprotocol/sdk": "^1.26.0", |
| 62 | 37 | }, |
| 63 | 38 | "devDependencies": { |
| 64 | − "@elysiajs/eden": "^1.4.6", | |
| 65 | − "elysia": "^1.4.22", | |
| 66 | 39 | "zod": "^4.3.6", |
| 67 | 40 | }, |
| 68 | 41 | }, |
| 42 | + "assistant/packages/reacord": { | |
| 43 | + "name": "@openassistant/reacord", | |
| 44 | + "dependencies": { | |
| 45 | + "@effect-atom/atom": "^0.4.11", | |
| 46 | + "@effect-atom/atom-react": "^0.4.4", | |
| 47 | + "discord.js": "^14.17.3", | |
| 48 | + "effect": "latest", | |
| 49 | + "react": "^19.0.0", | |
| 50 | + "react-reconciler": "^0.31.0", | |
| 51 | + }, | |
| 52 | + "devDependencies": { | |
| 53 | + "@types/react": "^19.0.0", | |
| 54 | + "@types/react-reconciler": "^0.31.0", | |
| 55 | + }, | |
| 56 | + }, | |
| 69 | 57 | "assistant/packages/server": { |
| 70 | 58 | "name": "@assistant/server", |
| 71 | 59 | "dependencies": { |
| @@ -81,12 +69,11 @@ · 9 unmodified lines | ||
| 81 | 69 | "executor": { |
| 82 | 70 | "name": "executor-monorepo", |
| 83 | 71 | "dependencies": { |
| 84 | − "convex": "latest", | |
| 72 | + "convex": "^1.31.7", | |
| 85 | 73 | }, |
| 86 | 74 | "devDependencies": { |
| 87 | 75 | "@types/bun": "latest", |
| 88 | 76 | "convex-test": "^0.0.41", |
| 89 | − "esbuild": "^0.27.3", | |
| 90 | 77 | "typescript": "latest", |
| 91 | 78 | }, |
| 92 | 79 | }, |
| @@ -94,10 +81,10 @@ · 1 unmodified line | ||
| 94 | 81 | "name": "@executor/server", |
| 95 | 82 | "dependencies": { |
| 96 | 83 | "@apidevtools/swagger-parser": "^12.1.0", |
| 97 | − "@elysiajs/eden": "^1.4.6", | |
| 84 | + "@executor/contracts": "workspace:*", | |
| 98 | 85 | "@modelcontextprotocol/sdk": "^1.26.0", |
| 99 | 86 | "@vercel/sandbox": "latest", |
| 100 | − "convex": "latest", | |
| 87 | + "convex": "^1.31.7", | |
| 101 | 88 | "elysia": "^1.4.22", |
| 102 | 89 | "openapi-typescript": "^7.10.1", |
| 103 | 90 | "zod": "^4.3.6", |
| @@ -111,10 +98,11 @@ · 7 unmodified lines | ||
| 111 | 98 | "name": "@executor/web", |
| 112 | 99 | "version": "0.1.0", |
| 113 | 100 | "dependencies": { |
| 101 | + "@executor/contracts": "workspace:*", | |
| 114 | 102 | "@monaco-editor/react": "^4.7.0", |
| 115 | 103 | "class-variance-authority": "^0.7.1", |
| 116 | 104 | "clsx": "^2.1.1", |
| 117 | − "convex": "latest", | |
| 105 | + "convex": "^1.31.7", | |
| 118 | 106 | "lucide-react": "^0.563.0", |
| 119 | 107 | "monaco-editor": "^0.55.1", |
| 120 | 108 | "next": "16.1.6", |
| @@ -139,6 +127,10 @@ · 18 unmodified lines | ||
| 139 | 127 | "typescript": "^5", |
| 140 | 128 | }, |
| 141 | 129 | }, |
| 130 | + "executor/packages/contracts": { | |
| 131 | + "name": "@executor/contracts", | |
| 132 | + "version": "0.0.1", | |
| 133 | + }, | |
| 142 | 134 | }, |
| 143 | 135 | "trustedDependencies": [ |
| 144 | 136 | "sharp", |
| @@ -159,8 +151,6 @@ · 14 unmodified lines | ||
| 159 | 151 | |
| 160 | 152 | "@apidevtools/swagger-parser": ["@apidevtools/swagger-parser@12.1.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "14.0.1", "@apidevtools/openapi-schemas": "^2.1.0", "@apidevtools/swagger-methods": "^3.0.2", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "call-me-maybe": "^1.0.2" }, "peerDependencies": { "openapi-types": ">=7" } }, "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng=="], |
| 161 | 153 | |
| 162 | − "@assistant/agent-executor-adapter": ["@assistant/agent-executor-adapter@workspace:assistant/packages/agent-executor-adapter"], | |
| 163 | − | |
| 164 | 154 | "@assistant/bot": ["@assistant/bot@workspace:assistant/packages/bot"], |
| 165 | 155 | |
| 166 | 156 | "@assistant/core": ["@assistant/core@workspace:assistant/packages/core"], |
| @@ -329,57 +319,57 @@ · 162 unmodified lines | ||
| 329 | 319 | |
| 330 | 320 | "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], |
| 331 | 321 | |
| 332 | − "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], | |
| 322 | + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="], | |
| 333 | 323 | |
| 334 | − "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], | |
| 324 | + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.0", "", { "os": "android", "cpu": "arm" }, "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ=="], | |
| 335 | 325 | |
| 336 | − "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], | |
| 326 | + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ=="], | |
| 337 | 327 | |
| 338 | − "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], | |
| 328 | + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.0", "", { "os": "android", "cpu": "x64" }, "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q=="], | |
| 339 | 329 | |
| 340 | − "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], | |
| 330 | + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg=="], | |
| 341 | 331 | |
| 342 | − "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], | |
| 332 | + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g=="], | |
| 343 | 333 | |
| 344 | − "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], | |
| 334 | + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw=="], | |
| 345 | 335 | |
| 346 | − "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], | |
| 336 | + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g=="], | |
| 347 | 337 | |
| 348 | − "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], | |
| 338 | + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ=="], | |
| 349 | 339 | |
| 350 | − "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], | |
| 340 | + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ=="], | |
| 351 | 341 | |
| 352 | − "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], | |
| 342 | + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw=="], | |
| 353 | 343 | |
| 354 | − "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], | |
| 344 | + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg=="], | |
| 355 | 345 | |
| 356 | − "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], | |
| 346 | + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg=="], | |
| 357 | 347 | |
| 358 | − "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], | |
| 348 | + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA=="], | |
| 359 | 349 | |
| 360 | − "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], | |
| 350 | + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ=="], | |
| 361 | 351 | |
| 362 | − "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], | |
| 352 | + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w=="], | |
| 363 | 353 | |
| 364 | − "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], | |
| 354 | + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw=="], | |
| 365 | 355 | |
| 366 | − "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], | |
| 356 | + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w=="], | |
| 367 | 357 | |
| 368 | − "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], | |
| 358 | + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.0", "", { "os": "none", "cpu": "x64" }, "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA=="], | |
| 369 | 359 | |
| 370 | − "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], | |
| 360 | + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ=="], | |
| 371 | 361 | |
| 372 | − "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], | |
| 362 | + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A=="], | |
| 373 | 363 | |
| 374 | − "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], | |
| 364 | + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA=="], | |
| 375 | 365 | |
| 376 | − "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], | |
| 366 | + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA=="], | |
| 377 | 367 | |
| 378 | − "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], | |
| 368 | + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg=="], | |
| 379 | 369 | |
| 380 | − "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], | |
| 370 | + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ=="], | |
| 381 | 371 | |
| 382 | − "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], | |
| 372 | + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="], | |
| 383 | 373 | |
| 384 | 374 | "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], |
| 385 | 375 | |
| @@ -399,6 +389,8 @@ · 13 unmodified lines | ||
| 399 | 389 | |
| 400 | 390 | "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], |
| 401 | 391 | |
| 392 | + "@executor/contracts": ["@executor/contracts@workspace:executor/packages/contracts"], | |
| 393 | + | |
| 402 | 394 | "@executor/server": ["@executor/server@workspace:executor/apps/server"], |
| 403 | 395 | |
| 404 | 396 | "@executor/web": ["@executor/web@workspace:executor/apps/web"], |
| @@ -565,7 +557,7 @@ · 160 unmodified lines | ||
| 565 | 557 | |
| 566 | 558 | "@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="], |
| 567 | 559 | |
| 568 | − "@openassistant/reacord": ["@openassistant/reacord@workspace:assistant/legacy/openassistant/packages/reacord"], | |
| 560 | + "@openassistant/reacord": ["@openassistant/reacord@workspace:assistant/packages/reacord"], | |
| 569 | 561 | |
| 570 | 562 | "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], |
| 571 | 563 | |
| @@ -1199,7 +1191,7 @@ · 627 unmodified lines | ||
| 1199 | 1191 | |
| 1200 | 1192 | "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], |
| 1201 | 1193 | |
| 1202 | − "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], | |
| 1194 | + "esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], | |
| 1203 | 1195 | |
| 1204 | 1196 | "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], |
| 1205 | 1197 | |
| @@ -2171,8 +2163,6 @@ · 965 unmodified lines | ||
| 2171 | 2163 | |
| 2172 | 2164 | "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], |
| 2173 | 2165 | |
| 2174 | − "convex/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], | |
| 2175 | − | |
| 2176 | 2166 | "cosmiconfig/parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], |
| 2177 | 2167 | |
| 2178 | 2168 | "degenerator/ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], |
| @@ -2275,58 +2265,6 @@ · 96 unmodified lines | ||
| 2275 | 2265 | |
| 2276 | 2266 | "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], |
| 2277 | 2267 | |
| 2278 | − "convex/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="], | |
| 2279 | − | |
| 2280 | − "convex/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.0", "", { "os": "android", "cpu": "arm" }, "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ=="], | |
| 2281 | − | |
| 2282 | − "convex/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ=="], | |
| 2283 | − | |
| 2284 | − "convex/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.0", "", { "os": "android", "cpu": "x64" }, "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q=="], | |
| 2285 | − | |
| 2286 | − "convex/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg=="], | |
| 2287 | − | |
| 2288 | − "convex/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g=="], | |
| 2289 | − | |
| 2290 | − "convex/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw=="], | |
| 2291 | − | |
| 2292 | − "convex/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g=="], | |
| 2293 | − | |
| 2294 | − "convex/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ=="], | |
| 2295 | − | |
| 2296 | − "convex/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ=="], | |
| 2297 | − | |
| 2298 | − "convex/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw=="], | |
| 2299 | − | |
| 2300 | − "convex/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg=="], | |
| 2301 | − | |
| 2302 | − "convex/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg=="], | |
| 2303 | − | |
| 2304 | − "convex/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA=="], | |
| 2305 | − | |
| 2306 | − "convex/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ=="], | |
| 2307 | − | |
| 2308 | − "convex/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w=="], | |
| 2309 | − | |
| 2310 | − "convex/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw=="], | |
| 2311 | − | |
| 2312 | − "convex/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w=="], | |
| 2313 | − | |
| 2314 | − "convex/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.0", "", { "os": "none", "cpu": "x64" }, "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA=="], | |
| 2315 | − | |
| 2316 | − "convex/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ=="], | |
| 2317 | − | |
| 2318 | − "convex/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A=="], | |
| 2319 | − | |
| 2320 | − "convex/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA=="], | |
| 2321 | − | |
| 2322 | − "convex/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA=="], | |
| 2323 | − | |
| 2324 | − "convex/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg=="], | |
| 2325 | − | |
| 2326 | − "convex/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ=="], | |
| 2327 | − | |
| 2328 | − "convex/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="], | |
| 2329 | − | |
| 2330 | 2268 | "eslint-plugin-import/tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], |
| 2331 | 2269 | |
| 2332 | 2270 | "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], |
| @@ -8,7 +8,7 @@ · 7 unmodified lines | ||
| 8 | 8 | "executor/packages/*", |
| 9 | 9 | "assistant", |
| 10 | 10 | "assistant/packages/*", |
| 11 | − "assistant/legacy/openassistant/packages/reacord" | |
| 11 | + "assistant/packages/reacord" | |
| 12 | 12 | ], |
| 13 | 13 | "scripts": { |
| 14 | 14 | "dev": "bun run dev.ts", |