@runtypelabs/cli 2.59.1 → 2.59.2

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 (84) hide show
  1. package/dist/chunk-0-T9NWjH.js +3899 -0
  2. package/dist/chunk-0x7UKC1U.js +17 -0
  3. package/dist/chunk-2ySxXtcg.js +418 -0
  4. package/dist/chunk-3An_BRHv.js +501 -0
  5. package/dist/chunk-6uTxgdiY.js +16218 -0
  6. package/dist/chunk-87nRnNRG.js +1363 -0
  7. package/dist/chunk-B3wo8uRx.js +322 -0
  8. package/dist/chunk-BGp3ZdBy.js +560 -0
  9. package/dist/chunk-BJQv9Elp.js +107 -0
  10. package/dist/chunk-BOpO6xE-.js +6053 -0
  11. package/dist/chunk-BQXXCL8B.js +102 -0
  12. package/dist/chunk-BV-8isOd.js +34 -0
  13. package/dist/chunk-BVMhjGVe.js +826 -0
  14. package/dist/chunk-BW2fg46T.js +141 -0
  15. package/dist/chunk-BWJdY5Ag.js +36 -0
  16. package/dist/chunk-BYJOrLhh.js +302 -0
  17. package/dist/chunk-BYab8EVL.js +127 -0
  18. package/dist/chunk-BfEKvp7h.js +82 -0
  19. package/dist/chunk-BhgLSfH6.js +1123 -0
  20. package/dist/chunk-BirUxTtH.js +296 -0
  21. package/dist/chunk-BmB6UVZ9.js +1559 -0
  22. package/dist/chunk-BnWhIqW1.js +109 -0
  23. package/dist/chunk-Bpfnfk0t.js +123 -0
  24. package/dist/chunk-BrhgdnM3.js +108 -0
  25. package/dist/chunk-Bt0XAgX5.js +327 -0
  26. package/dist/chunk-Bu0FJr6S.js +84 -0
  27. package/dist/chunk-BvEoKpN3.js +255 -0
  28. package/dist/chunk-BxNJgYbE.js +28 -0
  29. package/dist/chunk-C1OFhd7F.js +6131 -0
  30. package/dist/chunk-C7e7WwbU.js +331 -0
  31. package/dist/chunk-CDTYP6bN.js +558 -0
  32. package/dist/chunk-CGDQoqDC.js +24 -0
  33. package/dist/chunk-CJraEBVd.js +127 -0
  34. package/dist/chunk-CQrNhOtv.js +40 -0
  35. package/dist/chunk-CW6AzBH6.js +128 -0
  36. package/dist/chunk-CXgL4w-9.js +317 -0
  37. package/dist/chunk-CZrqoPyf.js +233 -0
  38. package/dist/chunk-CcFCyqbB.js +83 -0
  39. package/dist/chunk-CdaIvO-y.js +485 -0
  40. package/dist/chunk-CehSXtgK.js +624 -0
  41. package/dist/chunk-Ciqkop65.js +218 -0
  42. package/dist/chunk-CoAWdSiE.js +43 -0
  43. package/dist/chunk-Crb4y7t4.js +830 -0
  44. package/dist/chunk-CrlqIfmb.js +337 -0
  45. package/dist/chunk-Cs4pwCQ-.js +324 -0
  46. package/dist/chunk-CwM_9y01.js +519 -0
  47. package/dist/chunk-CyRbIJg8.js +440 -0
  48. package/dist/chunk-D0OHsyMy.js +212 -0
  49. package/dist/chunk-D3y2bURs.js +1347 -0
  50. package/dist/chunk-D4_mJk03.js +2338 -0
  51. package/dist/chunk-D6C8rned.js +578 -0
  52. package/dist/chunk-D9ZbUnks.js +11 -0
  53. package/dist/chunk-DEPmLshX.js +834 -0
  54. package/dist/chunk-DFj1UPB2.js +330 -0
  55. package/dist/chunk-DGdFdZVf.js +197 -0
  56. package/dist/chunk-DGv7X9S_.js +63 -0
  57. package/dist/chunk-DJQ47WYZ.js +664 -0
  58. package/dist/chunk-DJw3O61j.js +1974 -0
  59. package/dist/chunk-DK4FbWVK.js +100 -0
  60. package/dist/chunk-DSRYhUus.js +993 -0
  61. package/dist/chunk-DajZ9Eg4.js +299 -0
  62. package/dist/chunk-Df4dunvU.js +195 -0
  63. package/dist/chunk-DjNTPhfO.js +481 -0
  64. package/dist/chunk-Dn8eaJuu.js +1042 -0
  65. package/dist/chunk-DwDWqSxM.js +637 -0
  66. package/dist/chunk-Dz7cEtxl.js +84 -0
  67. package/dist/chunk-E_0bgjrS.js +359 -0
  68. package/dist/chunk-KPYN2vUp.js +776 -0
  69. package/dist/chunk-KcqPQJBi.js +86 -0
  70. package/dist/chunk-N9H0JnoG.js +349 -0
  71. package/dist/chunk-NONFcXkn.js +728 -0
  72. package/dist/chunk-PrFjcpmH.js +256 -0
  73. package/dist/chunk-QSfNYiw9.js +11 -0
  74. package/dist/chunk-R265bzj9.js +226 -0
  75. package/dist/chunk-X9V_r6jM.js +166 -0
  76. package/dist/chunk-ZBtPUtyA.js +55 -0
  77. package/dist/chunk-ZCTfmOs7.js +4939 -0
  78. package/dist/chunk-h6oavj0t.js +97 -0
  79. package/dist/chunk-i7DwkvvD.js +42 -0
  80. package/dist/chunk-mG-4eZ6S.js +203 -0
  81. package/dist/chunk-o0ygUbCf.js +313 -0
  82. package/dist/chunk-ucu5lrDZ.js +5715 -0
  83. package/dist/index.js +11367 -73789
  84. package/package.json +4 -4
@@ -0,0 +1,1363 @@
1
+ #!/usr/bin/env node
2
+ import { I as __exportAll } from "./index.js";
3
+ import { i as extractSecretReferencesFromAnyValue } from "./chunk-BVMhjGVe.js";
4
+ import { n as resolveModelSecretNames, t as resolveModelOptionalSettings } from "./chunk-Bpfnfk0t.js";
5
+ import { r as createCliClient } from "./chunk-BW2fg46T.js";
6
+ import { n as ensureAuth } from "./chunk-CJraEBVd.js";
7
+ import { Command } from "commander";
8
+ import chalk from "chalk";
9
+ import * as fs$1 from "node:fs";
10
+ import * as path$1 from "node:path";
11
+ import { RuntypeApiError } from "@runtypelabs/sdk";
12
+ // src/commands/deploy.ts
13
+ var deploy_exports = /* @__PURE__ */ __exportAll({
14
+ BYOC_PLAN_REQUIRED_CODE: () => BYOC_PLAN_REQUIRED_CODE,
15
+ RETIRED_DEPLOY_FLOW_MESSAGE: () => RETIRED_DEPLOY_FLOW_MESSAGE,
16
+ TELEMETRY_ENV_TABLE: () => TELEMETRY_ENV_TABLE,
17
+ agentReleaseSelectorParams: () => agentReleaseSelectorParams,
18
+ collectDeploymentSecretInventory: () => collectDeploymentSecretInventory,
19
+ deployCommand: () => deployCommand,
20
+ describeAgentExportFailure: () => describeAgentExportFailure,
21
+ formatOptionalProviderSettings: () => formatOptionalProviderSettings,
22
+ readme: () => readme,
23
+ serverTs: () => serverTs,
24
+ setupSh: () => setupSh,
25
+ vercelReadme: () => vercelReadme,
26
+ vercelRouteTs: () => vercelRouteTs,
27
+ vercelSetupSh: () => vercelSetupSh,
28
+ workerIndexTs: () => workerIndexTs,
29
+ workerReadme: () => workerReadme,
30
+ workerSetupSh: () => workerSetupSh,
31
+ wranglerToml: () => wranglerToml
32
+ });
33
+ function bashSingleQuote(s) {
34
+ return "'" + s.replace(/'/g, "'\\''") + "'";
35
+ }
36
+ /**
37
+ * Renders the `telemetry` block every scaffold passes to `createRuntime`.
38
+ * Reporting turns on as soon as an API key is present, and each switch is a
39
+ * plain environment variable so an operator can flip it without editing code.
40
+ * `envAccess` is `process.env` on Node hosts and `env` inside a Worker.
41
+ */
42
+ function telemetryConfigBlock(envAccess, indent) {
43
+ const inner = `${indent} `;
44
+ return [
45
+ `${indent}telemetry: {`,
46
+ `${inner}enabled: ${envAccess}.RUNTYPE_TELEMETRY !== 'off' && Boolean(${envAccess}.RUNTYPE_API_KEY),`,
47
+ `${inner}apiKey: ${envAccess}.RUNTYPE_API_KEY,`,
48
+ `${inner}baseUrl: ${envAccess}.RUNTYPE_API_URL,`,
49
+ `${inner}content: ${envAccess}.RUNTYPE_TELEMETRY_CONTENT !== 'off',`,
50
+ `${indent}},`
51
+ ].join("\n");
52
+ }
53
+ /** The four telemetry environment variables, documented once for every README. */
54
+ const TELEMETRY_ENV_TABLE = `| Variable | Effect |
55
+ | --- | --- |
56
+ | \`RUNTYPE_API_KEY\` | Turns reporting on. Use an API key with the \`TELEMETRY:WRITE\` scope. |
57
+ | \`RUNTYPE_TELEMETRY=off\` | Stops reporting even when the key is set. |
58
+ | \`RUNTYPE_TELEMETRY_CONTENT=off\` | Reports runs without messages, tool arguments, or tool results. |
59
+ | \`RUNTYPE_API_URL\` | Overrides the Runtype API origin. Defaults to \`https://api.runtype.com\`. |`;
60
+ /**
61
+ * README section describing the telemetry lane. `setCommand` is the host's own
62
+ * way of setting the key, so the reader never has to translate the instruction.
63
+ */
64
+ function telemetryReadmeSection(setCommand) {
65
+ return `## Telemetry
66
+
67
+ Set \`RUNTYPE_API_KEY\` and this deployment reports every run to Runtype over
68
+ OpenTelemetry, so each run appears in the dashboard with its trace tree,
69
+ transcript, tool calls, token usage, and cost estimate.
70
+
71
+ \`\`\`bash
72
+ ${setCommand}
73
+ \`\`\`
74
+
75
+ ${TELEMETRY_ENV_TABLE}
76
+ `;
77
+ }
78
+ function serverTs() {
79
+ return `/**
80
+ * Generated by \`runtype deploy\`.
81
+ *
82
+ * Boots a Hono server that loads every agent JSON file under ./agents/ and
83
+ * registers a streaming POST /agent/:name endpoint for each.
84
+ *
85
+ * See README.md for startup and deploy instructions.
86
+ */
87
+ import { readFile, readdir } from 'node:fs/promises'
88
+ import { resolve } from 'node:path'
89
+ import { Hono } from 'hono'
90
+ import { cors } from 'hono/cors'
91
+ import { serve } from '@hono/node-server'
92
+ import {
93
+ CloudRunAdapter,
94
+ createRuntime,
95
+ exportedAgentSchema,
96
+ type ExportedAgent,
97
+ } from '@runtypelabs/runtime'
98
+
99
+ async function loadAgent(filePath: string): Promise<ExportedAgent> {
100
+ const raw = await readFile(filePath, 'utf-8')
101
+ return exportedAgentSchema.parse(JSON.parse(raw))
102
+ }
103
+
104
+ async function loadAgentsDir(dir: string): Promise<Record<string, ExportedAgent>> {
105
+ let entries: string[]
106
+ try {
107
+ entries = await readdir(dir)
108
+ } catch (err) {
109
+ throw new Error(
110
+ \`[runtype] could not read agents directory at \${dir}: \${err instanceof Error ? err.message : String(err)}\`
111
+ )
112
+ }
113
+ const jsonFiles = entries.filter((f) => f.endsWith('.json')).sort()
114
+ if (jsonFiles.length === 0) {
115
+ throw new Error(\`[runtype] no agents found in \${dir} (expected one or more *.json files)\`)
116
+ }
117
+
118
+ const agents: Record<string, ExportedAgent> = {}
119
+ for (const file of jsonFiles) {
120
+ const agent = await loadAgent(resolve(dir, file))
121
+ if (agents[agent.name]) {
122
+ throw new Error(
123
+ \`[runtype] duplicate agent name "\${agent.name}" in \${file} — already defined by an earlier file\`
124
+ )
125
+ }
126
+ agents[agent.name] = agent
127
+ console.log(\`[runtype] loaded agent id=\${agent.id} name=\${agent.name}\`)
128
+ }
129
+ return agents
130
+ }
131
+
132
+ async function main(): Promise<void> {
133
+ const agentsDir = resolve(process.cwd(), 'agents')
134
+ const agents = await loadAgentsDir(agentsDir)
135
+
136
+ const runtime = createRuntime({
137
+ agents,
138
+ keys: { mode: 'own' },
139
+ adapter: new CloudRunAdapter(),
140
+ ${telemetryConfigBlock("process.env", " ")}
141
+ })
142
+
143
+ const app = new Hono()
144
+
145
+ const allowedOriginsEnv = process.env.ALLOWED_ORIGINS?.trim()
146
+ const allowedOrigins = allowedOriginsEnv
147
+ ? allowedOriginsEnv.split(',').map((o) => o.trim()).filter(Boolean)
148
+ : '*'
149
+ app.use(
150
+ '*',
151
+ cors({
152
+ origin: allowedOrigins,
153
+ allowMethods: ['GET', 'POST', 'OPTIONS'],
154
+ allowHeaders: ['Content-Type', 'Authorization'],
155
+ credentials: false,
156
+ maxAge: 86400,
157
+ })
158
+ )
159
+
160
+ app.get('/health', (c) => c.json({ status: 'ok', agents: Object.keys(agents) }))
161
+
162
+ app.post('/agent/:name', async (c) => {
163
+ const name = c.req.param('name')
164
+ if (!runtime.getAgent(name)) {
165
+ return c.json({ error: \`unknown agent: \${name}\` }, 404)
166
+ }
167
+
168
+ let body: { messages?: Array<{ role: string; content: string }> } = {}
169
+ try {
170
+ body = (await c.req.json()) as typeof body
171
+ } catch {
172
+ return c.json({ error: 'request body must be JSON' }, 400)
173
+ }
174
+ const messages = Array.isArray(body.messages) ? body.messages : []
175
+
176
+ try {
177
+ const response = await runtime.executeAgent(name, {
178
+ messages: messages as Array<{ role: 'system' | 'user' | 'assistant'; content: string }>,
179
+ signal: c.req.raw.signal,
180
+ })
181
+ return response
182
+ } catch (err) {
183
+ return c.json({ error: err instanceof Error ? err.message : 'unknown error' }, 500)
184
+ }
185
+ })
186
+
187
+ const port = Number(process.env.PORT ?? 8080)
188
+ serve({ fetch: app.fetch, port, hostname: '0.0.0.0' })
189
+ console.log(\`[runtype] listening on 0.0.0.0:\${port} — agents: \${Object.keys(agents).join(', ')}\`)
190
+ }
191
+
192
+ main().catch((err) => {
193
+ console.error('[runtype] fatal: failed to start server', err)
194
+ process.exit(1)
195
+ })
196
+ `;
197
+ }
198
+ function dockerfile() {
199
+ return `FROM node:22-bookworm-slim AS builder
200
+
201
+ WORKDIR /app
202
+
203
+ COPY package.json ./
204
+ COPY packages ./packages
205
+ RUN npm install --omit=dev --no-audit --no-fund
206
+
207
+ COPY tsconfig.json server.ts ./
208
+ COPY agents ./agents
209
+ RUN npm install --no-save --no-audit --no-fund typescript @types/node \\
210
+ && npx tsc -p tsconfig.json
211
+
212
+ FROM node:22-bookworm-slim AS runner
213
+
214
+ WORKDIR /app
215
+ ENV NODE_ENV=production
216
+ ENV PORT=8080
217
+ EXPOSE 8080
218
+
219
+ COPY --from=builder /app/node_modules ./node_modules
220
+ COPY --from=builder /app/dist ./dist
221
+ COPY --from=builder /app/agents ./agents
222
+ COPY --from=builder /app/package.json ./package.json
223
+
224
+ CMD ["node", "dist/server.js"]
225
+ `;
226
+ }
227
+ function dockerignore() {
228
+ return `node_modules
229
+ dist
230
+ *.log
231
+ `;
232
+ }
233
+ function tsconfigJson() {
234
+ return JSON.stringify({
235
+ compilerOptions: {
236
+ target: "ES2022",
237
+ module: "ESNext",
238
+ moduleResolution: "bundler",
239
+ lib: ["ES2022", "DOM"],
240
+ strict: true,
241
+ esModuleInterop: true,
242
+ forceConsistentCasingInFileNames: true,
243
+ resolveJsonModule: true,
244
+ skipLibCheck: true,
245
+ outDir: "dist",
246
+ declaration: false,
247
+ sourceMap: true,
248
+ noEmit: false
249
+ },
250
+ include: ["*.ts"],
251
+ exclude: ["node_modules", "dist"]
252
+ }, null, 2);
253
+ }
254
+ function packageJson(name, runtimeDep) {
255
+ return JSON.stringify({
256
+ name,
257
+ version: "0.0.0",
258
+ private: true,
259
+ type: "module",
260
+ scripts: {
261
+ start: "node dist/server.js",
262
+ dev: "tsx server.ts",
263
+ build: "tsc -p tsconfig.json",
264
+ typecheck: "tsc -p tsconfig.json --noEmit"
265
+ },
266
+ dependencies: {
267
+ "@hono/node-server": "^2.0.0",
268
+ "@runtypelabs/runtime": runtimeDep,
269
+ hono: "^4.12.16"
270
+ },
271
+ devDependencies: {
272
+ "@types/node": "^22.10.5",
273
+ tsx: "^4.19.2",
274
+ typescript: "^5.3.3"
275
+ },
276
+ engines: { node: ">=22.0.0" }
277
+ }, null, 2);
278
+ }
279
+ function setupSh(monorepoRoot) {
280
+ return `#!/usr/bin/env bash
281
+ # Generated by \`runtype deploy\`.
282
+ #
283
+ # Builds and packs @runtypelabs/runtime from your local monorepo, then
284
+ # installs all dependencies so you can run \`npm start\` or deploy to
285
+ # Cloud Run with \`gcloud run deploy --source .\`.
286
+ #
287
+ # Usage (from this directory):
288
+ # ./setup.sh
289
+ # # then: npm run dev OR gcloud run deploy …
290
+
291
+ set -euo pipefail
292
+
293
+ DEPLOY_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
294
+ MONOREPO_ROOT=${bashSingleQuote(monorepoRoot)}
295
+ TARBALL_DIR="\${DEPLOY_DIR}/packages"
296
+
297
+ echo "[setup] building @runtypelabs/runtime..."
298
+ pnpm --dir "\${MONOREPO_ROOT}" --filter @runtypelabs/runtime build
299
+
300
+ mkdir -p "\${TARBALL_DIR}"
301
+
302
+ echo "[setup] packing @runtypelabs/runtime tarball..."
303
+ pnpm --dir "\${MONOREPO_ROOT}/packages/runtime" pack \\
304
+ --pack-destination "\${TARBALL_DIR}"
305
+
306
+ RUNTIME_TGZ="$(ls "\${TARBALL_DIR}"/runtypelabs-runtime-*.tgz | head -n 1)"
307
+ if [ -z "\${RUNTIME_TGZ}" ]; then
308
+ echo "[setup] error: no tarball produced by pnpm pack" >&2
309
+ exit 1
310
+ fi
311
+ RUNTIME_TGZ_NAME="$(basename "\${RUNTIME_TGZ}")"
312
+
313
+ echo "[setup] updating package.json with file: dependency..."
314
+ node -e "
315
+ const fs = require('fs');
316
+ const pkg = JSON.parse(fs.readFileSync('\${DEPLOY_DIR}/package.json', 'utf8'));
317
+ pkg.dependencies['@runtypelabs/runtime'] = 'file:./packages/\${RUNTIME_TGZ_NAME}';
318
+ fs.writeFileSync('\${DEPLOY_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\\\\n');
319
+ "
320
+
321
+ echo "[setup] installing dependencies..."
322
+ npm install --no-audit --no-fund
323
+
324
+ echo ""
325
+ echo "[setup] Done! Next steps:"
326
+ echo " npm run dev — start locally"
327
+ echo " npm run build — compile TypeScript"
328
+ echo " gcloud run deploy --source . --region us-central1 --allow-unauthenticated --port 8080"
329
+ echo ""
330
+ echo "[setup] Telemetry: export RUNTYPE_API_KEY=<key with TELEMETRY:WRITE> to report runs"
331
+ echo " RUNTYPE_TELEMETRY=off disable reporting"
332
+ echo " RUNTYPE_TELEMETRY_CONTENT=off report without messages, tool arguments, or results"
333
+ echo " RUNTYPE_API_URL=<origin> override https://api.runtype.com"
334
+ `;
335
+ }
336
+ function formatOptionalProviderSettings(settings) {
337
+ return settings.map((setting) => `- \`${setting.name}\` (default: \`${setting.defaultValue}\`)`).join("\n");
338
+ }
339
+ function optionalProviderSettingsSection(settings) {
340
+ if (settings.length === 0) return "";
341
+ return `
342
+ ## Optional provider settings
343
+
344
+ These environment variables override runtime defaults and are not required:
345
+
346
+ ${formatOptionalProviderSettings(settings)}
347
+ `;
348
+ }
349
+ function readme(agentNames, secretNames, optionalSettings) {
350
+ const agentList = agentNames.map((n) => ` - ${n}`).join("\n");
351
+ const secretList = secretNames.length > 0 ? secretNames.map((s) => ` gcloud secrets create ${s} --data-file=-`).join("\n") : " (none detected)";
352
+ return `# Runtype Deployment
353
+
354
+ Generated by \`runtype deploy\`. Contains the following agents:
355
+
356
+ ${agentList}
357
+
358
+ ## Setup
359
+
360
+ Run \`./setup.sh\` to build the runtime and install dependencies:
361
+
362
+ \`\`\`bash
363
+ chmod +x setup.sh
364
+ ./setup.sh
365
+ \`\`\`
366
+
367
+ ## Local development
368
+
369
+ \`\`\`bash
370
+ npm run dev
371
+ \`\`\`
372
+
373
+ Test with:
374
+ \`\`\`bash
375
+ curl -X POST http://localhost:8080/agent/${agentNames[0] ?? "my-agent"} \\
376
+ -H "Content-Type: application/json" \\
377
+ -d '{"messages":[{"role":"user","content":"Hello!"}]}'
378
+ \`\`\`
379
+
380
+ ## Required secrets
381
+
382
+ Set these before deploying to Cloud Run:
383
+
384
+ ${secretList}
385
+
386
+ ${optionalProviderSettingsSection(optionalSettings)}
387
+
388
+ ${telemetryReadmeSection("gcloud run services update runtype-deploy --update-secrets RUNTYPE_API_KEY=runtype-api-key:latest")}
389
+
390
+ ## Deploy to Cloud Run
391
+
392
+ \`\`\`bash
393
+ gcloud run deploy runtype-deploy \\
394
+ --source . \\
395
+ --region us-central1 \\
396
+ --platform managed \\
397
+ --allow-unauthenticated \\
398
+ --port 8080
399
+ \`\`\`
400
+
401
+ ## Health check
402
+
403
+ \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
404
+ `;
405
+ }
406
+ /**
407
+ * Generates the Cloudflare Workers entry point.
408
+ * Agents are statically imported (no fs access in Workers); each deployed
409
+ * agent needs an explicit import statement so Wrangler bundles the JSON.
410
+ */
411
+ function workerIndexTs(agentSlugs) {
412
+ return `/**
413
+ * Generated by \`runtype deploy --target cloudflare\`.
414
+ *
415
+ * Routes:
416
+ * GET /health — liveness probe (lists registered agent names)
417
+ * POST /agent/:name — SSE stream of the agent event schema
418
+ *
419
+ * Agent definitions are statically imported so Wrangler bundles them.
420
+ * To add a new agent:
421
+ * 1. Export it with \`runtype deploy --agent <id> --target cloudflare\`
422
+ * 2. Or drop a JSON file under agents/ and add an import + entry below
423
+ * 3. Run \`wrangler deploy\`
424
+ */
425
+ import {
426
+ CloudflareAdapter,
427
+ createRuntime,
428
+ exportedAgentSchema,
429
+ type ExportedAgent,
430
+ } from '@runtypelabs/runtime'
431
+
432
+ // ---------------------------------------------------------------------------
433
+ // Agent registry — static imports bundled by Wrangler at deploy time
434
+ // ---------------------------------------------------------------------------
435
+
436
+ ${agentSlugs.map((slug, i) => `import agent${i}Json from '../agents/${slug}.json'`).join("\n")}
437
+
438
+ function parseAgent(json: unknown, filename: string): ExportedAgent {
439
+ try {
440
+ return exportedAgentSchema.parse(json)
441
+ } catch (err) {
442
+ throw new Error(
443
+ \`[runtype] invalid agent JSON in \${filename}: \${
444
+ err instanceof Error ? err.message : String(err)
445
+ }\`
446
+ )
447
+ }
448
+ }
449
+
450
+ const agentFiles: Array<[string, unknown]> = [
451
+ ${agentSlugs.map((slug, i) => ` ['${slug}.json', agent${i}Json],`).join("\n")}
452
+ ]
453
+
454
+ const agentRegistry: Record<string, ExportedAgent> = {}
455
+ for (const [filename, json] of agentFiles) {
456
+ const agent = parseAgent(json, filename)
457
+ if (agentRegistry[agent.name]) {
458
+ throw new Error(\`[runtype] duplicate agent name "\${agent.name}" in \${filename}\`)
459
+ }
460
+ agentRegistry[agent.name] = agent
461
+ }
462
+
463
+ // ---------------------------------------------------------------------------
464
+ // Env — Wrangler injects secrets + bindings here
465
+ // ---------------------------------------------------------------------------
466
+
467
+ export interface Env {
468
+ // Secrets — set via \`wrangler secret put <NAME>\`
469
+ ANTHROPIC_API_KEY?: string
470
+ OPENAI_API_KEY?: string
471
+ GOOGLE_API_KEY?: string
472
+ MIXLAYER_API_KEY?: string
473
+ // Optional: narrow CORS from * to specific origins (comma-separated)
474
+ ALLOWED_ORIGINS?: string
475
+ // Optional: KV namespace binding for durable cross-request caching
476
+ CACHE_KV?: KVNamespace
477
+ // Telemetry: set RUNTYPE_API_KEY (scope TELEMETRY:WRITE) to report runs to Runtype
478
+ RUNTYPE_API_KEY?: string
479
+ // Optional: override the Runtype API origin (default https://api.runtype.com)
480
+ RUNTYPE_API_URL?: string
481
+ // Optional: set to "off" to stop reporting, or to report without conversation content
482
+ RUNTYPE_TELEMETRY?: string
483
+ RUNTYPE_TELEMETRY_CONTENT?: string
484
+ }
485
+
486
+ // ---------------------------------------------------------------------------
487
+ // Worker
488
+ // ---------------------------------------------------------------------------
489
+
490
+ export default {
491
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
492
+ const url = new URL(request.url)
493
+ const cors = corsHeaders(request, env.ALLOWED_ORIGINS)
494
+
495
+ if (request.method === 'OPTIONS') {
496
+ return new Response(null, { status: 204, headers: cors })
497
+ }
498
+
499
+ if (request.method === 'GET' && url.pathname === '/health') {
500
+ return json({ status: 'ok', agents: Object.keys(agentRegistry) }, 200, cors)
501
+ }
502
+
503
+ const agentMatch = url.pathname.match(/^\\/agent\\/([^/]+)$/)
504
+ if (request.method === 'POST' && agentMatch) {
505
+ const name = decodeURIComponent(agentMatch[1])
506
+ if (!agentRegistry[name]) {
507
+ return json({ error: \`unknown agent: \${name}\` }, 404, cors)
508
+ }
509
+
510
+ const runtime = createRuntime({
511
+ agents: agentRegistry,
512
+ keys: { mode: 'own' },
513
+ adapter: new CloudflareAdapter(env, {
514
+ kvNamespace: env.CACHE_KV,
515
+ waitUntil: ctx.waitUntil.bind(ctx),
516
+ }),
517
+ ${telemetryConfigBlock("env", " ")}
518
+ })
519
+
520
+ let body: { messages?: Array<{ role: string; content: string }> } = {}
521
+ try {
522
+ body = (await request.json()) as typeof body
523
+ } catch {
524
+ return json({ error: 'request body must be JSON' }, 400, cors)
525
+ }
526
+ const messages = Array.isArray(body.messages) ? body.messages : []
527
+
528
+ try {
529
+ const response = await runtime.executeAgent(name, {
530
+ messages: messages as Array<{
531
+ role: 'system' | 'user' | 'assistant'
532
+ content: string
533
+ }>,
534
+ signal: request.signal,
535
+ })
536
+ const headers = new Headers(response.headers)
537
+ for (const [k, v] of Object.entries(cors)) headers.set(k, v)
538
+ return new Response(response.body, { status: response.status, headers })
539
+ } catch (err) {
540
+ return json({ error: err instanceof Error ? err.message : 'unknown error' }, 500, cors)
541
+ }
542
+ }
543
+
544
+ return json({ error: 'not found' }, 404, cors)
545
+ },
546
+ }
547
+
548
+ // ---------------------------------------------------------------------------
549
+ // Helpers
550
+ // ---------------------------------------------------------------------------
551
+
552
+ function json(body: unknown, status: number, extra: Record<string, string>): Response {
553
+ return new Response(JSON.stringify(body), {
554
+ status,
555
+ headers: { 'Content-Type': 'application/json', ...extra },
556
+ })
557
+ }
558
+
559
+ function corsHeaders(request: Request, allowedOriginsEnv?: string): Record<string, string> {
560
+ const origin = request.headers.get('Origin') ?? ''
561
+ let allow = '*'
562
+ if (allowedOriginsEnv) {
563
+ const list = allowedOriginsEnv.split(',').map((o) => o.trim()).filter(Boolean)
564
+ allow = list.includes(origin) ? origin : (list[0] ?? '*')
565
+ }
566
+ const headers: Record<string, string> = {
567
+ 'Access-Control-Allow-Origin': allow,
568
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
569
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
570
+ 'Access-Control-Max-Age': '86400',
571
+ }
572
+ if (allow !== '*') headers['Vary'] = 'Origin'
573
+ return headers
574
+ }
575
+ `;
576
+ }
577
+ function wranglerToml(projectName) {
578
+ return `name = "${projectName}"
579
+ main = "src/index.ts"
580
+ compatibility_date = "2025-01-01"
581
+
582
+ # Secrets — set via \`wrangler secret put <NAME>\`
583
+ # Run this for each API key your agents need:
584
+ # wrangler secret put ANTHROPIC_API_KEY
585
+ # wrangler secret put OPENAI_API_KEY
586
+ # wrangler secret put GOOGLE_API_KEY
587
+ # wrangler secret put MIXLAYER_API_KEY
588
+
589
+ # Telemetry: set the Runtype API key (scope \`TELEMETRY:WRITE\`) to report every
590
+ # run to Runtype over OpenTelemetry:
591
+ # wrangler secret put RUNTYPE_API_KEY
592
+
593
+ # Optional: KV namespace for durable cross-request caching.
594
+ # Without this the adapter uses a per-isolate in-memory Map (ephemeral).
595
+ # To enable:
596
+ # 1. wrangler kv namespace create CACHE_KV
597
+ # 2. Uncomment the block below and paste the generated id.
598
+ # [[kv_namespaces]]
599
+ # binding = "CACHE_KV"
600
+ # id = "<paste your KV namespace id here>"
601
+
602
+ # Optional: Workers AI binding (for @cf/* and workers-ai/* model ids).
603
+ # [ai]
604
+ # binding = "AI"
605
+
606
+ # CORS: narrow from * to specific origins via ALLOWED_ORIGINS env var.
607
+ # Telemetry switches: RUNTYPE_TELEMETRY = "off" stops reporting,
608
+ # RUNTYPE_TELEMETRY_CONTENT = "off" reports without messages, tool arguments or
609
+ # results, and RUNTYPE_API_URL overrides https://api.runtype.com.
610
+ # [vars]
611
+ # ALLOWED_ORIGINS = "https://yourapp.com,https://persona-chat.dev"
612
+ # RUNTYPE_TELEMETRY = "off"
613
+ # RUNTYPE_TELEMETRY_CONTENT = "off"
614
+ # RUNTYPE_API_URL = "https://api.runtype.com"
615
+ `;
616
+ }
617
+ function workerPackageJson(name, runtimeDep) {
618
+ return JSON.stringify({
619
+ name,
620
+ version: "0.0.0",
621
+ private: true,
622
+ scripts: {
623
+ dev: "wrangler dev",
624
+ deploy: "wrangler deploy",
625
+ typecheck: "tsc --noEmit"
626
+ },
627
+ dependencies: { "@runtypelabs/runtime": runtimeDep },
628
+ devDependencies: {
629
+ "@cloudflare/workers-types": "^5.0.0",
630
+ typescript: "^5.3.3",
631
+ wrangler: "^4.0.0"
632
+ }
633
+ }, null, 2);
634
+ }
635
+ function workerTsconfigJson() {
636
+ return JSON.stringify({
637
+ compilerOptions: {
638
+ target: "ES2022",
639
+ module: "ES2022",
640
+ moduleResolution: "bundler",
641
+ strict: true,
642
+ resolveJsonModule: true,
643
+ types: ["@cloudflare/workers-types"]
644
+ },
645
+ include: ["src"]
646
+ }, null, 2);
647
+ }
648
+ function workerSetupSh(monorepoRoot) {
649
+ return `#!/usr/bin/env bash
650
+ # Generated by \`runtype deploy --target cloudflare\`.
651
+ #
652
+ # Builds and packs @runtypelabs/runtime from your local monorepo, then
653
+ # installs all dependencies so you can run \`wrangler dev\` or deploy with
654
+ # \`wrangler deploy\`.
655
+ #
656
+ # Usage (from this directory):
657
+ # ./setup.sh
658
+
659
+ set -euo pipefail
660
+
661
+ DEPLOY_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
662
+ MONOREPO_ROOT=${bashSingleQuote(monorepoRoot)}
663
+ TARBALL_DIR="\${DEPLOY_DIR}/packages"
664
+
665
+ echo "[setup] building @runtypelabs/runtime..."
666
+ pnpm --dir "\${MONOREPO_ROOT}" --filter @runtypelabs/runtime build
667
+
668
+ mkdir -p "\${TARBALL_DIR}"
669
+
670
+ echo "[setup] packing @runtypelabs/runtime tarball..."
671
+ pnpm --dir "\${MONOREPO_ROOT}/packages/runtime" pack \\
672
+ --pack-destination "\${TARBALL_DIR}"
673
+
674
+ RUNTIME_TGZ="$(ls "\${TARBALL_DIR}"/runtypelabs-runtime-*.tgz | head -n 1)"
675
+ if [ -z "\${RUNTIME_TGZ}" ]; then
676
+ echo "[setup] error: no tarball produced by pnpm pack" >&2
677
+ exit 1
678
+ fi
679
+ RUNTIME_TGZ_NAME="$(basename "\${RUNTIME_TGZ}")"
680
+
681
+ echo "[setup] updating package.json with file: dependency..."
682
+ node -e "
683
+ const fs = require('fs');
684
+ const pkg = JSON.parse(fs.readFileSync('\${DEPLOY_DIR}/package.json', 'utf8'));
685
+ pkg.dependencies['@runtypelabs/runtime'] = 'file:./packages/\${RUNTIME_TGZ_NAME}';
686
+ fs.writeFileSync('\${DEPLOY_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\\\\n');
687
+ "
688
+
689
+ echo "[setup] installing dependencies..."
690
+ npm install --no-audit --no-fund
691
+
692
+ echo ""
693
+ echo "[setup] Done! Next steps:"
694
+ echo " wrangler dev — preview locally"
695
+ echo " wrangler deploy — deploy to Cloudflare Workers"
696
+ echo ""
697
+ echo "[setup] Telemetry: wrangler secret put RUNTYPE_API_KEY (scope TELEMETRY:WRITE) to report runs"
698
+ echo " RUNTYPE_TELEMETRY=off disable reporting"
699
+ echo " RUNTYPE_TELEMETRY_CONTENT=off report without messages, tool arguments, or results"
700
+ echo " RUNTYPE_API_URL=<origin> override https://api.runtype.com"
701
+ `;
702
+ }
703
+ function workerReadme(agentNames, secretNames, optionalSettings) {
704
+ return `# Runtype Cloudflare Workers Deployment
705
+
706
+ Generated by \`runtype deploy --target cloudflare\`. Contains the following agents:
707
+
708
+ ${agentNames.map((n) => ` - ${n}`).join("\n")}
709
+
710
+ ## Setup
711
+
712
+ \`\`\`bash
713
+ chmod +x setup.sh && ./setup.sh # build runtime + install deps
714
+ \`\`\`
715
+
716
+ ## Secrets
717
+
718
+ Set each API key your agents use:
719
+
720
+ \`\`\`bash
721
+ ${secretNames.length > 0 ? secretNames.map((s) => ` wrangler secret put ${s}`).join("\n") : " (none detected)"}
722
+ \`\`\`
723
+
724
+ ${optionalProviderSettingsSection(optionalSettings)}
725
+
726
+ ${telemetryReadmeSection("wrangler secret put RUNTYPE_API_KEY")}
727
+
728
+ ## Run locally
729
+
730
+ \`\`\`bash
731
+ wrangler dev
732
+ \`\`\`
733
+
734
+ ## Deploy
735
+
736
+ \`\`\`bash
737
+ wrangler deploy
738
+ \`\`\`
739
+
740
+ ## Health check
741
+
742
+ \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
743
+
744
+ ## Call an agent
745
+
746
+ \`\`\`bash
747
+ curl https://<worker-subdomain>.workers.dev/agent/<name> \\
748
+ -H 'Content-Type: application/json' \\
749
+ -d '{"messages":[{"role":"user","content":"Hello"}]}'
750
+ \`\`\`
751
+ `;
752
+ }
753
+ /**
754
+ * Generates the Vercel catch-all API route.
755
+ * Uses Hono for routing. Agent JSONs are statically imported so Vercel
756
+ * bundles them into the function package at build time.
757
+ */
758
+ function vercelRouteTs(agentSlugs) {
759
+ return `/**
760
+ * Generated by \`runtype deploy --target vercel\`.
761
+ *
762
+ * Catch-all Vercel Function. All traffic is rewritten here by vercel.json.
763
+ * Routes:
764
+ * GET /health — liveness probe (lists registered agent names)
765
+ * POST /agent/:name — SSE stream of the agent event schema
766
+ *
767
+ * Agent definitions are statically imported and bundled by Vercel.
768
+ * To add a new agent:
769
+ * 1. Export it with \`runtype deploy --agent <id> --target vercel\`
770
+ * 2. Or drop a JSON file under agents/ and add an import + entry below
771
+ * 3. Run \`vercel deploy\`
772
+ */
773
+ import { Hono } from 'hono'
774
+ import { handle } from 'hono/vercel'
775
+ import { cors } from 'hono/cors'
776
+ import {
777
+ VercelAdapter,
778
+ createRuntime,
779
+ exportedAgentSchema,
780
+ type ExportedAgent,
781
+ } from '@runtypelabs/runtime'
782
+
783
+ // ---------------------------------------------------------------------------
784
+ // Agent registry — built at module load, cached across warm invocations
785
+ // ---------------------------------------------------------------------------
786
+
787
+ ${agentSlugs.map((slug, i) => `import agent${i}Json from '../agents/${slug}.json'`).join("\n")}
788
+
789
+ function parseAgent(json: unknown, filename: string): ExportedAgent {
790
+ try {
791
+ return exportedAgentSchema.parse(json)
792
+ } catch (err) {
793
+ throw new Error(
794
+ \`[runtype] invalid agent JSON in \${filename}: \${
795
+ err instanceof Error ? err.message : String(err)
796
+ }\`
797
+ )
798
+ }
799
+ }
800
+
801
+ const agentFiles: Array<[string, unknown]> = [
802
+ ${agentSlugs.map((slug, i) => ` ['${slug}.json', agent${i}Json],`).join("\n")}
803
+ ]
804
+
805
+ const agentRegistry: Record<string, ExportedAgent> = {}
806
+ for (const [filename, json] of agentFiles) {
807
+ const agent = parseAgent(json, filename)
808
+ if (agentRegistry[agent.name]) {
809
+ throw new Error(\`[runtype] duplicate agent name "\${agent.name}" in \${filename}\`)
810
+ }
811
+ agentRegistry[agent.name] = agent
812
+ }
813
+
814
+ // ---------------------------------------------------------------------------
815
+ // Runtime — constructed at module scope (warm across invocations)
816
+ // ---------------------------------------------------------------------------
817
+
818
+ const runtime = createRuntime({
819
+ agents: agentRegistry,
820
+ keys: { mode: 'own' },
821
+ adapter: new VercelAdapter(),
822
+ ${telemetryConfigBlock("process.env", " ")}
823
+ })
824
+
825
+ // ---------------------------------------------------------------------------
826
+ // Hono app
827
+ // ---------------------------------------------------------------------------
828
+
829
+ const app = new Hono()
830
+
831
+ const allowedOriginsEnv = process.env.ALLOWED_ORIGINS?.trim()
832
+ const allowedOrigins = allowedOriginsEnv
833
+ ? allowedOriginsEnv.split(',').map((o) => o.trim()).filter(Boolean)
834
+ : '*'
835
+
836
+ app.use(
837
+ '*',
838
+ cors({
839
+ origin: allowedOrigins,
840
+ allowMethods: ['GET', 'POST', 'OPTIONS'],
841
+ allowHeaders: ['Content-Type', 'Authorization'],
842
+ maxAge: 86400,
843
+ })
844
+ )
845
+
846
+ app.get('/health', (c) => c.json({ status: 'ok', agents: Object.keys(agentRegistry) }))
847
+
848
+ app.post('/agent/:name', async (c) => {
849
+ const name = c.req.param('name')
850
+ if (!runtime.getAgent(name)) {
851
+ return c.json({ error: \`unknown agent: \${name}\` }, 404)
852
+ }
853
+
854
+ let body: { messages?: Array<{ role: string; content: string }> } = {}
855
+ try {
856
+ body = (await c.req.json()) as typeof body
857
+ } catch {
858
+ return c.json({ error: 'request body must be JSON' }, 400)
859
+ }
860
+ const messages = Array.isArray(body.messages) ? body.messages : []
861
+
862
+ try {
863
+ const response = await runtime.executeAgent(name, {
864
+ messages: messages as Array<{
865
+ role: 'system' | 'user' | 'assistant'
866
+ content: string
867
+ }>,
868
+ signal: c.req.raw.signal,
869
+ })
870
+ return response
871
+ } catch (err) {
872
+ return c.json({ error: err instanceof Error ? err.message : 'unknown error' }, 500)
873
+ }
874
+ })
875
+
876
+ export const GET = handle(app)
877
+ export const POST = handle(app)
878
+ `;
879
+ }
880
+ function vercelJson() {
881
+ return JSON.stringify({
882
+ rewrites: [{
883
+ source: "/(.*)",
884
+ destination: "/api/[[...route]]"
885
+ }],
886
+ functions: { "api/[[...route]].ts": { maxDuration: 60 } }
887
+ }, null, 2);
888
+ }
889
+ function vercelPackageJson(name, runtimeDep) {
890
+ return JSON.stringify({
891
+ name,
892
+ version: "0.0.0",
893
+ private: true,
894
+ scripts: {
895
+ dev: "vercel dev",
896
+ deploy: "vercel deploy",
897
+ typecheck: "tsc --noEmit"
898
+ },
899
+ dependencies: {
900
+ "@runtypelabs/runtime": runtimeDep,
901
+ hono: "^4.12.16"
902
+ },
903
+ devDependencies: {
904
+ "@types/node": "^22.0.0",
905
+ "@vercel/node": "^5.0.0",
906
+ typescript: "^5.3.3",
907
+ vercel: "^44.0.0"
908
+ }
909
+ }, null, 2);
910
+ }
911
+ function vercelTsconfigJson() {
912
+ return JSON.stringify({
913
+ compilerOptions: {
914
+ target: "ES2022",
915
+ module: "ESNext",
916
+ moduleResolution: "bundler",
917
+ strict: true,
918
+ resolveJsonModule: true,
919
+ esModuleInterop: true,
920
+ skipLibCheck: true
921
+ },
922
+ include: ["api"]
923
+ }, null, 2);
924
+ }
925
+ function vercelSetupSh(monorepoRoot) {
926
+ return `#!/usr/bin/env bash
927
+ # Generated by \`runtype deploy --target vercel\`.
928
+ #
929
+ # Builds and packs @runtypelabs/runtime from your local monorepo, then
930
+ # installs all dependencies so you can run \`vercel dev\` or deploy with
931
+ # \`vercel deploy\`.
932
+ #
933
+ # Usage (from this directory):
934
+ # ./setup.sh
935
+
936
+ set -euo pipefail
937
+
938
+ DEPLOY_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
939
+ MONOREPO_ROOT=${bashSingleQuote(monorepoRoot)}
940
+ TARBALL_DIR="\${DEPLOY_DIR}/packages"
941
+
942
+ echo "[setup] building @runtypelabs/runtime..."
943
+ pnpm --dir "\${MONOREPO_ROOT}" --filter @runtypelabs/runtime build
944
+
945
+ mkdir -p "\${TARBALL_DIR}"
946
+
947
+ echo "[setup] packing @runtypelabs/runtime tarball..."
948
+ pnpm --dir "\${MONOREPO_ROOT}/packages/runtime" pack \\
949
+ --pack-destination "\${TARBALL_DIR}"
950
+
951
+ RUNTIME_TGZ="$(ls "\${TARBALL_DIR}"/runtypelabs-runtime-*.tgz | head -n 1)"
952
+ if [ -z "\${RUNTIME_TGZ}" ]; then
953
+ echo "[setup] error: no tarball produced by pnpm pack" >&2
954
+ exit 1
955
+ fi
956
+ RUNTIME_TGZ_NAME="$(basename "\${RUNTIME_TGZ}")"
957
+
958
+ echo "[setup] updating package.json with file: dependency..."
959
+ node -e "
960
+ const fs = require('fs');
961
+ const pkg = JSON.parse(fs.readFileSync('\${DEPLOY_DIR}/package.json', 'utf8'));
962
+ pkg.dependencies['@runtypelabs/runtime'] = 'file:./packages/\${RUNTIME_TGZ_NAME}';
963
+ fs.writeFileSync('\${DEPLOY_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\\\\n');
964
+ "
965
+
966
+ echo "[setup] installing dependencies..."
967
+ npm install --no-audit --no-fund
968
+
969
+ echo ""
970
+ echo "[setup] Done! Next steps:"
971
+ echo " vercel dev — preview locally"
972
+ echo " vercel deploy — deploy to Vercel Functions"
973
+ echo ""
974
+ echo "[setup] Telemetry: vercel env add RUNTYPE_API_KEY (scope TELEMETRY:WRITE) to report runs"
975
+ echo " RUNTYPE_TELEMETRY=off disable reporting"
976
+ echo " RUNTYPE_TELEMETRY_CONTENT=off report without messages, tool arguments, or results"
977
+ echo " RUNTYPE_API_URL=<origin> override https://api.runtype.com"
978
+ `;
979
+ }
980
+ function vercelReadme(agentNames, secretNames, optionalSettings) {
981
+ return `# Runtype Vercel Functions Deployment
982
+
983
+ Generated by \`runtype deploy --target vercel\`. Contains the following agents:
984
+
985
+ ${agentNames.map((n) => ` - ${n}`).join("\n")}
986
+
987
+ ## Setup
988
+
989
+ \`\`\`bash
990
+ chmod +x setup.sh && ./setup.sh # build runtime + install deps
991
+ \`\`\`
992
+
993
+ ## Secrets
994
+
995
+ Set each API key your agents use via the Vercel dashboard or CLI:
996
+
997
+ \`\`\`bash
998
+ ${secretNames.length > 0 ? secretNames.map((s) => ` vercel env add ${s}`).join("\n") : " (none detected)"}
999
+ \`\`\`
1000
+
1001
+ ${optionalProviderSettingsSection(optionalSettings)}
1002
+
1003
+ ${telemetryReadmeSection("vercel env add RUNTYPE_API_KEY")}
1004
+
1005
+ ## Run locally
1006
+
1007
+ \`\`\`bash
1008
+ vercel dev
1009
+ \`\`\`
1010
+
1011
+ ## Deploy
1012
+
1013
+ \`\`\`bash
1014
+ vercel deploy
1015
+ \`\`\`
1016
+
1017
+ ## Health check
1018
+
1019
+ \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
1020
+
1021
+ ## Call an agent
1022
+
1023
+ \`\`\`bash
1024
+ curl https://<your-project>.vercel.app/agent/<name> \\
1025
+ -H 'Content-Type: application/json' \\
1026
+ -d '{"messages":[{"role":"user","content":"Hello"}]}'
1027
+ \`\`\`
1028
+ `;
1029
+ }
1030
+ function collectExportedExecutionInventory(definition) {
1031
+ const models = /* @__PURE__ */ new Set();
1032
+ const namedSecrets = /* @__PURE__ */ new Set();
1033
+ const visited = /* @__PURE__ */ new WeakSet();
1034
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1035
+ const addModel = (value) => {
1036
+ if (typeof value === "string" && value) models.add(value);
1037
+ };
1038
+ const walkMcpServers = (servers) => {
1039
+ if (!Array.isArray(servers)) return;
1040
+ for (const server of servers) {
1041
+ if (!isObject(server) || !isObject(server.auth)) continue;
1042
+ for (const key of [
1043
+ "tokenSecretName",
1044
+ "usernameSecretName",
1045
+ "passwordSecretName"
1046
+ ]) {
1047
+ const value = server.auth[key];
1048
+ if (typeof value === "string" && value) namedSecrets.add(value);
1049
+ }
1050
+ }
1051
+ };
1052
+ function walkRuntimeTools(runtimeTools) {
1053
+ if (!Array.isArray(runtimeTools)) return;
1054
+ for (const tool of runtimeTools) {
1055
+ if (!isObject(tool) || !isObject(tool.config)) continue;
1056
+ if (isObject(tool.config.flow)) walkFlow(tool.config.flow);
1057
+ if (isObject(tool.config.agent)) walkAgent(tool.config.agent);
1058
+ if (isObject(tool.config.skill)) walkRuntimeTools(tool.config.skill.resolvedTools);
1059
+ }
1060
+ }
1061
+ function walkTools(tools) {
1062
+ if (!isObject(tools)) return;
1063
+ walkMcpServers(tools.mcpServers);
1064
+ walkRuntimeTools(tools.runtimeTools);
1065
+ }
1066
+ function walkFallbackModels(config) {
1067
+ if (!isObject(config.errorHandling) || !Array.isArray(config.errorHandling.fallbacks)) return;
1068
+ for (const fallback of config.errorHandling.fallbacks) if (isObject(fallback) && fallback.type === "model") addModel(fallback.model);
1069
+ }
1070
+ function walkSteps(steps) {
1071
+ if (!Array.isArray(steps)) return;
1072
+ for (const step of steps) {
1073
+ if (!isObject(step) || !isObject(step.config)) continue;
1074
+ if (step.type === "prompt") addModel(step.config.model);
1075
+ walkFallbackModels(step.config);
1076
+ walkTools(step.config.tools);
1077
+ if (step.type !== "conditional") continue;
1078
+ for (const key of [
1079
+ "trueSteps",
1080
+ "falseSteps",
1081
+ "otherwiseSteps"
1082
+ ]) walkSteps(step.config[key]);
1083
+ if (Array.isArray(step.config.branches)) {
1084
+ for (const branch of step.config.branches) if (isObject(branch)) walkSteps(branch.steps);
1085
+ }
1086
+ }
1087
+ }
1088
+ function walkFlow(flow) {
1089
+ if (visited.has(flow)) return;
1090
+ visited.add(flow);
1091
+ walkSteps(flow.steps);
1092
+ if (isObject(flow.agents)) {
1093
+ for (const agent of Object.values(flow.agents)) if (isObject(agent)) walkAgent(agent);
1094
+ }
1095
+ }
1096
+ function walkAgent(agent) {
1097
+ if (visited.has(agent)) return;
1098
+ visited.add(agent);
1099
+ if (isObject(agent.config)) {
1100
+ addModel(agent.config.model);
1101
+ walkTools(agent.config.tools);
1102
+ }
1103
+ if (isObject(agent.claudeManagedConfig)) addModel(agent.claudeManagedConfig.model);
1104
+ if (Array.isArray(agent.skills)) {
1105
+ for (const skill of agent.skills) if (isObject(skill)) walkRuntimeTools(skill.resolvedTools);
1106
+ }
1107
+ if (Array.isArray(agent.capabilities)) for (const capability of agent.capabilities) {
1108
+ if (!isObject(capability)) continue;
1109
+ if (isObject(capability.flow)) walkFlow(capability.flow);
1110
+ if (isObject(capability.subAgent)) walkAgent(capability.subAgent);
1111
+ }
1112
+ }
1113
+ if (isObject(definition)) {
1114
+ if (Array.isArray(definition.steps) && !isObject(definition.config)) walkFlow(definition);
1115
+ else walkAgent(definition);
1116
+ }
1117
+ return {
1118
+ models: Array.from(models),
1119
+ namedSecrets: Array.from(namedSecrets)
1120
+ };
1121
+ }
1122
+ function collectDeploymentSecretInventory(agentDef) {
1123
+ const requiredNames = /* @__PURE__ */ new Set();
1124
+ const optionalSettings = /* @__PURE__ */ new Map();
1125
+ const executionInventory = collectExportedExecutionInventory(agentDef);
1126
+ for (const model of executionInventory.models) {
1127
+ for (const name of resolveModelSecretNames(model)) requiredNames.add(name);
1128
+ for (const setting of resolveModelOptionalSettings(model)) optionalSettings.set(setting.name, setting);
1129
+ }
1130
+ for (const name of executionInventory.namedSecrets) requiredNames.add(name);
1131
+ for (const name of extractSecretReferencesFromAnyValue(agentDef, 512)) requiredNames.add(name);
1132
+ for (const name of requiredNames) optionalSettings.delete(name);
1133
+ return {
1134
+ requiredNames: Array.from(requiredNames),
1135
+ optionalSettings: Array.from(optionalSettings.values())
1136
+ };
1137
+ }
1138
+ function slugify(s) {
1139
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
1140
+ }
1141
+ /**
1142
+ * Why `runtype deploy --flow` no longer scaffolds, and the two supported ways to
1143
+ * deploy a flow instead. Exported so a test pins that both stay named.
1144
+ */
1145
+ const RETIRED_DEPLOY_FLOW_MESSAGE = [
1146
+ "Error: `runtype deploy --flow` is retired (ADR 0024).",
1147
+ " The scaffold wrapped an exported flow in a primary-flow agent, and that agent execution mode is gone.",
1148
+ " Two ways to deploy a flow instead:",
1149
+ " 1. Attach the flow to an agent as a runtime tool ({ toolType: 'flow', flowId }), then deploy that agent:",
1150
+ " runtype deploy --agent <agentId>",
1151
+ " 2. Run the flow on the hosted API without scaffolding anything:",
1152
+ " runtype dispatch --flow <flowId>"
1153
+ ];
1154
+ const BYOC_PLAN_REQUIRED_CODE = "BYOC_PLAN_REQUIRED";
1155
+ /**
1156
+ * Hints printed under a failed `GET /agents/{id}/export-runtime`, keyed on the
1157
+ * API's own denial contract: a 403 is the plan or scope gate, a 404 is the
1158
+ * agent itself. Exported so a test pins that a 404 is never read as an
1159
+ * environment restriction again.
1160
+ */
1161
+ function describeAgentExportFailure(err) {
1162
+ if (!(err instanceof RuntypeApiError)) return [];
1163
+ if (err.statusCode === 403) {
1164
+ if ((err.data && typeof err.data === "object" && "code" in err.data ? err.data.code : void 0) === "BYOC_PLAN_REQUIRED") return ["Self-hosting (BYOC) requires an Enterprise plan. Contact support to enable it for your account.", "The API key also needs the RUNTIME:EXPORT scope."];
1165
+ return ["The API key needs the RUNTIME:EXPORT scope, and self-hosting (BYOC) requires an Enterprise plan."];
1166
+ }
1167
+ if (err.statusCode === 404) {
1168
+ const code = err.data && typeof err.data === "object" && "code" in err.data ? err.data.code : void 0;
1169
+ if (typeof code === "string" && RELEASE_SELECTOR_NOT_FOUND_CODES.has(code)) return ["The requested --alias or --version-id does not exist on this agent, or the alias is archived or expired."];
1170
+ return ["Agent not found, or not owned by this account."];
1171
+ }
1172
+ return [];
1173
+ }
1174
+ const RELEASE_SELECTOR_NOT_FOUND_CODES = /* @__PURE__ */ new Set([
1175
+ "alias_not_found",
1176
+ "alias_archived",
1177
+ "alias_expired",
1178
+ "version_not_found"
1179
+ ]);
1180
+ /** The export-runtime query for a release selector; errors when both selectors are given. */
1181
+ function agentReleaseSelectorParams(options, agentCount) {
1182
+ if (options.alias !== void 0 && options.versionId !== void 0) return { error: "Pass either --alias or --version-id, not both." };
1183
+ if (options.versionId !== void 0 && agentCount !== 1) return { error: "--version-id names one agent version, so pass exactly one --agent with it." };
1184
+ return { params: {
1185
+ ...options.alias !== void 0 ? { alias: options.alias } : {},
1186
+ ...options.versionId !== void 0 ? { versionId: options.versionId } : {}
1187
+ } };
1188
+ }
1189
+ function describeExportedRelease(def) {
1190
+ const release = def.release;
1191
+ if (!release?.versionId) return "";
1192
+ const via = release.alias ? ` via ${release.alias}${release.aliasRevision !== void 0 ? ` r${release.aliasRevision}` : ""}` : "";
1193
+ return chalk.dim(` (${release.versionId}${via})`);
1194
+ }
1195
+ const deployCommand = new Command("deploy").description("Export an agent and scaffold a deployment (cloud-run, cloudflare, or vercel)").option("--agent <id>", "Agent ID to export (may be repeated)", (v, acc) => {
1196
+ acc.push(v);
1197
+ return acc;
1198
+ }, []).option("--flow <id>", "Removed (ADR 0024): export the agent that runs the flow instead", (v, acc) => {
1199
+ acc.push(v);
1200
+ return acc;
1201
+ }, []).option("--output <dir>", "Output directory for the scaffold (default: ./runtype-deploy)", "./runtype-deploy").option("--name <name>", "Project name used in package.json (default: derived from output dir)").option("--target <target>", "Deployment target: cloud-run, cloudflare, or vercel (default: cloud-run)", "cloud-run").option("--alias <name>", "Export the version each agent's release alias points at (e.g. staging, live, pr-42)").option("--version-id <id>", "Export this published agent version (requires exactly one --agent)").action(async (options) => {
1202
+ const agentIds = options.agent;
1203
+ if (options.flow.length > 0) {
1204
+ for (const line of RETIRED_DEPLOY_FLOW_MESSAGE) console.error(chalk.yellow(line));
1205
+ process.exit(1);
1206
+ }
1207
+ if (agentIds.length === 0) {
1208
+ console.error(chalk.red("Error: provide at least one --agent <id>"));
1209
+ process.exit(1);
1210
+ }
1211
+ const selector = agentReleaseSelectorParams(options, agentIds.length);
1212
+ if ("error" in selector) {
1213
+ console.error(chalk.red(`Error: ${selector.error}`));
1214
+ process.exit(1);
1215
+ }
1216
+ const target = options.target;
1217
+ if (target !== "cloud-run" && target !== "cloudflare" && target !== "vercel") {
1218
+ console.error(chalk.red(`Error: unknown --target "${target}". Must be one of: cloud-run, cloudflare, vercel`));
1219
+ process.exit(1);
1220
+ }
1221
+ const apiKey = await ensureAuth();
1222
+ if (!apiKey) return;
1223
+ const client = createCliClient(apiKey);
1224
+ const outDir = path$1.resolve(options.output);
1225
+ const projectName = options.name ?? slugify(path$1.basename(outDir));
1226
+ console.log(chalk.cyan(`\nScaffolding ${target} deployment to ${outDir}\n`));
1227
+ const agentDefs = [];
1228
+ for (const id of agentIds) {
1229
+ process.stdout.write(` Exporting agent ${chalk.green(id)}... `);
1230
+ try {
1231
+ const def = await client.get(`/agents/${id}/export-runtime`, selector.params);
1232
+ agentDefs.push({
1233
+ id,
1234
+ name: String(def.name ?? id),
1235
+ def
1236
+ });
1237
+ process.stdout.write(chalk.green("✓") + describeExportedRelease(def) + "\n");
1238
+ } catch (err) {
1239
+ const msg = err instanceof RuntypeApiError ? err.message : String(err);
1240
+ process.stdout.write(chalk.red(`✗\n`));
1241
+ console.error(chalk.red(` Failed to export agent ${id}: ${msg}`));
1242
+ for (const hint of describeAgentExportFailure(err)) console.error(chalk.yellow(` ${hint}`));
1243
+ process.exit(1);
1244
+ }
1245
+ }
1246
+ const agentSlugs = agentDefs.map(({ name }) => slugify(name));
1247
+ const slugSet = /* @__PURE__ */ new Set();
1248
+ for (let i = 0; i < agentSlugs.length; i++) {
1249
+ const slug = agentSlugs[i];
1250
+ if (slugSet.has(slug)) {
1251
+ const colliders = agentDefs.filter(({ name }) => slugify(name) === slug).map(({ name }) => `"${name}"`).join(" and ");
1252
+ console.error(chalk.red(`Error: agents ${colliders} produce the same slug "${slug}".`));
1253
+ console.error(chalk.yellow(" Rename one of them in the Runtype dashboard to make slugs unique."));
1254
+ process.exit(1);
1255
+ }
1256
+ slugSet.add(slug);
1257
+ }
1258
+ fs$1.mkdirSync(path$1.join(outDir, "agents"), { recursive: true });
1259
+ fs$1.mkdirSync(path$1.join(outDir, "packages"), { recursive: true });
1260
+ for (const { name, def } of agentDefs) {
1261
+ const filename = `${slugify(name)}.json`;
1262
+ fs$1.writeFileSync(path$1.join(outDir, "agents", filename), JSON.stringify(def, null, 2));
1263
+ console.log(` Wrote agents/${filename}`);
1264
+ }
1265
+ const agentNames = agentDefs.map((a) => a.name);
1266
+ let monorepoRoot = process.cwd();
1267
+ for (let i = 0; i < 8; i++) {
1268
+ if (fs$1.existsSync(path$1.join(monorepoRoot, "pnpm-workspace.yaml"))) break;
1269
+ const parent = path$1.dirname(monorepoRoot);
1270
+ if (parent === monorepoRoot) break;
1271
+ monorepoRoot = parent;
1272
+ }
1273
+ const allSecrets = /* @__PURE__ */ new Set();
1274
+ const allOptionalSettings = /* @__PURE__ */ new Map();
1275
+ for (const { def } of agentDefs) {
1276
+ const inventory = collectDeploymentSecretInventory(def);
1277
+ for (const name of inventory.requiredNames) allSecrets.add(name);
1278
+ for (const setting of inventory.optionalSettings) allOptionalSettings.set(setting.name, setting);
1279
+ }
1280
+ for (const name of allSecrets) allOptionalSettings.delete(name);
1281
+ const secretNames = Array.from(allSecrets);
1282
+ const optionalSettings = Array.from(allOptionalSettings.values());
1283
+ if (target === "cloudflare") {
1284
+ fs$1.mkdirSync(path$1.join(outDir, "src"), { recursive: true });
1285
+ fs$1.writeFileSync(path$1.join(outDir, "src", "index.ts"), workerIndexTs(agentSlugs));
1286
+ fs$1.writeFileSync(path$1.join(outDir, "wrangler.toml"), wranglerToml(projectName));
1287
+ fs$1.writeFileSync(path$1.join(outDir, "package.json"), workerPackageJson(projectName, "workspace:*"));
1288
+ fs$1.writeFileSync(path$1.join(outDir, "tsconfig.json"), workerTsconfigJson());
1289
+ const setupPath = path$1.join(outDir, "setup.sh");
1290
+ fs$1.writeFileSync(setupPath, workerSetupSh(monorepoRoot));
1291
+ fs$1.chmodSync(setupPath, 493);
1292
+ fs$1.writeFileSync(path$1.join(outDir, "README.md"), workerReadme(agentNames, secretNames, optionalSettings));
1293
+ } else if (target === "vercel") {
1294
+ fs$1.mkdirSync(path$1.join(outDir, "api"), { recursive: true });
1295
+ fs$1.writeFileSync(path$1.join(outDir, "api", "[[...route]].ts"), vercelRouteTs(agentSlugs));
1296
+ fs$1.writeFileSync(path$1.join(outDir, "vercel.json"), vercelJson());
1297
+ fs$1.writeFileSync(path$1.join(outDir, "package.json"), vercelPackageJson(projectName, "workspace:*"));
1298
+ fs$1.writeFileSync(path$1.join(outDir, "tsconfig.json"), vercelTsconfigJson());
1299
+ const setupPath = path$1.join(outDir, "setup.sh");
1300
+ fs$1.writeFileSync(setupPath, vercelSetupSh(monorepoRoot));
1301
+ fs$1.chmodSync(setupPath, 493);
1302
+ fs$1.writeFileSync(path$1.join(outDir, "README.md"), vercelReadme(agentNames, secretNames, optionalSettings));
1303
+ } else {
1304
+ fs$1.writeFileSync(path$1.join(outDir, "server.ts"), serverTs());
1305
+ fs$1.writeFileSync(path$1.join(outDir, "Dockerfile"), dockerfile());
1306
+ fs$1.writeFileSync(path$1.join(outDir, ".dockerignore"), dockerignore());
1307
+ fs$1.writeFileSync(path$1.join(outDir, "tsconfig.json"), tsconfigJson());
1308
+ fs$1.writeFileSync(path$1.join(outDir, "package.json"), packageJson(projectName, "workspace:*"));
1309
+ const setupPath = path$1.join(outDir, "setup.sh");
1310
+ fs$1.writeFileSync(setupPath, setupSh(monorepoRoot));
1311
+ fs$1.chmodSync(setupPath, 493);
1312
+ fs$1.writeFileSync(path$1.join(outDir, "README.md"), readme(agentNames, secretNames, optionalSettings));
1313
+ }
1314
+ console.log("");
1315
+ console.log(chalk.green(`✓ Scaffold written to ${outDir}`));
1316
+ console.log("");
1317
+ console.log(chalk.bold("Next steps:"));
1318
+ console.log(` cd ${options.output}`);
1319
+ console.log(" chmod +x setup.sh && ./setup.sh # pack runtime + install deps");
1320
+ if (optionalSettings.length > 0) {
1321
+ console.log("");
1322
+ console.log(chalk.yellow("Optional provider settings (defaults used when unset):"));
1323
+ for (const setting of optionalSettings) console.log(` ${chalk.cyan(setting.name)} (default: ${chalk.cyan(setting.defaultValue)})`);
1324
+ }
1325
+ if (target === "cloudflare") {
1326
+ console.log(" wrangler dev # test locally");
1327
+ console.log("");
1328
+ if (secretNames.length > 0) {
1329
+ console.log(chalk.yellow("Required secrets (set via wrangler):"));
1330
+ for (const s of secretNames) console.log(` wrangler secret put ${chalk.cyan(s)}`);
1331
+ console.log("");
1332
+ }
1333
+ console.log("Deploy to Cloudflare Workers:");
1334
+ console.log(" wrangler deploy");
1335
+ } else if (target === "vercel") {
1336
+ console.log(" vercel dev # test locally");
1337
+ console.log("");
1338
+ if (secretNames.length > 0) {
1339
+ console.log(chalk.yellow("Required secrets (set via Vercel):"));
1340
+ for (const s of secretNames) console.log(` vercel env add ${chalk.cyan(s)}`);
1341
+ console.log("");
1342
+ }
1343
+ console.log("Deploy to Vercel Functions:");
1344
+ console.log(" vercel deploy");
1345
+ } else {
1346
+ console.log(" npm run dev # test locally");
1347
+ console.log("");
1348
+ if (secretNames.length > 0) {
1349
+ console.log(chalk.yellow("Required secrets (set before deploying):"));
1350
+ for (const s of secretNames) console.log(` ${chalk.cyan(s)}`);
1351
+ console.log("");
1352
+ console.log(" Set via Cloud Run: gcloud run deploy … --set-secrets KEY=NAME:latest");
1353
+ console.log(" Or export locally: export KEY=<value>");
1354
+ console.log("");
1355
+ }
1356
+ console.log("Deploy to Cloud Run:");
1357
+ console.log(` gcloud run deploy ${projectName} --source ${options.output} \\`);
1358
+ console.log(" --region us-central1 --platform managed \\");
1359
+ console.log(" --allow-unauthenticated --port 8080");
1360
+ }
1361
+ });
1362
+ export { deploy_exports as n, describeAgentExportFailure as r, agentReleaseSelectorParams as t };
1363
+