@runtypelabs/cli 2.14.0 → 2.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 (2) hide show
  1. package/dist/index.js +806 -62
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -26255,7 +26255,8 @@ var BuiltInToolCategory = {
26255
26255
  DATA_MANAGEMENT: "data_management",
26256
26256
  COMMERCE: "commerce",
26257
26257
  BROWSER: "browser",
26258
- SANDBOX: "sandbox"
26258
+ SANDBOX: "sandbox",
26259
+ MESSAGING: "messaging"
26259
26260
  };
26260
26261
  var BuiltInToolIdPrefix = {
26261
26262
  BUILTIN: "builtin",
@@ -26651,7 +26652,15 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
26651
26652
  modelCompatibility: [
26652
26653
  {
26653
26654
  provider: BuiltInToolProvider.ANTHROPIC,
26654
- models: ["claude-opus-4", "claude-sonnet-4", "claude-opus-4-5", "claude-sonnet-4-5", "claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"]
26655
+ models: [
26656
+ "claude-opus-4",
26657
+ "claude-sonnet-4",
26658
+ "claude-opus-4-5",
26659
+ "claude-sonnet-4-5",
26660
+ "claude-opus-4-6",
26661
+ "claude-sonnet-4-6",
26662
+ "claude-haiku-4-5"
26663
+ ]
26655
26664
  }
26656
26665
  ],
26657
26666
  documentationUrl: "https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic#web-search",
@@ -26742,7 +26751,10 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
26742
26751
  type: "string",
26743
26752
  enum: ["web", "x", "news", "rss"]
26744
26753
  },
26745
- country: { type: "string", description: "ISO 3166-1 alpha-2 country code (web/news)." },
26754
+ country: {
26755
+ type: "string",
26756
+ description: "ISO 3166-1 alpha-2 country code (web/news)."
26757
+ },
26746
26758
  allowedWebsites: {
26747
26759
  type: "array",
26748
26760
  description: "Up to 5 domains to restrict search to (web only).",
@@ -28231,7 +28243,10 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
28231
28243
  city: { type: "string", description: "City" },
28232
28244
  province_code: { type: "string", description: "State/province code" },
28233
28245
  postal_code: { type: "string", description: "Postal/ZIP code" },
28234
- country_code: { type: "string", description: "ISO 3166-1 alpha-2 country code" }
28246
+ country_code: {
28247
+ type: "string",
28248
+ description: "ISO 3166-1 alpha-2 country code"
28249
+ }
28235
28250
  }
28236
28251
  },
28237
28252
  phone_number: { type: "string", description: "Recipient phone number" }
@@ -28349,7 +28364,10 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
28349
28364
  city: { type: "string", description: "City" },
28350
28365
  province_code: { type: "string", description: "State/province code" },
28351
28366
  postal_code: { type: "string", description: "Postal/ZIP code" },
28352
- country_code: { type: "string", description: "ISO 3166-1 alpha-2 country code" }
28367
+ country_code: {
28368
+ type: "string",
28369
+ description: "ISO 3166-1 alpha-2 country code"
28370
+ }
28353
28371
  }
28354
28372
  },
28355
28373
  phone_number: { type: "string", description: "Recipient phone number" }
@@ -28556,7 +28574,10 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
28556
28574
  intent: { type: "string", description: "Buyer intent description" }
28557
28575
  }
28558
28576
  },
28559
- bearerToken: { type: "string", description: "Bearer token for authenticated session (optional)" }
28577
+ bearerToken: {
28578
+ type: "string",
28579
+ description: "Bearer token for authenticated session (optional)"
28580
+ }
28560
28581
  },
28561
28582
  required: ["domain", "items"]
28562
28583
  },
@@ -29180,6 +29201,81 @@ var CORE_BUILTIN_TOOLS_REGISTRY = [
29180
29201
  executionHint: "platform",
29181
29202
  requiresApiKey: false,
29182
29203
  platformKeySupport: true
29204
+ },
29205
+ // Send email via Resend. Mirrors the send-email context step so agents can
29206
+ // dispatch transactional email without wrapping it in a flow.
29207
+ {
29208
+ id: "send_email",
29209
+ name: "Send Email",
29210
+ description: 'Send a transactional email via Resend. Leave "from" blank to use the platform-managed sender; set it to send from your own verified domain (requires a custom Resend key configured in Settings > Integrations).',
29211
+ category: BuiltInToolCategory.MESSAGING,
29212
+ providers: [BuiltInToolProvider.MULTI],
29213
+ parametersSchema: {
29214
+ type: "object",
29215
+ properties: {
29216
+ to: {
29217
+ type: "string",
29218
+ description: "Recipient email address. Multiple addresses can be provided comma-separated.",
29219
+ minLength: 1
29220
+ },
29221
+ subject: {
29222
+ type: "string",
29223
+ description: "Email subject line.",
29224
+ minLength: 1
29225
+ },
29226
+ html: {
29227
+ type: "string",
29228
+ description: "HTML body of the email. Provide either html or text (or both); at least one is required."
29229
+ },
29230
+ text: {
29231
+ type: "string",
29232
+ description: "Plain-text body of the email. Provide either html or text (or both); at least one is required."
29233
+ },
29234
+ from: {
29235
+ type: "string",
29236
+ description: "Optional sender address. Leave blank to use the platform-managed sender (e.g. agent_id@<platform-domain>). Custom values require your own Resend API key and a verified domain."
29237
+ },
29238
+ cc: {
29239
+ type: "string",
29240
+ description: "Optional CC recipients, comma-separated."
29241
+ },
29242
+ bcc: {
29243
+ type: "string",
29244
+ description: "Optional BCC recipients, comma-separated."
29245
+ },
29246
+ replyTo: {
29247
+ type: "string",
29248
+ description: "Optional Reply-To address."
29249
+ },
29250
+ attachments: {
29251
+ type: "array",
29252
+ description: "Optional file attachments. Each item must include a filename and base64-encoded content.",
29253
+ items: {
29254
+ type: "object",
29255
+ properties: {
29256
+ filename: {
29257
+ type: "string",
29258
+ description: "Attachment filename, including extension."
29259
+ },
29260
+ content: {
29261
+ type: "string",
29262
+ description: "Base64-encoded file contents."
29263
+ },
29264
+ contentType: {
29265
+ type: "string",
29266
+ description: "Optional MIME type (e.g. application/pdf)."
29267
+ }
29268
+ },
29269
+ required: ["filename", "content"]
29270
+ }
29271
+ }
29272
+ },
29273
+ required: ["to", "subject"]
29274
+ },
29275
+ documentationUrl: "https://resend.com/docs/api-reference/emails/send-email",
29276
+ requiresApiKey: false,
29277
+ executionHint: "platform",
29278
+ platformKeySupport: true
29183
29279
  }
29184
29280
  ];
29185
29281
  var BUILTIN_TOOLS_REGISTRY = [
@@ -34687,7 +34783,8 @@ var CATEGORY_DISPLAY_NAMES = {
34687
34783
  [BuiltInToolCategory.THIRD_PARTY_API]: "Third-Party API",
34688
34784
  [BuiltInToolCategory.ARTIFACT]: "Artifact",
34689
34785
  [BuiltInToolCategory.DATA_MANAGEMENT]: "Data Management",
34690
- [BuiltInToolCategory.COMMERCE]: "Commerce"
34786
+ [BuiltInToolCategory.COMMERCE]: "Commerce",
34787
+ [BuiltInToolCategory.MESSAGING]: "Messaging"
34691
34788
  };
34692
34789
  var TOOL_CATALOG_FOR_FLOW_GENERATION = (() => {
34693
34790
  const providerNativeSearchIds = /* @__PURE__ */ new Set([
@@ -35408,15 +35505,17 @@ var FlowInputSchema = external_exports.object({
35408
35505
  id: external_exports.string().min(1).optional(),
35409
35506
  name: external_exports.string().min(1).optional(),
35410
35507
  description: external_exports.string().optional(),
35411
- steps: external_exports.array(external_exports.any()).optional()
35508
+ steps: external_exports.array(external_exports.any()).optional(),
35509
+ contentHash: external_exports.string().length(64).optional()
35412
35510
  }).refine(
35413
35511
  (data) => {
35414
35512
  const hasFlowId = !!data.id;
35415
35513
  const hasSteps = !!(data.name && data.steps && data.steps.length > 0);
35416
- return hasFlowId || hasSteps;
35514
+ const hasPersistedFlow = !!(data.name && data.contentHash);
35515
+ return hasFlowId || hasSteps || hasPersistedFlow;
35417
35516
  },
35418
35517
  {
35419
- message: "Either 'id' or flow definition with 'name' and 'steps' must be provided."
35518
+ message: "Either 'id', flow definition with 'name' and 'steps', or persisted flow with 'name' and 'contentHash' must be provided."
35420
35519
  }
35421
35520
  );
35422
35521
  var AgentLoopConfigSchema = external_exports.object({
@@ -53970,6 +54069,612 @@ gcloud run deploy runtype-deploy \\
53970
54069
  \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
53971
54070
  `;
53972
54071
  }
54072
+ function workerIndexTs(agentSlugs) {
54073
+ const imports = agentSlugs.map((slug, i) => `import agent${i}Json from '../agents/${slug}.json'`).join("\n");
54074
+ const agentFiles = agentSlugs.map((slug, i) => ` ['${slug}.json', agent${i}Json],`).join("\n");
54075
+ return `/**
54076
+ * Generated by \`runtype deploy --target cloudflare\`.
54077
+ *
54078
+ * Routes:
54079
+ * GET /health \u2014 liveness probe (lists registered agent names)
54080
+ * POST /agent/:name \u2014 SSE stream of the agent event schema
54081
+ *
54082
+ * Agent definitions are statically imported so Wrangler bundles them.
54083
+ * To add a new agent:
54084
+ * 1. Export it with \`runtype deploy --agent <id> --target cloudflare\`
54085
+ * 2. Or drop a JSON file under agents/ and add an import + entry below
54086
+ * 3. Run \`wrangler deploy\`
54087
+ */
54088
+ import {
54089
+ CloudflareAdapter,
54090
+ createRuntime,
54091
+ exportedAgentSchema,
54092
+ type ExportedAgent,
54093
+ } from '@runtypelabs/runtime'
54094
+
54095
+ // ---------------------------------------------------------------------------
54096
+ // Agent registry \u2014 static imports bundled by Wrangler at deploy time
54097
+ // ---------------------------------------------------------------------------
54098
+
54099
+ ${imports}
54100
+
54101
+ function parseAgent(json: unknown, filename: string): ExportedAgent {
54102
+ try {
54103
+ return exportedAgentSchema.parse(json)
54104
+ } catch (err) {
54105
+ throw new Error(
54106
+ \`[runtype] invalid agent JSON in \${filename}: \${
54107
+ err instanceof Error ? err.message : String(err)
54108
+ }\`
54109
+ )
54110
+ }
54111
+ }
54112
+
54113
+ const agentFiles: Array<[string, unknown]> = [
54114
+ ${agentFiles}
54115
+ ]
54116
+
54117
+ const agentRegistry: Record<string, ExportedAgent> = {}
54118
+ for (const [filename, json] of agentFiles) {
54119
+ const agent = parseAgent(json, filename)
54120
+ if (agentRegistry[agent.name]) {
54121
+ throw new Error(\`[runtype] duplicate agent name "\${agent.name}" in \${filename}\`)
54122
+ }
54123
+ agentRegistry[agent.name] = agent
54124
+ }
54125
+
54126
+ // ---------------------------------------------------------------------------
54127
+ // Env \u2014 Wrangler injects secrets + bindings here
54128
+ // ---------------------------------------------------------------------------
54129
+
54130
+ export interface Env {
54131
+ // Secrets \u2014 set via \`wrangler secret put <NAME>\`
54132
+ ANTHROPIC_API_KEY?: string
54133
+ OPENAI_API_KEY?: string
54134
+ GOOGLE_API_KEY?: string
54135
+ MIXLAYER_API_KEY?: string
54136
+ // Optional: narrow CORS from * to specific origins (comma-separated)
54137
+ ALLOWED_ORIGINS?: string
54138
+ // Optional: KV namespace binding for durable cross-request caching
54139
+ CACHE_KV?: KVNamespace
54140
+ }
54141
+
54142
+ // ---------------------------------------------------------------------------
54143
+ // Worker
54144
+ // ---------------------------------------------------------------------------
54145
+
54146
+ export default {
54147
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
54148
+ const url = new URL(request.url)
54149
+ const cors = corsHeaders(request, env.ALLOWED_ORIGINS)
54150
+
54151
+ if (request.method === 'OPTIONS') {
54152
+ return new Response(null, { status: 204, headers: cors })
54153
+ }
54154
+
54155
+ if (request.method === 'GET' && url.pathname === '/health') {
54156
+ return json({ status: 'ok', agents: Object.keys(agentRegistry) }, 200, cors)
54157
+ }
54158
+
54159
+ const agentMatch = url.pathname.match(/^\\/agent\\/([^/]+)$/)
54160
+ if (request.method === 'POST' && agentMatch) {
54161
+ const name = decodeURIComponent(agentMatch[1])
54162
+ if (!agentRegistry[name]) {
54163
+ return json({ error: \`unknown agent: \${name}\` }, 404, cors)
54164
+ }
54165
+
54166
+ const runtime = createRuntime({
54167
+ agents: agentRegistry,
54168
+ keys: { mode: 'own' },
54169
+ adapter: new CloudflareAdapter(env, {
54170
+ kvNamespace: env.CACHE_KV,
54171
+ waitUntil: ctx.waitUntil.bind(ctx),
54172
+ }),
54173
+ })
54174
+
54175
+ let body: { messages?: Array<{ role: string; content: string }> } = {}
54176
+ try {
54177
+ body = (await request.json()) as typeof body
54178
+ } catch {
54179
+ return json({ error: 'request body must be JSON' }, 400, cors)
54180
+ }
54181
+ const messages = Array.isArray(body.messages) ? body.messages : []
54182
+
54183
+ try {
54184
+ const response = await runtime.executeAgent(name, {
54185
+ messages: messages as Array<{
54186
+ role: 'system' | 'user' | 'assistant'
54187
+ content: string
54188
+ }>,
54189
+ signal: request.signal,
54190
+ })
54191
+ const headers = new Headers(response.headers)
54192
+ for (const [k, v] of Object.entries(cors)) headers.set(k, v)
54193
+ return new Response(response.body, { status: response.status, headers })
54194
+ } catch (err) {
54195
+ return json({ error: err instanceof Error ? err.message : 'unknown error' }, 500, cors)
54196
+ }
54197
+ }
54198
+
54199
+ return json({ error: 'not found' }, 404, cors)
54200
+ },
54201
+ }
54202
+
54203
+ // ---------------------------------------------------------------------------
54204
+ // Helpers
54205
+ // ---------------------------------------------------------------------------
54206
+
54207
+ function json(body: unknown, status: number, extra: Record<string, string>): Response {
54208
+ return new Response(JSON.stringify(body), {
54209
+ status,
54210
+ headers: { 'Content-Type': 'application/json', ...extra },
54211
+ })
54212
+ }
54213
+
54214
+ function corsHeaders(request: Request, allowedOriginsEnv?: string): Record<string, string> {
54215
+ const origin = request.headers.get('Origin') ?? ''
54216
+ let allow = '*'
54217
+ if (allowedOriginsEnv) {
54218
+ const list = allowedOriginsEnv.split(',').map((o) => o.trim()).filter(Boolean)
54219
+ allow = list.includes(origin) ? origin : (list[0] ?? '*')
54220
+ }
54221
+ const headers: Record<string, string> = {
54222
+ 'Access-Control-Allow-Origin': allow,
54223
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
54224
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
54225
+ 'Access-Control-Max-Age': '86400',
54226
+ }
54227
+ if (allow !== '*') headers['Vary'] = 'Origin'
54228
+ return headers
54229
+ }
54230
+ `;
54231
+ }
54232
+ function wranglerToml(projectName) {
54233
+ return `name = "${projectName}"
54234
+ main = "src/index.ts"
54235
+ compatibility_date = "2025-01-01"
54236
+
54237
+ # Secrets \u2014 set via \`wrangler secret put <NAME>\`
54238
+ # Run this for each API key your agents need:
54239
+ # wrangler secret put ANTHROPIC_API_KEY
54240
+ # wrangler secret put OPENAI_API_KEY
54241
+ # wrangler secret put GOOGLE_API_KEY
54242
+ # wrangler secret put MIXLAYER_API_KEY
54243
+
54244
+ # Optional: KV namespace for durable cross-request caching.
54245
+ # Without this the adapter uses a per-isolate in-memory Map (ephemeral).
54246
+ # To enable:
54247
+ # 1. wrangler kv namespace create CACHE_KV
54248
+ # 2. Uncomment the block below and paste the generated id.
54249
+ # [[kv_namespaces]]
54250
+ # binding = "CACHE_KV"
54251
+ # id = "<paste your KV namespace id here>"
54252
+
54253
+ # Optional: Workers AI binding (for @cf/* and workers-ai/* model ids).
54254
+ # [ai]
54255
+ # binding = "AI"
54256
+
54257
+ # CORS: narrow from * to specific origins via ALLOWED_ORIGINS env var.
54258
+ # [vars]
54259
+ # ALLOWED_ORIGINS = "https://yourapp.com,https://persona-chat.dev"
54260
+ `;
54261
+ }
54262
+ function workerPackageJson(name, runtimeDep) {
54263
+ return JSON.stringify(
54264
+ {
54265
+ name,
54266
+ version: "0.0.0",
54267
+ private: true,
54268
+ scripts: {
54269
+ dev: "wrangler dev",
54270
+ deploy: "wrangler deploy",
54271
+ typecheck: "tsc --noEmit"
54272
+ },
54273
+ dependencies: {
54274
+ "@runtypelabs/runtime": runtimeDep
54275
+ },
54276
+ devDependencies: {
54277
+ "@cloudflare/workers-types": "^4.0.0",
54278
+ typescript: "^5.3.3",
54279
+ wrangler: "^4.0.0"
54280
+ }
54281
+ },
54282
+ null,
54283
+ 2
54284
+ );
54285
+ }
54286
+ function workerTsconfigJson() {
54287
+ return JSON.stringify(
54288
+ {
54289
+ compilerOptions: {
54290
+ target: "ES2022",
54291
+ module: "ES2022",
54292
+ moduleResolution: "bundler",
54293
+ strict: true,
54294
+ resolveJsonModule: true,
54295
+ types: ["@cloudflare/workers-types"]
54296
+ },
54297
+ include: ["src"]
54298
+ },
54299
+ null,
54300
+ 2
54301
+ );
54302
+ }
54303
+ function workerSetupSh(monorepoRoot) {
54304
+ return `#!/usr/bin/env bash
54305
+ # Generated by \`runtype deploy --target cloudflare\`.
54306
+ #
54307
+ # Builds and packs @runtypelabs/runtime from your local monorepo, then
54308
+ # installs all dependencies so you can run \`wrangler dev\` or deploy with
54309
+ # \`wrangler deploy\`.
54310
+ #
54311
+ # Usage (from this directory):
54312
+ # ./setup.sh
54313
+
54314
+ set -euo pipefail
54315
+
54316
+ DEPLOY_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
54317
+ MONOREPO_ROOT=${bashSingleQuote(monorepoRoot)}
54318
+ TARBALL_DIR="\${DEPLOY_DIR}/packages"
54319
+
54320
+ echo "[setup] building @runtypelabs/runtime..."
54321
+ pnpm --dir "\${MONOREPO_ROOT}" --filter @runtypelabs/runtime build
54322
+
54323
+ mkdir -p "\${TARBALL_DIR}"
54324
+
54325
+ echo "[setup] packing @runtypelabs/runtime tarball..."
54326
+ pnpm --dir "\${MONOREPO_ROOT}/packages/runtime" pack \\
54327
+ --pack-destination "\${TARBALL_DIR}"
54328
+
54329
+ RUNTIME_TGZ="$(ls "\${TARBALL_DIR}"/runtypelabs-runtime-*.tgz | head -n 1)"
54330
+ if [ -z "\${RUNTIME_TGZ}" ]; then
54331
+ echo "[setup] error: no tarball produced by pnpm pack" >&2
54332
+ exit 1
54333
+ fi
54334
+ RUNTIME_TGZ_NAME="$(basename "\${RUNTIME_TGZ}")"
54335
+
54336
+ echo "[setup] updating package.json with file: dependency..."
54337
+ node -e "
54338
+ const fs = require('fs');
54339
+ const pkg = JSON.parse(fs.readFileSync('\${DEPLOY_DIR}/package.json', 'utf8'));
54340
+ pkg.dependencies['@runtypelabs/runtime'] = 'file:./packages/\${RUNTIME_TGZ_NAME}';
54341
+ fs.writeFileSync('\${DEPLOY_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\\\\n');
54342
+ "
54343
+
54344
+ echo "[setup] installing dependencies..."
54345
+ npm install --no-audit --no-fund
54346
+
54347
+ echo ""
54348
+ echo "[setup] Done! Next steps:"
54349
+ echo " wrangler dev \u2014 preview locally"
54350
+ echo " wrangler deploy \u2014 deploy to Cloudflare Workers"
54351
+ `;
54352
+ }
54353
+ function workerReadme(agentNames, secretNames) {
54354
+ const agentList = agentNames.map((n) => ` - ${n}`).join("\n");
54355
+ const secretList = secretNames.length > 0 ? secretNames.map((s) => ` wrangler secret put ${s}`).join("\n") : " (none detected)";
54356
+ return `# Runtype Cloudflare Workers Deployment
54357
+
54358
+ Generated by \`runtype deploy --target cloudflare\`. Contains the following agents:
54359
+
54360
+ ${agentList}
54361
+
54362
+ ## Setup
54363
+
54364
+ \`\`\`bash
54365
+ chmod +x setup.sh && ./setup.sh # build runtime + install deps
54366
+ \`\`\`
54367
+
54368
+ ## Secrets
54369
+
54370
+ Set each API key your agents use:
54371
+
54372
+ \`\`\`bash
54373
+ ${secretList}
54374
+ \`\`\`
54375
+
54376
+ ## Run locally
54377
+
54378
+ \`\`\`bash
54379
+ wrangler dev
54380
+ \`\`\`
54381
+
54382
+ ## Deploy
54383
+
54384
+ \`\`\`bash
54385
+ wrangler deploy
54386
+ \`\`\`
54387
+
54388
+ ## Health check
54389
+
54390
+ \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
54391
+
54392
+ ## Call an agent
54393
+
54394
+ \`\`\`bash
54395
+ curl https://<worker-subdomain>.workers.dev/agent/<name> \\
54396
+ -H 'Content-Type: application/json' \\
54397
+ -d '{"messages":[{"role":"user","content":"Hello"}]}'
54398
+ \`\`\`
54399
+ `;
54400
+ }
54401
+ function vercelRouteTs(agentSlugs) {
54402
+ const imports = agentSlugs.map((slug, i) => `import agent${i}Json from '../agents/${slug}.json'`).join("\n");
54403
+ const agentFiles = agentSlugs.map((slug, i) => ` ['${slug}.json', agent${i}Json],`).join("\n");
54404
+ return `/**
54405
+ * Generated by \`runtype deploy --target vercel\`.
54406
+ *
54407
+ * Catch-all Vercel Function. All traffic is rewritten here by vercel.json.
54408
+ * Routes:
54409
+ * GET /health \u2014 liveness probe (lists registered agent names)
54410
+ * POST /agent/:name \u2014 SSE stream of the agent event schema
54411
+ *
54412
+ * Agent definitions are statically imported and bundled by Vercel.
54413
+ * To add a new agent:
54414
+ * 1. Export it with \`runtype deploy --agent <id> --target vercel\`
54415
+ * 2. Or drop a JSON file under agents/ and add an import + entry below
54416
+ * 3. Run \`vercel deploy\`
54417
+ */
54418
+ import { Hono } from 'hono'
54419
+ import { handle } from 'hono/vercel'
54420
+ import { cors } from 'hono/cors'
54421
+ import {
54422
+ VercelAdapter,
54423
+ createRuntime,
54424
+ exportedAgentSchema,
54425
+ type ExportedAgent,
54426
+ } from '@runtypelabs/runtime'
54427
+
54428
+ // ---------------------------------------------------------------------------
54429
+ // Agent registry \u2014 built at module load, cached across warm invocations
54430
+ // ---------------------------------------------------------------------------
54431
+
54432
+ ${imports}
54433
+
54434
+ function parseAgent(json: unknown, filename: string): ExportedAgent {
54435
+ try {
54436
+ return exportedAgentSchema.parse(json)
54437
+ } catch (err) {
54438
+ throw new Error(
54439
+ \`[runtype] invalid agent JSON in \${filename}: \${
54440
+ err instanceof Error ? err.message : String(err)
54441
+ }\`
54442
+ )
54443
+ }
54444
+ }
54445
+
54446
+ const agentFiles: Array<[string, unknown]> = [
54447
+ ${agentFiles}
54448
+ ]
54449
+
54450
+ const agentRegistry: Record<string, ExportedAgent> = {}
54451
+ for (const [filename, json] of agentFiles) {
54452
+ const agent = parseAgent(json, filename)
54453
+ if (agentRegistry[agent.name]) {
54454
+ throw new Error(\`[runtype] duplicate agent name "\${agent.name}" in \${filename}\`)
54455
+ }
54456
+ agentRegistry[agent.name] = agent
54457
+ }
54458
+
54459
+ // ---------------------------------------------------------------------------
54460
+ // Runtime \u2014 constructed at module scope (warm across invocations)
54461
+ // ---------------------------------------------------------------------------
54462
+
54463
+ const runtime = createRuntime({
54464
+ agents: agentRegistry,
54465
+ keys: { mode: 'own' },
54466
+ adapter: new VercelAdapter(),
54467
+ })
54468
+
54469
+ // ---------------------------------------------------------------------------
54470
+ // Hono app
54471
+ // ---------------------------------------------------------------------------
54472
+
54473
+ const app = new Hono()
54474
+
54475
+ const allowedOriginsEnv = process.env.ALLOWED_ORIGINS?.trim()
54476
+ const allowedOrigins = allowedOriginsEnv
54477
+ ? allowedOriginsEnv.split(',').map((o) => o.trim()).filter(Boolean)
54478
+ : '*'
54479
+
54480
+ app.use(
54481
+ '*',
54482
+ cors({
54483
+ origin: allowedOrigins,
54484
+ allowMethods: ['GET', 'POST', 'OPTIONS'],
54485
+ allowHeaders: ['Content-Type', 'Authorization'],
54486
+ maxAge: 86400,
54487
+ })
54488
+ )
54489
+
54490
+ app.get('/health', (c) => c.json({ status: 'ok', agents: Object.keys(agentRegistry) }))
54491
+
54492
+ app.post('/agent/:name', async (c) => {
54493
+ const name = c.req.param('name')
54494
+ if (!runtime.getAgent(name)) {
54495
+ return c.json({ error: \`unknown agent: \${name}\` }, 404)
54496
+ }
54497
+
54498
+ let body: { messages?: Array<{ role: string; content: string }> } = {}
54499
+ try {
54500
+ body = (await c.req.json()) as typeof body
54501
+ } catch {
54502
+ return c.json({ error: 'request body must be JSON' }, 400)
54503
+ }
54504
+ const messages = Array.isArray(body.messages) ? body.messages : []
54505
+
54506
+ try {
54507
+ const response = await runtime.executeAgent(name, {
54508
+ messages: messages as Array<{
54509
+ role: 'system' | 'user' | 'assistant'
54510
+ content: string
54511
+ }>,
54512
+ signal: c.req.raw.signal,
54513
+ })
54514
+ return response
54515
+ } catch (err) {
54516
+ return c.json({ error: err instanceof Error ? err.message : 'unknown error' }, 500)
54517
+ }
54518
+ })
54519
+
54520
+ export const GET = handle(app)
54521
+ export const POST = handle(app)
54522
+ `;
54523
+ }
54524
+ function vercelJson() {
54525
+ return JSON.stringify(
54526
+ {
54527
+ rewrites: [{ source: "/(.*)", destination: "/api/[[...route]]" }],
54528
+ functions: {
54529
+ "api/[[...route]].ts": { maxDuration: 60 }
54530
+ }
54531
+ },
54532
+ null,
54533
+ 2
54534
+ );
54535
+ }
54536
+ function vercelPackageJson(name, runtimeDep) {
54537
+ return JSON.stringify(
54538
+ {
54539
+ name,
54540
+ version: "0.0.0",
54541
+ private: true,
54542
+ scripts: {
54543
+ dev: "vercel dev",
54544
+ deploy: "vercel deploy",
54545
+ typecheck: "tsc --noEmit"
54546
+ },
54547
+ dependencies: {
54548
+ "@runtypelabs/runtime": runtimeDep,
54549
+ hono: "^4.12.16"
54550
+ },
54551
+ devDependencies: {
54552
+ "@types/node": "^22.0.0",
54553
+ "@vercel/node": "^5.0.0",
54554
+ typescript: "^5.3.3",
54555
+ vercel: "^44.0.0"
54556
+ }
54557
+ },
54558
+ null,
54559
+ 2
54560
+ );
54561
+ }
54562
+ function vercelTsconfigJson() {
54563
+ return JSON.stringify(
54564
+ {
54565
+ compilerOptions: {
54566
+ target: "ES2022",
54567
+ module: "ESNext",
54568
+ moduleResolution: "bundler",
54569
+ strict: true,
54570
+ resolveJsonModule: true,
54571
+ esModuleInterop: true,
54572
+ skipLibCheck: true
54573
+ },
54574
+ include: ["api"]
54575
+ },
54576
+ null,
54577
+ 2
54578
+ );
54579
+ }
54580
+ function vercelSetupSh(monorepoRoot) {
54581
+ return `#!/usr/bin/env bash
54582
+ # Generated by \`runtype deploy --target vercel\`.
54583
+ #
54584
+ # Builds and packs @runtypelabs/runtime from your local monorepo, then
54585
+ # installs all dependencies so you can run \`vercel dev\` or deploy with
54586
+ # \`vercel deploy\`.
54587
+ #
54588
+ # Usage (from this directory):
54589
+ # ./setup.sh
54590
+
54591
+ set -euo pipefail
54592
+
54593
+ DEPLOY_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
54594
+ MONOREPO_ROOT=${bashSingleQuote(monorepoRoot)}
54595
+ TARBALL_DIR="\${DEPLOY_DIR}/packages"
54596
+
54597
+ echo "[setup] building @runtypelabs/runtime..."
54598
+ pnpm --dir "\${MONOREPO_ROOT}" --filter @runtypelabs/runtime build
54599
+
54600
+ mkdir -p "\${TARBALL_DIR}"
54601
+
54602
+ echo "[setup] packing @runtypelabs/runtime tarball..."
54603
+ pnpm --dir "\${MONOREPO_ROOT}/packages/runtime" pack \\
54604
+ --pack-destination "\${TARBALL_DIR}"
54605
+
54606
+ RUNTIME_TGZ="$(ls "\${TARBALL_DIR}"/runtypelabs-runtime-*.tgz | head -n 1)"
54607
+ if [ -z "\${RUNTIME_TGZ}" ]; then
54608
+ echo "[setup] error: no tarball produced by pnpm pack" >&2
54609
+ exit 1
54610
+ fi
54611
+ RUNTIME_TGZ_NAME="$(basename "\${RUNTIME_TGZ}")"
54612
+
54613
+ echo "[setup] updating package.json with file: dependency..."
54614
+ node -e "
54615
+ const fs = require('fs');
54616
+ const pkg = JSON.parse(fs.readFileSync('\${DEPLOY_DIR}/package.json', 'utf8'));
54617
+ pkg.dependencies['@runtypelabs/runtime'] = 'file:./packages/\${RUNTIME_TGZ_NAME}';
54618
+ fs.writeFileSync('\${DEPLOY_DIR}/package.json', JSON.stringify(pkg, null, 2) + '\\\\n');
54619
+ "
54620
+
54621
+ echo "[setup] installing dependencies..."
54622
+ npm install --no-audit --no-fund
54623
+
54624
+ echo ""
54625
+ echo "[setup] Done! Next steps:"
54626
+ echo " vercel dev \u2014 preview locally"
54627
+ echo " vercel deploy \u2014 deploy to Vercel Functions"
54628
+ `;
54629
+ }
54630
+ function vercelReadme(agentNames, secretNames) {
54631
+ const agentList = agentNames.map((n) => ` - ${n}`).join("\n");
54632
+ const secretList = secretNames.length > 0 ? secretNames.map((s) => ` vercel env add ${s}`).join("\n") : " (none detected)";
54633
+ return `# Runtype Vercel Functions Deployment
54634
+
54635
+ Generated by \`runtype deploy --target vercel\`. Contains the following agents:
54636
+
54637
+ ${agentList}
54638
+
54639
+ ## Setup
54640
+
54641
+ \`\`\`bash
54642
+ chmod +x setup.sh && ./setup.sh # build runtime + install deps
54643
+ \`\`\`
54644
+
54645
+ ## Secrets
54646
+
54647
+ Set each API key your agents use via the Vercel dashboard or CLI:
54648
+
54649
+ \`\`\`bash
54650
+ ${secretList}
54651
+ \`\`\`
54652
+
54653
+ ## Run locally
54654
+
54655
+ \`\`\`bash
54656
+ vercel dev
54657
+ \`\`\`
54658
+
54659
+ ## Deploy
54660
+
54661
+ \`\`\`bash
54662
+ vercel deploy
54663
+ \`\`\`
54664
+
54665
+ ## Health check
54666
+
54667
+ \`GET /health\` returns \`{"status":"ok","agents":[...]}\`.
54668
+
54669
+ ## Call an agent
54670
+
54671
+ \`\`\`bash
54672
+ curl https://<your-project>.vercel.app/agent/<name> \\
54673
+ -H 'Content-Type: application/json' \\
54674
+ -d '{"messages":[{"role":"user","content":"Hello"}]}'
54675
+ \`\`\`
54676
+ `;
54677
+ }
53973
54678
  function collectSecretNames(agentDef) {
53974
54679
  const names = /* @__PURE__ */ new Set();
53975
54680
  const model = agentDef.config?.model?.toLowerCase();
@@ -53992,13 +54697,13 @@ function collectSecretNames(agentDef) {
53992
54697
  function slugify2(s) {
53993
54698
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
53994
54699
  }
53995
- var deployCommand = new Command24("deploy").description("Export an agent or flow and scaffold a Cloud Run deployment").option("--agent <id>", "Agent ID to export (may be repeated)", (v, acc) => {
54700
+ var deployCommand = new Command24("deploy").description("Export an agent or flow and scaffold a deployment (cloud-run, cloudflare, or vercel)").option("--agent <id>", "Agent ID to export (may be repeated)", (v, acc) => {
53996
54701
  acc.push(v);
53997
54702
  return acc;
53998
54703
  }, []).option("--flow <id>", "Flow ID to export (may be repeated)", (v, acc) => {
53999
54704
  acc.push(v);
54000
54705
  return acc;
54001
- }, []).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)").action(
54706
+ }, []).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").action(
54002
54707
  async (options) => {
54003
54708
  const agentIds = options.agent;
54004
54709
  const flowIds = options.flow;
@@ -54006,13 +54711,18 @@ var deployCommand = new Command24("deploy").description("Export an agent or flow
54006
54711
  console.error(chalk30.red("Error: provide at least one --agent <id> or --flow <id>"));
54007
54712
  process.exit(1);
54008
54713
  }
54714
+ const target = options.target;
54715
+ if (target !== "cloud-run" && target !== "cloudflare" && target !== "vercel") {
54716
+ console.error(chalk30.red(`Error: unknown --target "${target}". Must be one of: cloud-run, cloudflare, vercel`));
54717
+ process.exit(1);
54718
+ }
54009
54719
  const apiKey = await ensureAuth();
54010
54720
  if (!apiKey) return;
54011
54721
  const client = new ApiClient(apiKey);
54012
54722
  const outDir = path14.resolve(options.output);
54013
54723
  const projectName = options.name ?? slugify2(path14.basename(outDir));
54014
54724
  console.log(chalk30.cyan(`
54015
- Scaffolding deployment to ${outDir}
54725
+ Scaffolding ${target} deployment to ${outDir}
54016
54726
  `));
54017
54727
  const agentDefs = [];
54018
54728
  for (const id of agentIds) {
@@ -54063,36 +54773,26 @@ Scaffolding deployment to ${outDir}
54063
54773
  process.exit(1);
54064
54774
  }
54065
54775
  }
54776
+ const agentSlugs = agentDefs.map(({ name }) => slugify2(name));
54777
+ const slugSet = /* @__PURE__ */ new Set();
54778
+ for (let i = 0; i < agentSlugs.length; i++) {
54779
+ const slug = agentSlugs[i];
54780
+ if (slugSet.has(slug)) {
54781
+ const colliders = agentDefs.filter(({ name }) => slugify2(name) === slug).map(({ name }) => `"${name}"`).join(" and ");
54782
+ console.error(chalk30.red(`Error: agents ${colliders} produce the same slug "${slug}".`));
54783
+ console.error(chalk30.yellow(" Rename one of them in the Runtype dashboard to make slugs unique."));
54784
+ process.exit(1);
54785
+ }
54786
+ slugSet.add(slug);
54787
+ }
54066
54788
  fs14.mkdirSync(path14.join(outDir, "agents"), { recursive: true });
54067
54789
  fs14.mkdirSync(path14.join(outDir, "packages"), { recursive: true });
54068
54790
  for (const { name, def } of agentDefs) {
54069
54791
  const filename = `${slugify2(name)}.json`;
54070
- fs14.writeFileSync(
54071
- path14.join(outDir, "agents", filename),
54072
- JSON.stringify(def, null, 2)
54073
- );
54792
+ fs14.writeFileSync(path14.join(outDir, "agents", filename), JSON.stringify(def, null, 2));
54074
54793
  console.log(` Wrote agents/${filename}`);
54075
54794
  }
54076
- fs14.writeFileSync(
54077
- path14.join(outDir, "server.ts"),
54078
- serverTs()
54079
- );
54080
- fs14.writeFileSync(
54081
- path14.join(outDir, "Dockerfile"),
54082
- dockerfile()
54083
- );
54084
- fs14.writeFileSync(
54085
- path14.join(outDir, ".dockerignore"),
54086
- dockerignore()
54087
- );
54088
- fs14.writeFileSync(
54089
- path14.join(outDir, "tsconfig.json"),
54090
- tsconfigJson()
54091
- );
54092
- fs14.writeFileSync(
54093
- path14.join(outDir, "package.json"),
54094
- packageJson(projectName, "workspace:*")
54095
- );
54795
+ const agentNames = agentDefs.map((a) => a.name);
54096
54796
  let monorepoRoot = process.cwd();
54097
54797
  for (let i = 0; i < 8; i++) {
54098
54798
  if (fs14.existsSync(path14.join(monorepoRoot, "pnpm-workspace.yaml"))) break;
@@ -54100,46 +54800,90 @@ Scaffolding deployment to ${outDir}
54100
54800
  if (parent === monorepoRoot) break;
54101
54801
  monorepoRoot = parent;
54102
54802
  }
54103
- const setupScript = setupSh(monorepoRoot);
54104
- const setupPath = path14.join(outDir, "setup.sh");
54105
- fs14.writeFileSync(setupPath, setupScript);
54106
- fs14.chmodSync(setupPath, 493);
54107
54803
  const allSecrets = /* @__PURE__ */ new Set();
54108
54804
  for (const { def } of agentDefs) {
54109
54805
  for (const s of collectSecretNames(def)) allSecrets.add(s);
54110
54806
  }
54111
54807
  const secretNames = Array.from(allSecrets);
54112
- fs14.writeFileSync(
54113
- path14.join(outDir, "README.md"),
54114
- readme(
54115
- agentDefs.map((a) => a.name),
54116
- secretNames
54117
- )
54118
- );
54808
+ if (target === "cloudflare") {
54809
+ fs14.mkdirSync(path14.join(outDir, "src"), { recursive: true });
54810
+ fs14.writeFileSync(path14.join(outDir, "src", "index.ts"), workerIndexTs(agentSlugs));
54811
+ fs14.writeFileSync(path14.join(outDir, "wrangler.toml"), wranglerToml(projectName));
54812
+ fs14.writeFileSync(path14.join(outDir, "package.json"), workerPackageJson(projectName, "workspace:*"));
54813
+ fs14.writeFileSync(path14.join(outDir, "tsconfig.json"), workerTsconfigJson());
54814
+ const setupPath = path14.join(outDir, "setup.sh");
54815
+ fs14.writeFileSync(setupPath, workerSetupSh(monorepoRoot));
54816
+ fs14.chmodSync(setupPath, 493);
54817
+ fs14.writeFileSync(path14.join(outDir, "README.md"), workerReadme(agentNames, secretNames));
54818
+ } else if (target === "vercel") {
54819
+ fs14.mkdirSync(path14.join(outDir, "api"), { recursive: true });
54820
+ fs14.writeFileSync(path14.join(outDir, "api", "[[...route]].ts"), vercelRouteTs(agentSlugs));
54821
+ fs14.writeFileSync(path14.join(outDir, "vercel.json"), vercelJson());
54822
+ fs14.writeFileSync(path14.join(outDir, "package.json"), vercelPackageJson(projectName, "workspace:*"));
54823
+ fs14.writeFileSync(path14.join(outDir, "tsconfig.json"), vercelTsconfigJson());
54824
+ const setupPath = path14.join(outDir, "setup.sh");
54825
+ fs14.writeFileSync(setupPath, vercelSetupSh(monorepoRoot));
54826
+ fs14.chmodSync(setupPath, 493);
54827
+ fs14.writeFileSync(path14.join(outDir, "README.md"), vercelReadme(agentNames, secretNames));
54828
+ } else {
54829
+ fs14.writeFileSync(path14.join(outDir, "server.ts"), serverTs());
54830
+ fs14.writeFileSync(path14.join(outDir, "Dockerfile"), dockerfile());
54831
+ fs14.writeFileSync(path14.join(outDir, ".dockerignore"), dockerignore());
54832
+ fs14.writeFileSync(path14.join(outDir, "tsconfig.json"), tsconfigJson());
54833
+ fs14.writeFileSync(path14.join(outDir, "package.json"), packageJson(projectName, "workspace:*"));
54834
+ const setupPath = path14.join(outDir, "setup.sh");
54835
+ fs14.writeFileSync(setupPath, setupSh(monorepoRoot));
54836
+ fs14.chmodSync(setupPath, 493);
54837
+ fs14.writeFileSync(path14.join(outDir, "README.md"), readme(agentNames, secretNames));
54838
+ }
54119
54839
  console.log("");
54120
54840
  console.log(chalk30.green(`\u2713 Scaffold written to ${outDir}`));
54121
54841
  console.log("");
54122
54842
  console.log(chalk30.bold("Next steps:"));
54123
54843
  console.log(` cd ${options.output}`);
54124
54844
  console.log(" chmod +x setup.sh && ./setup.sh # pack runtime + install deps");
54125
- console.log(" npm run dev # test locally");
54126
- console.log("");
54127
- if (secretNames.length > 0) {
54128
- console.log(chalk30.yellow("Required secrets (set before deploying):"));
54129
- for (const s of secretNames) {
54130
- console.log(` ${chalk30.cyan(s)}`);
54845
+ if (target === "cloudflare") {
54846
+ console.log(" wrangler dev # test locally");
54847
+ console.log("");
54848
+ if (secretNames.length > 0) {
54849
+ console.log(chalk30.yellow("Required secrets (set via wrangler):"));
54850
+ for (const s of secretNames) {
54851
+ console.log(` wrangler secret put ${chalk30.cyan(s)}`);
54852
+ }
54853
+ console.log("");
54131
54854
  }
54855
+ console.log("Deploy to Cloudflare Workers:");
54856
+ console.log(" wrangler deploy");
54857
+ } else if (target === "vercel") {
54858
+ console.log(" vercel dev # test locally");
54132
54859
  console.log("");
54133
- console.log(" Set via Cloud Run: gcloud run deploy \u2026 --set-secrets KEY=NAME:latest");
54134
- console.log(" Or export locally: export KEY=<value>");
54860
+ if (secretNames.length > 0) {
54861
+ console.log(chalk30.yellow("Required secrets (set via Vercel):"));
54862
+ for (const s of secretNames) {
54863
+ console.log(` vercel env add ${chalk30.cyan(s)}`);
54864
+ }
54865
+ console.log("");
54866
+ }
54867
+ console.log("Deploy to Vercel Functions:");
54868
+ console.log(" vercel deploy");
54869
+ } else {
54870
+ console.log(" npm run dev # test locally");
54135
54871
  console.log("");
54872
+ if (secretNames.length > 0) {
54873
+ console.log(chalk30.yellow("Required secrets (set before deploying):"));
54874
+ for (const s of secretNames) {
54875
+ console.log(` ${chalk30.cyan(s)}`);
54876
+ }
54877
+ console.log("");
54878
+ console.log(" Set via Cloud Run: gcloud run deploy \u2026 --set-secrets KEY=NAME:latest");
54879
+ console.log(" Or export locally: export KEY=<value>");
54880
+ console.log("");
54881
+ }
54882
+ console.log("Deploy to Cloud Run:");
54883
+ console.log(` gcloud run deploy ${projectName} --source ${options.output} \\`);
54884
+ console.log(" --region us-central1 --platform managed \\");
54885
+ console.log(" --allow-unauthenticated --port 8080");
54136
54886
  }
54137
- console.log("Deploy to Cloud Run:");
54138
- console.log(
54139
- ` gcloud run deploy ${projectName} --source ${options.output} \\`
54140
- );
54141
- console.log(" --region us-central1 --platform managed \\");
54142
- console.log(" --allow-unauthenticated --port 8080");
54143
54887
  }
54144
54888
  );
54145
54889
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/cli",
3
- "version": "2.14.0",
3
+ "version": "2.15.1",
4
4
  "description": "Command-line interface for Runtype AI platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "micromatch": "^4.0.8",
22
22
  "yaml": "^2.8.3",
23
23
  "@runtypelabs/ink-components": "0.3.1",
24
- "@runtypelabs/sdk": "1.23.0",
24
+ "@runtypelabs/sdk": "1.24.1",
25
25
  "@runtypelabs/terminal-animations": "0.2.0"
26
26
  },
27
27
  "devDependencies": {