theokit 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/{actions-virtual-module-3CDQTWOC.js → actions-virtual-module-G4BANOLW.js} +5 -3
  2. package/dist/{actions-virtual-module-3CDQTWOC.js.map → actions-virtual-module-G4BANOLW.js.map} +1 -1
  3. package/dist/agent-MN7XGJR3.js +209 -0
  4. package/dist/agent-MN7XGJR3.js.map +1 -0
  5. package/dist/{app-typed-client-CSOK7NPC.js → app-typed-client-Z6BHD4MF.js} +5 -3
  6. package/dist/{app-typed-client-CSOK7NPC.js.map → app-typed-client-Z6BHD4MF.js.map} +1 -1
  7. package/dist/{build-5K7LK77K.js → build-QDAFSKKW.js} +11 -7
  8. package/dist/{build-5K7LK77K.js.map → build-QDAFSKKW.js.map} +1 -1
  9. package/dist/chunk-34YQOXGM.js +37 -0
  10. package/dist/chunk-34YQOXGM.js.map +1 -0
  11. package/dist/{chunk-7YZHAQU7.js → chunk-567NA7Y6.js} +8 -99
  12. package/dist/chunk-567NA7Y6.js.map +1 -0
  13. package/dist/{chunk-BQDGES7C.js → chunk-GBXLKYIA.js} +19 -43
  14. package/dist/chunk-GBXLKYIA.js.map +1 -0
  15. package/dist/chunk-GDN3PXFH.js +101 -0
  16. package/dist/chunk-GDN3PXFH.js.map +1 -0
  17. package/dist/chunk-NBWB4S46.js +81 -0
  18. package/dist/chunk-NBWB4S46.js.map +1 -0
  19. package/dist/{chunk-F4YUPDJ2.js → chunk-NXTF5PPW.js} +15 -41
  20. package/dist/chunk-NXTF5PPW.js.map +1 -0
  21. package/dist/{chunk-S7Y5WLZY.js → chunk-OTFIRP6S.js} +76 -46
  22. package/dist/chunk-OTFIRP6S.js.map +1 -0
  23. package/dist/chunk-P37RZRFV.js +31 -0
  24. package/dist/chunk-P37RZRFV.js.map +1 -0
  25. package/dist/{chunk-RBHCJHRR.js → chunk-UOR6JTCI.js} +142 -7
  26. package/dist/chunk-UOR6JTCI.js.map +1 -0
  27. package/dist/{chunk-637WJB7Z.js → chunk-ZJDKAD3L.js} +55 -14
  28. package/dist/chunk-ZJDKAD3L.js.map +1 -0
  29. package/dist/cli/index.js +22 -6
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/{dev-QOEVYNBG.js → dev-TEE4T6ZB.js} +13 -8
  32. package/dist/{dev-QOEVYNBG.js.map → dev-TEE4T6ZB.js.map} +1 -1
  33. package/dist/{dev-emit-5MDSBP5D.js → dev-emit-VJ5CFMPY.js} +5 -3
  34. package/dist/{dev-emit-5MDSBP5D.js.map → dev-emit-VJ5CFMPY.js.map} +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/{info-OUEUZOT7.js → info-7PE2PZJI.js} +6 -5
  37. package/dist/{info-OUEUZOT7.js.map → info-7PE2PZJI.js.map} +1 -1
  38. package/dist/{internal-api-EFKZWIYZ.js → internal-api-J27TYE2I.js} +7 -4
  39. package/dist/load-config-JKYO5RFK.js +14 -0
  40. package/dist/{openapi-FHY6HC6I.js → openapi-MXMLZCXC.js} +7 -4
  41. package/dist/{openapi-FHY6HC6I.js.map → openapi-MXMLZCXC.js.map} +1 -1
  42. package/dist/{registry-34LL7NF4.js → registry-XJUYD2OU.js} +2 -2
  43. package/dist/{routes-EW7TP7NJ.js → routes-NNBEZSGN.js} +5 -3
  44. package/dist/{routes-EW7TP7NJ.js.map → routes-NNBEZSGN.js.map} +1 -1
  45. package/dist/{start-2KG4JSXM.js → start-7MQEEQH4.js} +68 -8
  46. package/dist/start-7MQEEQH4.js.map +1 -0
  47. package/dist/{static-55G3LX2I.js → static-7ARBVDJF.js} +4 -4
  48. package/dist/vite-plugin/index.js +1 -1
  49. package/dist/{vite-plugin-TDIDZ5U7.js → vite-plugin-GC6WCU4P.js} +11 -7
  50. package/dist/vite-plugin-GC6WCU4P.js.map +1 -0
  51. package/package.json +2 -2
  52. package/dist/chunk-637WJB7Z.js.map +0 -1
  53. package/dist/chunk-7YZHAQU7.js.map +0 -1
  54. package/dist/chunk-BQDGES7C.js.map +0 -1
  55. package/dist/chunk-F4YUPDJ2.js.map +0 -1
  56. package/dist/chunk-RBHCJHRR.js.map +0 -1
  57. package/dist/chunk-S7Y5WLZY.js.map +0 -1
  58. package/dist/start-2KG4JSXM.js.map +0 -1
  59. /package/dist/{internal-api-EFKZWIYZ.js.map → internal-api-J27TYE2I.js.map} +0 -0
  60. /package/dist/{vite-plugin-TDIDZ5U7.js.map → load-config-JKYO5RFK.js.map} +0 -0
  61. /package/dist/{registry-34LL7NF4.js.map → registry-XJUYD2OU.js.map} +0 -0
  62. /package/dist/{static-55G3LX2I.js.map → static-7ARBVDJF.js.map} +0 -0
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
+ import {
4
+ getApprovalRegistry
5
+ } from "./chunk-NBWB4S46.js";
3
6
  import {
4
7
  validateCsrfRequest
5
8
  } from "./chunk-WR4F4EEZ.js";
@@ -41,8 +44,63 @@ function resolveTransformer(selector) {
41
44
  return selector;
42
45
  }
43
46
 
47
+ // src/server/agent/approve-agent.ts
48
+ var APPROVE_SEGMENT = "/approve/";
49
+ function parseApprovalId(urlPath) {
50
+ const at = urlPath.indexOf(APPROVE_SEGMENT);
51
+ if (at === -1) return null;
52
+ const id = urlPath.slice(at + APPROVE_SEGMENT.length);
53
+ return id.length > 0 && !id.includes("/") ? id : null;
54
+ }
55
+ function isApprovalPath(urlPath) {
56
+ return urlPath.includes(APPROVE_SEGMENT);
57
+ }
58
+ function jsonError(status, code, message) {
59
+ return new Response(JSON.stringify({ error: { code, message } }), {
60
+ status,
61
+ headers: { "content-type": "application/json" }
62
+ });
63
+ }
64
+ function parseApprovalBody(body) {
65
+ if (typeof body !== "object" || body === null) return null;
66
+ const b = body;
67
+ return typeof b.approved === "boolean" ? { approved: b.approved } : null;
68
+ }
69
+ async function handleAgentApproval(request, urlPath, registry, csrfMode = "strict") {
70
+ if (csrfMode !== "off") {
71
+ const csrf = validateCsrfRequest(request);
72
+ if (!csrf.valid && csrfMode === "strict") {
73
+ return jsonError(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
74
+ }
75
+ }
76
+ const approvalId = parseApprovalId(urlPath);
77
+ if (approvalId === null) {
78
+ return jsonError(400, "BAD_REQUEST", "Approval path must be /api/agents/<name>/approve/<id>.");
79
+ }
80
+ let body = null;
81
+ try {
82
+ body = await request.json();
83
+ } catch {
84
+ }
85
+ const parsed = parseApprovalBody(body);
86
+ if (parsed === null) {
87
+ return jsonError(400, "BAD_REQUEST", "Request body must contain a boolean `approved`.");
88
+ }
89
+ const resolved = registry.resolve(approvalId, parsed.approved);
90
+ if (!resolved) {
91
+ return jsonError(404, "NOT_PENDING", `No pending approval for id '${approvalId}'.`);
92
+ }
93
+ return new Response(JSON.stringify({ resolved: true }), {
94
+ status: 200,
95
+ headers: { "content-type": "application/json" }
96
+ });
97
+ }
98
+
44
99
  // src/server/agent/mount-agent.ts
45
- import { compileAgentModule, streamAgentUIMessages } from "@theokit/agents";
100
+ import {
101
+ compileAgentModule,
102
+ streamAgentUIMessages
103
+ } from "@theokit/agents";
46
104
 
47
105
  // src/server/define/ui-message-stream-response.ts
48
106
  var UI_MESSAGE_STREAM_HEADERS = {
@@ -78,7 +136,7 @@ function partText(part) {
78
136
  const p = part;
79
137
  return p.type === "text" && typeof p.text === "string" ? p.text : "";
80
138
  }
81
- function jsonError(status, code, message) {
139
+ function jsonError2(status, code, message) {
82
140
  return new Response(JSON.stringify({ error: { code, message } }), {
83
141
  status,
84
142
  headers: { "content-type": "application/json" }
@@ -105,7 +163,7 @@ async function mountAgent(mod, request, apiKey, source = "agent module", csrfMod
105
163
  if (csrfMode !== "off") {
106
164
  const csrf = validateCsrfRequest(request);
107
165
  if (!csrf.valid && csrfMode === "strict") {
108
- return jsonError(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
166
+ return jsonError2(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
109
167
  }
110
168
  }
111
169
  const compiled = compileAgentModule(mod, source);
@@ -116,48 +174,19 @@ async function mountAgent(mod, request, apiKey, source = "agent module", csrfMod
116
174
  }
117
175
  const input = parseAgentRequestBody(body);
118
176
  if (input === null) {
119
- return jsonError(400, "BAD_REQUEST", "Request must contain a non-empty message or messages[].");
120
- }
121
- return uiMessageStreamResponse(streamAgentUIMessages(compiled, apiKey, input));
122
- }
123
-
124
- // src/server/agent/provider-resolver.ts
125
- var DEFAULT_REGISTRY = [
126
- {
127
- name: "openrouter",
128
- envKey: "OPENROUTER_API_KEY",
129
- baseUrl: "https://openrouter.ai/api/v1",
130
- priority: 1
131
- },
132
- {
133
- name: "openai",
134
- envKey: "OPENAI_API_KEY",
135
- baseUrl: "https://api.openai.com/v1",
136
- priority: 2
137
- },
138
- {
139
- name: "anthropic",
140
- envKey: "ANTHROPIC_API_KEY",
141
- baseUrl: "https://api.anthropic.com",
142
- priority: 3
143
- }
144
- ];
145
- var registry = [...DEFAULT_REGISTRY];
146
- function resolveProvider() {
147
- const sorted = [...registry].sort((a, b) => a.priority - b.priority);
148
- for (const desc of sorted) {
149
- const apiKey = process.env[desc.envKey];
150
- if (apiKey && apiKey.length > 0) {
151
- return {
152
- name: desc.name,
153
- apiKey,
154
- baseUrl: desc.baseUrl
155
- };
156
- }
177
+ return jsonError2(400, "BAD_REQUEST", "Request must contain a non-empty message or messages[].");
157
178
  }
158
- const envKeys = sorted.map((p) => p.envKey).join(" OR ");
159
- throw new Error(
160
- `No LLM provider API key found in environment. Set one of: ${envKeys}. Get a free OpenRouter key at https://openrouter.ai/keys (recommended \u2014 one key, many models).`
179
+ const gated = compiled.hitl;
180
+ const registry = getApprovalRegistry();
181
+ const hitl = gated && gated.size > 0 ? {
182
+ gated,
183
+ awaitApproval: (approvalId, opts) => registry.register(approvalId, {
184
+ timeoutMs: opts.timeout ?? 3e5,
185
+ onTimeout: opts.onTimeout ?? "abort"
186
+ })
187
+ } : void 0;
188
+ return uiMessageStreamResponse(
189
+ streamAgentUIMessages(compiled, apiKey, { ...input, hitl, signal: request.signal })
161
190
  );
162
191
  }
163
192
 
@@ -227,9 +256,10 @@ function pickHeaderString(value) {
227
256
 
228
257
  export {
229
258
  resolveTransformer,
259
+ isApprovalPath,
260
+ handleAgentApproval,
230
261
  mountAgent,
231
- resolveProvider,
232
262
  incomingMessageToWebRequest,
233
263
  writeWebResponseToServerResponse
234
264
  };
235
- //# sourceMappingURL=chunk-S7Y5WLZY.js.map
265
+ //# sourceMappingURL=chunk-OTFIRP6S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/transformer.ts","../src/server/agent/approve-agent.ts","../src/server/agent/mount-agent.ts","../src/server/define/ui-message-stream-response.ts","../src/server/http/node-web-adapter.ts"],"sourcesContent":["import superjson from 'superjson'\n\n/**\n * T5.2 — pluggable response/request transformer.\n *\n * `superjson` is the default, preserving Date/Map/Set/BigInt/etc.\n * `json` is the lightweight option (plain JSON.stringify/parse).\n * Users can supply a custom object implementing this contract.\n */\nexport interface TheoTransformer {\n name: string\n serialize: (value: unknown) => string\n deserialize: (raw: string) => unknown\n}\n\nexport const superjsonTransformer: TheoTransformer = {\n name: 'superjson',\n serialize: (v) => JSON.stringify(superjson.serialize(v)),\n deserialize: (raw) => {\n const parsed = JSON.parse(raw) as Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize(parsed)\n },\n}\n\nexport const jsonTransformer: TheoTransformer = {\n name: 'json',\n serialize: (v) => JSON.stringify(v),\n deserialize: (raw) => JSON.parse(raw) as unknown,\n}\n\nconst BUILT_INS: Record<string, TheoTransformer> = {\n superjson: superjsonTransformer,\n json: jsonTransformer,\n}\n\nexport function resolveTransformer(\n selector: 'json' | 'superjson' | TheoTransformer,\n): TheoTransformer {\n if (typeof selector === 'string') {\n // selector is 'json' | 'superjson' literal — both keys exist in\n // BUILT_INS by construction. Type system guarantees a hit; we keep\n // a defensive fallback that the compiler cannot see is unreachable\n // at runtime, just in case someone adds a new literal to the union\n // but forgets to register the built-in.\n const built = BUILT_INS[selector]\n // Defensive: the public union ensures `built` is defined, but if a\n // future contributor extends the union without registering the impl,\n // the cast keeps the failure mode loud.\n if ((built as TheoTransformer | undefined) === undefined) {\n throw new Error(\n `Unknown transformer \"${selector}\". Built-in options: ${Object.keys(BUILT_INS).join(', ')}.`,\n )\n }\n return built\n }\n if (\n typeof selector !== 'object' ||\n typeof selector.serialize !== 'function' ||\n typeof selector.deserialize !== 'function'\n ) {\n throw new Error(\n `Custom transformer must have serialize and deserialize functions. Got: ${JSON.stringify(selector)}`,\n )\n }\n return selector\n}\n","/**\n * M4 (theokit-ai-first) — the HITL approve endpoint: `POST /api/agents/<name>/approve/<approvalId>`.\n *\n * The counterpart to `mountAgent`'s HITL pause. While a gated tool holds the SDK run paused (the\n * awaited `pre_tool_call` hook), the client POSTs here with `{ approved }`; this resolves the\n * pending approval in the shared registry, which un-pauses the run (allow) or vetoes the tool (deny).\n *\n * Web-Standard `Request` → `Response`, one wiring point shared by dev (vite middleware) and prod\n * (built server) so the two never drift (EC-4 parity with `mountAgent`). The registry is INJECTED —\n * dev/prod pass the process singleton (`getApprovalRegistry`), tests pass a fresh instance.\n */\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport type { ApprovalRegistry } from './approval-registry.js'\n\n/** The path segment separating the agent name from the approval id. */\nconst APPROVE_SEGMENT = '/approve/'\n\n/**\n * Extract the `<approvalId>` from a `/api/agents/<name>/approve/<approvalId>` path.\n * Returns `null` when the path has no `/approve/` segment or an empty / nested id.\n */\nexport function parseApprovalId(urlPath: string): string | null {\n const at = urlPath.indexOf(APPROVE_SEGMENT)\n if (at === -1) return null\n const id = urlPath.slice(at + APPROVE_SEGMENT.length)\n return id.length > 0 && !id.includes('/') ? id : null\n}\n\n/** True when `urlPath` targets a HITL approve endpoint (used by dev/prod routing to branch early). */\nexport function isApprovalPath(urlPath: string): boolean {\n return urlPath.includes(APPROVE_SEGMENT)\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/** Extract `{ approved: boolean }` from an untrusted body; `null` when the shape is wrong. */\nfunction parseApprovalBody(body: unknown): { approved: boolean } | null {\n if (typeof body !== 'object' || body === null) return null\n const b = body as Record<string, unknown>\n return typeof b.approved === 'boolean' ? { approved: b.approved } : null\n}\n\n/**\n * Resolve a pending HITL approval. CSRF-guarded like `mountAgent` (the custom `X-Theo-Action`\n * header + Origin match) — a cross-origin POST must not approve a paused tool. Returns:\n * 403 CSRF_FAILED — strict CSRF check failed\n * 400 BAD_REQUEST — no `/approve/<id>` in the path, or body lacks a boolean `approved`\n * 404 NOT_PENDING — the id is unknown or already settled (idempotent double-submit)\n * 200 { resolved:true } — the approval was settled by this call\n */\nexport async function handleAgentApproval(\n request: Request,\n urlPath: string,\n registry: ApprovalRegistry,\n csrfMode: CsrfMode = 'strict',\n): Promise<Response> {\n if (csrfMode !== 'off') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid && csrfMode === 'strict') {\n return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n }\n\n const approvalId = parseApprovalId(urlPath)\n if (approvalId === null) {\n return jsonError(400, 'BAD_REQUEST', 'Approval path must be /api/agents/<name>/approve/<id>.')\n }\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → handled below as a 400 */\n }\n const parsed = parseApprovalBody(body)\n if (parsed === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request body must contain a boolean `approved`.')\n }\n\n const resolved = registry.resolve(approvalId, parsed.approved)\n if (!resolved) {\n return jsonError(404, 'NOT_PENDING', `No pending approval for id '${approvalId}'.`)\n }\n return new Response(JSON.stringify({ resolved: true }), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })\n}\n","/**\n * M2 (theokit-ai-first) — mount a scanned `agents/<name>.ts` module as an SSE endpoint.\n *\n * The SINGLE wiring point shared by dev (vite middleware) and prod (built server), so the\n * two never drift (EC-4). Web-Standard `Request` → `Response`: parse the chat body, compile\n * the module (`compileAgentModule`, converges both surfaces), stream via the M0/M1 canonical\n * protocol (`streamAgentUIMessages` → `uiMessageStreamResponse`).\n *\n * Request body — accepts the `@ai-sdk/react` `useChat` shape (`{ id, messages: UIMessage[] }`,\n * the typed-client path) AND a simple `{ message, sessionId? }` shape (M0/M1-style clients).\n */\nimport {\n compileAgentModule,\n streamAgentUIMessages,\n type HumanInTheLoopOptions,\n} from '@theokit/agents'\n\nimport { uiMessageStreamResponse } from '../define/ui-message-stream-response.js'\nimport { validateCsrfRequest, type CsrfMode } from '../security/csrf.js'\n\nimport { getApprovalRegistry } from './approval-registry.js'\n\n/** The message + session extracted from a chat request, or `null` when the body is invalid. */\nexport interface AgentRequestInput {\n message: string\n sessionId: string\n}\n\n/** Extract the text of a `{ type: 'text', text: string }` part from an untrusted value. */\nfunction partText(part: unknown): string {\n if (typeof part !== 'object' || part === null) return ''\n const p = part as Record<string, unknown>\n return p.type === 'text' && typeof p.text === 'string' ? p.text : ''\n}\n\nfunction jsonError(status: number, code: string, message: string): Response {\n return new Response(JSON.stringify({ error: { code, message } }), {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\n/**\n * Extract `{ message, sessionId }` from a chat request body. Returns `null` when neither the\n * ai-sdk `messages` shape nor the simple `message` shape yields non-empty user text.\n */\nexport function parseAgentRequestBody(body: unknown): AgentRequestInput | null {\n if (typeof body !== 'object' || body === null) return null\n const b = body as Record<string, unknown>\n\n // ai-sdk useChat shape: { id, messages: UIMessage[] } — take the last message's text parts.\n if (Array.isArray(b.messages) && b.messages.length > 0) {\n const last = b.messages[b.messages.length - 1] as Record<string, unknown>\n const parts = Array.isArray(last.parts) ? last.parts : []\n const text = parts.map(partText).join('')\n if (text.length === 0) return null\n const sessionId = typeof b.id === 'string' && b.id.length > 0 ? b.id : crypto.randomUUID()\n return { message: text, sessionId }\n }\n\n // Simple shape: { message, sessionId? }.\n if (typeof b.message === 'string' && b.message.length > 0) {\n const sessionId =\n typeof b.sessionId === 'string' && b.sessionId.length > 0 ? b.sessionId : crypto.randomUUID()\n return { message: b.message, sessionId }\n }\n\n return null\n}\n\n/**\n * Mount a loaded agent module as a `Response`. `apiKey` is resolved by the caller\n * (`resolveProvider`). `source` labels a fail-fast `AgentDefinitionError` (the file path).\n */\nexport async function mountAgent(\n mod: unknown,\n request: Request,\n apiKey: string,\n source = 'agent module',\n csrfMode: CsrfMode = 'strict',\n): Promise<Response> {\n // Enforce CSRF BEFORE any work — an agent run spends real LLM tokens, so a cross-origin\n // POST must be rejected before it reaches the SDK (parity with actions/routes). The custom\n // `X-Theo-Action` header + Origin match is the same defense `executeRoute`/`executeAction`\n // apply; the `useAgent` client sends the header. `off` skips; `warn` never blocks.\n if (csrfMode !== 'off') {\n const csrf = validateCsrfRequest(request)\n if (!csrf.valid && csrfMode === 'strict') {\n return jsonError(403, 'CSRF_FAILED', `CSRF check failed: ${csrf.reason}`)\n }\n }\n\n const compiled = compileAgentModule(mod, source)\n\n let body: unknown = null\n try {\n body = await request.json()\n } catch {\n /* invalid/empty JSON → handled below as a 400 */\n }\n\n const input = parseAgentRequestBody(body)\n if (input === null) {\n return jsonError(400, 'BAD_REQUEST', 'Request must contain a non-empty message or messages[].')\n }\n\n // When the agent has @HumanInTheLoop-gated tools (M4), wire the pause: the plugin's `awaitApproval`\n // registers a pending approval in the shared registry (the Promise that PAUSES the run); the\n // approve route (`handleAgentApproval`) resolves it. No gated tools ⇒ the M2 stream path unchanged.\n const gated = compiled.hitl\n const registry = getApprovalRegistry()\n const hitl =\n gated && gated.size > 0\n ? {\n gated,\n awaitApproval: (approvalId: string, opts: HumanInTheLoopOptions) =>\n registry.register(approvalId, {\n timeoutMs: opts.timeout ?? 300_000,\n onTimeout: opts.onTimeout ?? 'abort',\n }),\n }\n : undefined\n\n return uiMessageStreamResponse(\n streamAgentUIMessages(compiled, apiKey, { ...input, hitl, signal: request.signal }),\n )\n}\n","import type { UIMessageChunk } from 'ai'\n\n/**\n * M0 (theokit-ai-first) — serialize a stream of ai-sdk `UIMessageChunk`s into a\n * Web-Standards `Response` on the UIMessageStream wire so `@ai-sdk/react`'s\n * `useChat` consumes it WITHOUT a custom adapter.\n *\n * Wire contract (must match ai-sdk's consumer transport exactly):\n * - `content-type: text/event-stream`\n * - `x-vercel-ai-ui-message-stream: v1` — the version marker `useChat` checks\n * - each chunk framed as `data: ${JSON.stringify(chunk)}\\n\\n`\n * - a terminal `data: [DONE]\\n\\n` after the last chunk (ignored by the parser)\n *\n * Web Standards only — `Response` + `ReadableStream` (G8, no node:http). The\n * headers commit before the stream starts.\n *\n * Fail-clear (error-handling.md): if the source iterable throws mid-stream, the\n * `[DONE]` terminal is still flushed and the stream is closed — never left\n * hanging. (The translator upstream already closes gracefully; this is defense\n * in depth for any other chunk source.)\n */\nconst UI_MESSAGE_STREAM_HEADERS = {\n 'content-type': 'text/event-stream',\n 'x-vercel-ai-ui-message-stream': 'v1',\n} as const\n\nconst DONE_FRAME = 'data: [DONE]\\n\\n'\n\nfunction encode(text: string): Uint8Array {\n return new TextEncoder().encode(text)\n}\n\nexport function uiMessageStreamResponse(chunks: AsyncIterable<UIMessageChunk>): Response {\n const stream = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const chunk of chunks) {\n controller.enqueue(encode(`data: ${JSON.stringify(chunk)}\\n\\n`))\n }\n } catch {\n // The source iterable aborted mid-stream. The upstream translator owns\n // error semantics (it surfaces failures as chunks + closes gracefully);\n // this transport's sole guarantee is a terminated stream — fall through\n // to the DONE terminal rather than re-throw and error an open stream.\n } finally {\n controller.enqueue(encode(DONE_FRAME))\n controller.close()\n }\n },\n })\n return new Response(stream, { headers: UI_MESSAGE_STREAM_HEADERS })\n}\n","/**\n * T5a.2 Phase G slice 5/N — Node adapter shim for the Web request handler.\n *\n * Bridges Node's `IncomingMessage` + `ServerResponse` shape to the\n * Web-Standards `executeWebRequest` (Phase A → G slices 1-4). Per\n * ADR-0028 R3a, the Node adapter is the ONLY place IncomingMessage ↔\n * Request conversion happens — every other layer of `server/` flows\n * through Web `Request`/`Response`.\n *\n * **Two conversions:**\n *\n * 1. `incomingMessageToWebRequest(req)` — Node → Web. Reads\n * `req.method`, `req.url`, `req.headers`, and (for POST/PUT/etc.)\n * drains the Node Readable body into a Web `ReadableStream`. The\n * request URL is resolved to absolute form using `req.headers.host`\n * (Web Request guarantees absolute URL).\n *\n * 2. `writeWebResponseToServerResponse(response, res)` — Web → Node.\n * Sets status code + status text, copies headers (including all\n * `Set-Cookie` values via `getSetCookie()`), then drains the Web\n * `ReadableStream` body into the Node ServerResponse.\n *\n * Plus the convenience composer `executeWebRequestFromNode(req, res,\n * routeModule, opts?)` that wires both ends — exactly what api-middleware\n * (and the prod CLI start path) need to migrate from the legacy\n * `executeRoute` to `executeWebRequest` without touching call sites.\n *\n * Per `docs/plans/t5a2-incoming-message-to-request-shape-refactor-plan.md`\n * v1.0 § Phase G slice 5/N (closes the executor bridge surface).\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { Readable } from 'node:stream'\n\nimport { executeWebRequest, type ExecuteWebRequestOptions } from '../web-handler.js'\n\n/**\n * Build a Web `Request` from a Node `IncomingMessage`. The Web Request\n * spec requires an absolute URL; we synthesize one from\n * `req.headers.host` (or fall back to `localhost` for test doubles).\n *\n * For methods with a body (POST/PUT/PATCH/DELETE), the Node Readable\n * stream is wrapped as a Web ReadableStream via `Readable.toWeb()` so\n * downstream consumers can call `request.json()` / `request.formData()`\n * / `request.text()` natively.\n *\n * EC-1: Node sometimes provides headers as `string | string[]`. Web\n * `Headers` collapses to single-comma-joined strings — we do the join\n * manually for repeated headers because `Headers.append` would create\n * multi-value entries which behave differently on `.get()`.\n */\nexport function incomingMessageToWebRequest(req: IncomingMessage): Request {\n const host = pickHeaderString(req.headers.host) ?? 'localhost'\n const url = `http://${host}${req.url ?? '/'}`\n\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n headers.set(key, value.join(', '))\n } else {\n headers.set(key, value)\n }\n }\n\n const method = (req.method ?? 'GET').toUpperCase()\n const hasBody = method !== 'GET' && method !== 'HEAD'\n\n if (!hasBody) {\n return new Request(url, { method, headers })\n }\n\n // Drain Node's Readable into a Web ReadableStream. `Readable.toWeb` is\n // available in Node 18+ (theokit's engines.node floor is 22+, so safe).\n const webStream = Readable.toWeb(req) as ReadableStream\n return new Request(url, {\n method,\n headers,\n body: webStream,\n // EC-2: Node 18+ requires `duplex: 'half'` when body is a stream.\n // The `RequestInit` type omits it (Web spec gap); cast accordingly.\n ...({ duplex: 'half' } as { duplex: 'half' }),\n })\n}\n\n/**\n * Write a Web `Response` into a Node `ServerResponse`. Mirrors the Node\n * `res.writeHead` + `res.end` pattern.\n *\n * Set-Cookie is the only multi-value header the Web spec exposes via\n * `getSetCookie()`. We append each entry individually so Node emits\n * separate `Set-Cookie:` lines per the HTTP spec.\n *\n * If the Response body is a `ReadableStream`, it's piped chunk-by-chunk\n * to `res`. Empty body (null) → just close.\n */\nexport async function writeWebResponseToServerResponse(\n response: Response,\n res: ServerResponse,\n): Promise<void> {\n // Status + headers FIRST (writeHead locks them).\n // EC-3: Set-Cookie needs special handling (writeHead's plain object\n // shape conflicts with multi-value headers; we set them via setHeader\n // BEFORE writeHead so the array form is preserved).\n const setCookies = response.headers.getSetCookie()\n if (setCookies.length > 0) {\n res.setHeader('Set-Cookie', setCookies)\n }\n const otherHeaders: Record<string, string> = {}\n for (const [key, value] of response.headers.entries()) {\n if (key.toLowerCase() === 'set-cookie') continue\n otherHeaders[key] = value\n }\n res.writeHead(response.status, response.statusText, otherHeaders)\n\n // Body — drain ReadableStream OR just end() for null body.\n if (response.body === null) {\n res.end()\n return\n }\n const reader = response.body.getReader()\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n // Node's res.write accepts Uint8Array natively (no conversion needed).\n res.write(value)\n }\n res.end()\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Convenience composer — full request lifecycle Node → Web → Node.\n *\n * Use case: existing api-middleware (and the prod CLI start path)\n * receive Node `(req, res)` from `http.createServer`. To migrate to the\n * Web-Standards executor without rewriting every call site, wrap with\n * this composer:\n *\n * import * as users from './app/users/route.js'\n * await executeWebRequestFromNode(req, res, users, { csrfMode: 'strict' })\n *\n * The Web request is built, dispatched through executeWebRequest, and\n * the Response is drained back into `res`. Caller does NOT need to call\n * `res.end()` afterwards — this composer handles it.\n */\nexport async function executeWebRequestFromNode(\n req: IncomingMessage,\n res: ServerResponse,\n routeModule: Parameters<typeof executeWebRequest>[1],\n opts?: ExecuteWebRequestOptions,\n): Promise<void> {\n const webRequest = incomingMessageToWebRequest(req)\n const webResponse = await executeWebRequest(webRequest, routeModule, opts)\n await writeWebResponseToServerResponse(webResponse, res)\n}\n\n/** Pick the first usable string from Node's `string | string[] | undefined` headers. */\nfunction pickHeaderString(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value\n if (Array.isArray(value)) {\n for (const v of value) if (typeof v === 'string' && v.length > 0) return v\n }\n return undefined\n}\n"],"mappings":"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","names":["jsonError"]}
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+
4
+ // src/server/_internal/scan-walker.ts
5
+ import { readdirSync } from "fs";
6
+ import { extname, join, resolve } from "path";
7
+ function walkSourceFiles(root, opts, onFile) {
8
+ const skipPrefixes = opts.skipPrefixes ?? ["_", "."];
9
+ const visit = (dir) => {
10
+ let entries;
11
+ try {
12
+ entries = readdirSync(dir, { withFileTypes: true });
13
+ } catch {
14
+ return;
15
+ }
16
+ for (const entry of entries) {
17
+ const fullPath = join(dir, entry.name);
18
+ if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {
19
+ visit(fullPath);
20
+ } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {
21
+ onFile(resolve(fullPath));
22
+ }
23
+ }
24
+ };
25
+ visit(root);
26
+ }
27
+
28
+ export {
29
+ walkSourceFiles
30
+ };
31
+ //# sourceMappingURL=chunk-P37RZRFV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/_internal/scan-walker.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks directories derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { readdirSync } from 'node:fs'\nimport { extname, join, resolve } from 'node:path'\n\n/**\n * Options for walkSourceFiles.\n *\n * Sequential by design — callers (route precedence) depend on insertion\n * order (EC-19 documented decision). Async-callback support is implicit\n * via JavaScript's serial await loop.\n *\n * Tested on macOS + Linux. Windows long-path support not validated (EC-20).\n */\nexport interface WalkOptions {\n /** File extensions to include (e.g., new Set(['.ts', '.tsx'])). */\n extensions: ReadonlySet<string>\n /** Skip directories whose name starts with any of these (default: ['_', '.']). */\n skipPrefixes?: readonly string[]\n}\n\n/**\n * Recursively walk `root`, invoking `onFile(absPath)` for every file matching\n * `opts.extensions`. Directories whose name starts with any `skipPrefixes`\n * char are skipped (defaults to `_` and `.`).\n *\n * Replaces 3 near-identical recursive walkers in scan.ts, action-scan.ts,\n * ws-scan.ts (PV-3 — DRY consolidation). Resolves T3.1 of\n * architecture-review-remediation-plan.\n *\n * Symlink loops are NOT tracked — callers must avoid them or pre-resolve\n * via `fs.realpath`. EC-11 documented but not implemented (rare in dev).\n */\nexport function walkSourceFiles(\n root: string,\n opts: WalkOptions,\n onFile: (absPath: string) => void,\n): void {\n const skipPrefixes = opts.skipPrefixes ?? ['_', '.']\n const visit = (dir: string): void => {\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n } catch {\n // Silently skip unreadable directories — caller controls discoverability\n return\n }\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {\n visit(fullPath)\n } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {\n onFile(resolve(fullPath))\n }\n }\n }\n visit(root)\n}\n"],"mappings":";;;;AAIA,SAAS,mBAAmB;AAC5B,SAAS,SAAS,MAAM,eAAe;AA8BhC,SAAS,gBACd,MACA,MACA,QACM;AACN,QAAM,eAAe,KAAK,gBAAgB,CAAC,KAAK,GAAG;AACnD,QAAM,QAAQ,CAAC,QAAsB;AACnC,QAAI;AACJ,QAAI;AACF,gBAAU,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IACpD,QAAQ;AAEN;AAAA,IACF;AACA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,KAAK,KAAK,MAAM,IAAI;AACrC,UAAI,MAAM,YAAY,KAAK,CAAC,aAAa,KAAK,CAAC,MAAM,MAAM,KAAK,WAAW,CAAC,CAAC,GAAG;AAC9E,cAAM,QAAQ;AAAA,MAChB,WAAW,MAAM,OAAO,KAAK,KAAK,WAAW,IAAI,QAAQ,MAAM,IAAI,CAAC,GAAG;AACrE,eAAO,QAAQ,QAAQ,CAAC;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AACA,QAAM,IAAI;AACZ;","names":[]}
@@ -984,14 +984,102 @@ function createActionMiddleware(vite, serverDir, options) {
984
984
  // src/vite-plugin/agent-middleware.ts
985
985
  import { randomUUID as randomUUID2 } from "crypto";
986
986
 
987
+ // src/server/agent/approval-registry.ts
988
+ var serverRegistry;
989
+ function getApprovalRegistry() {
990
+ serverRegistry ??= createInProcessApprovalRegistry();
991
+ return serverRegistry;
992
+ }
993
+ function createInProcessApprovalRegistry() {
994
+ const pending = /* @__PURE__ */ new Map();
995
+ return {
996
+ register(approvalId, opts) {
997
+ return new Promise((resolve10) => {
998
+ const settle = (approved) => {
999
+ const entry = pending.get(approvalId);
1000
+ if (!entry) return;
1001
+ clearTimeout(entry.timer);
1002
+ pending.delete(approvalId);
1003
+ resolve10(approved);
1004
+ };
1005
+ const timer = setTimeout(() => {
1006
+ settle(opts.onTimeout === "proceed");
1007
+ }, opts.timeoutMs);
1008
+ pending.set(approvalId, { settle, timer });
1009
+ });
1010
+ },
1011
+ resolve(approvalId, approved) {
1012
+ const entry = pending.get(approvalId);
1013
+ if (!entry) return false;
1014
+ entry.settle(approved);
1015
+ return true;
1016
+ }
1017
+ };
1018
+ }
1019
+
1020
+ // src/server/agent/approve-agent.ts
1021
+ var APPROVE_SEGMENT = "/approve/";
1022
+ function parseApprovalId(urlPath) {
1023
+ const at = urlPath.indexOf(APPROVE_SEGMENT);
1024
+ if (at === -1) return null;
1025
+ const id = urlPath.slice(at + APPROVE_SEGMENT.length);
1026
+ return id.length > 0 && !id.includes("/") ? id : null;
1027
+ }
1028
+ function isApprovalPath(urlPath) {
1029
+ return urlPath.includes(APPROVE_SEGMENT);
1030
+ }
1031
+ function jsonError(status, code, message) {
1032
+ return new Response(JSON.stringify({ error: { code, message } }), {
1033
+ status,
1034
+ headers: { "content-type": "application/json" }
1035
+ });
1036
+ }
1037
+ function parseApprovalBody(body) {
1038
+ if (typeof body !== "object" || body === null) return null;
1039
+ const b = body;
1040
+ return typeof b.approved === "boolean" ? { approved: b.approved } : null;
1041
+ }
1042
+ async function handleAgentApproval(request, urlPath, registry2, csrfMode = "strict") {
1043
+ if (csrfMode !== "off") {
1044
+ const csrf = validateCsrfRequest(request);
1045
+ if (!csrf.valid && csrfMode === "strict") {
1046
+ return jsonError(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
1047
+ }
1048
+ }
1049
+ const approvalId = parseApprovalId(urlPath);
1050
+ if (approvalId === null) {
1051
+ return jsonError(400, "BAD_REQUEST", "Approval path must be /api/agents/<name>/approve/<id>.");
1052
+ }
1053
+ let body = null;
1054
+ try {
1055
+ body = await request.json();
1056
+ } catch {
1057
+ }
1058
+ const parsed = parseApprovalBody(body);
1059
+ if (parsed === null) {
1060
+ return jsonError(400, "BAD_REQUEST", "Request body must contain a boolean `approved`.");
1061
+ }
1062
+ const resolved = registry2.resolve(approvalId, parsed.approved);
1063
+ if (!resolved) {
1064
+ return jsonError(404, "NOT_PENDING", `No pending approval for id '${approvalId}'.`);
1065
+ }
1066
+ return new Response(JSON.stringify({ resolved: true }), {
1067
+ status: 200,
1068
+ headers: { "content-type": "application/json" }
1069
+ });
1070
+ }
1071
+
987
1072
  // src/server/agent/mount-agent.ts
988
- import { compileAgentModule, streamAgentUIMessages } from "@theokit/agents";
1073
+ import {
1074
+ compileAgentModule,
1075
+ streamAgentUIMessages
1076
+ } from "@theokit/agents";
989
1077
  function partText(part) {
990
1078
  if (typeof part !== "object" || part === null) return "";
991
1079
  const p = part;
992
1080
  return p.type === "text" && typeof p.text === "string" ? p.text : "";
993
1081
  }
994
- function jsonError(status, code, message) {
1082
+ function jsonError2(status, code, message) {
995
1083
  return new Response(JSON.stringify({ error: { code, message } }), {
996
1084
  status,
997
1085
  headers: { "content-type": "application/json" }
@@ -1018,7 +1106,7 @@ async function mountAgent(mod, request, apiKey, source = "agent module", csrfMod
1018
1106
  if (csrfMode !== "off") {
1019
1107
  const csrf = validateCsrfRequest(request);
1020
1108
  if (!csrf.valid && csrfMode === "strict") {
1021
- return jsonError(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
1109
+ return jsonError2(403, "CSRF_FAILED", `CSRF check failed: ${csrf.reason}`);
1022
1110
  }
1023
1111
  }
1024
1112
  const compiled = compileAgentModule(mod, source);
@@ -1029,9 +1117,20 @@ async function mountAgent(mod, request, apiKey, source = "agent module", csrfMod
1029
1117
  }
1030
1118
  const input = parseAgentRequestBody(body);
1031
1119
  if (input === null) {
1032
- return jsonError(400, "BAD_REQUEST", "Request must contain a non-empty message or messages[].");
1033
- }
1034
- return uiMessageStreamResponse(streamAgentUIMessages(compiled, apiKey, input));
1120
+ return jsonError2(400, "BAD_REQUEST", "Request must contain a non-empty message or messages[].");
1121
+ }
1122
+ const gated = compiled.hitl;
1123
+ const registry2 = getApprovalRegistry();
1124
+ const hitl = gated && gated.size > 0 ? {
1125
+ gated,
1126
+ awaitApproval: (approvalId, opts) => registry2.register(approvalId, {
1127
+ timeoutMs: opts.timeout ?? 3e5,
1128
+ onTimeout: opts.onTimeout ?? "abort"
1129
+ })
1130
+ } : void 0;
1131
+ return uiMessageStreamResponse(
1132
+ streamAgentUIMessages(compiled, apiKey, { ...input, hitl, signal: request.signal })
1133
+ );
1035
1134
  }
1036
1135
 
1037
1136
  // src/server/agent/provider-resolver.ts
@@ -1153,6 +1252,42 @@ function createAgentMiddleware(vite, projectRoot, csrfMode = "strict") {
1153
1252
  const start = Date.now();
1154
1253
  res.setHeader("x-request-id", requestId);
1155
1254
  const urlPath = url.split("?")[0];
1255
+ if (isApprovalPath(urlPath)) {
1256
+ const method2 = (req.method ?? "POST").toUpperCase();
1257
+ if (method2 !== "POST") {
1258
+ sendError(
1259
+ res,
1260
+ "METHOD_NOT_ALLOWED",
1261
+ "Approve endpoints accept POST",
1262
+ 405,
1263
+ void 0,
1264
+ requestId
1265
+ );
1266
+ logRequest({ method: method2, url, status: 405, duration: Date.now() - start, requestId });
1267
+ return;
1268
+ }
1269
+ try {
1270
+ const request = incomingMessageToWebRequest(req);
1271
+ const response = await handleAgentApproval(
1272
+ request,
1273
+ urlPath,
1274
+ getApprovalRegistry(),
1275
+ csrfMode
1276
+ );
1277
+ await writeWebResponseToServerResponse(response, res);
1278
+ } catch (err) {
1279
+ sendError(
1280
+ res,
1281
+ "INTERNAL",
1282
+ err instanceof Error ? err.message : "Approve handler failed",
1283
+ 500,
1284
+ void 0,
1285
+ requestId
1286
+ );
1287
+ }
1288
+ logRequest({ method: method2, url, status: res.statusCode, duration: Date.now() - start, requestId });
1289
+ return;
1290
+ }
1156
1291
  const agent = scanAgents(projectRoot).find((a) => a.agentPath === urlPath);
1157
1292
  if (!agent) {
1158
1293
  next();
@@ -2743,4 +2878,4 @@ export {
2743
2878
  theoPluginAsync,
2744
2879
  theoPlugin
2745
2880
  };
2746
- //# sourceMappingURL=chunk-RBHCJHRR.js.map
2881
+ //# sourceMappingURL=chunk-UOR6JTCI.js.map