@agentstep/agent-sdk 0.1.0

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 (105) hide show
  1. package/package.json +45 -0
  2. package/src/auth/middleware.ts +38 -0
  3. package/src/backends/claude/args.ts +88 -0
  4. package/src/backends/claude/index.ts +193 -0
  5. package/src/backends/claude/permission-hook.ts +152 -0
  6. package/src/backends/claude/tool-bridge.ts +211 -0
  7. package/src/backends/claude/translator.ts +209 -0
  8. package/src/backends/claude/wrapper-script.ts +45 -0
  9. package/src/backends/codex/args.ts +69 -0
  10. package/src/backends/codex/auth.ts +35 -0
  11. package/src/backends/codex/index.ts +57 -0
  12. package/src/backends/codex/setup.ts +37 -0
  13. package/src/backends/codex/translator.ts +223 -0
  14. package/src/backends/codex/wrapper-script.ts +26 -0
  15. package/src/backends/factory/args.ts +45 -0
  16. package/src/backends/factory/auth.ts +30 -0
  17. package/src/backends/factory/index.ts +56 -0
  18. package/src/backends/factory/setup.ts +34 -0
  19. package/src/backends/factory/translator.ts +139 -0
  20. package/src/backends/factory/wrapper-script.ts +33 -0
  21. package/src/backends/gemini/args.ts +44 -0
  22. package/src/backends/gemini/auth.ts +30 -0
  23. package/src/backends/gemini/index.ts +53 -0
  24. package/src/backends/gemini/setup.ts +34 -0
  25. package/src/backends/gemini/translator.ts +139 -0
  26. package/src/backends/gemini/wrapper-script.ts +26 -0
  27. package/src/backends/opencode/args.ts +53 -0
  28. package/src/backends/opencode/auth.ts +53 -0
  29. package/src/backends/opencode/index.ts +70 -0
  30. package/src/backends/opencode/mcp.ts +67 -0
  31. package/src/backends/opencode/setup.ts +54 -0
  32. package/src/backends/opencode/translator.ts +168 -0
  33. package/src/backends/opencode/wrapper-script.ts +46 -0
  34. package/src/backends/registry.ts +38 -0
  35. package/src/backends/shared/ndjson.ts +29 -0
  36. package/src/backends/shared/translator-types.ts +69 -0
  37. package/src/backends/shared/wrap-prompt.ts +17 -0
  38. package/src/backends/types.ts +85 -0
  39. package/src/config/index.ts +95 -0
  40. package/src/db/agents.ts +185 -0
  41. package/src/db/api_keys.ts +78 -0
  42. package/src/db/batch.ts +142 -0
  43. package/src/db/client.ts +81 -0
  44. package/src/db/environments.ts +127 -0
  45. package/src/db/events.ts +208 -0
  46. package/src/db/memory.ts +143 -0
  47. package/src/db/migrations.ts +295 -0
  48. package/src/db/proxy.ts +37 -0
  49. package/src/db/sessions.ts +295 -0
  50. package/src/db/vaults.ts +110 -0
  51. package/src/errors.ts +53 -0
  52. package/src/handlers/agents.ts +194 -0
  53. package/src/handlers/batch.ts +41 -0
  54. package/src/handlers/docs.ts +87 -0
  55. package/src/handlers/environments.ts +154 -0
  56. package/src/handlers/events.ts +234 -0
  57. package/src/handlers/index.ts +12 -0
  58. package/src/handlers/memory.ts +141 -0
  59. package/src/handlers/openapi.ts +14 -0
  60. package/src/handlers/sessions.ts +223 -0
  61. package/src/handlers/stream.ts +76 -0
  62. package/src/handlers/threads.ts +26 -0
  63. package/src/handlers/ui/app.js +984 -0
  64. package/src/handlers/ui/index.html +112 -0
  65. package/src/handlers/ui/style.css +164 -0
  66. package/src/handlers/ui.ts +1281 -0
  67. package/src/handlers/vaults.ts +99 -0
  68. package/src/http.ts +35 -0
  69. package/src/index.ts +104 -0
  70. package/src/init.ts +227 -0
  71. package/src/openapi/registry.ts +8 -0
  72. package/src/openapi/schemas.ts +625 -0
  73. package/src/openapi/spec.ts +691 -0
  74. package/src/providers/apple.ts +220 -0
  75. package/src/providers/daytona.ts +217 -0
  76. package/src/providers/docker.ts +264 -0
  77. package/src/providers/e2b.ts +203 -0
  78. package/src/providers/fly.ts +276 -0
  79. package/src/providers/modal.ts +222 -0
  80. package/src/providers/podman.ts +206 -0
  81. package/src/providers/registry.ts +28 -0
  82. package/src/providers/shared.ts +11 -0
  83. package/src/providers/sprites.ts +55 -0
  84. package/src/providers/types.ts +73 -0
  85. package/src/providers/vercel.ts +208 -0
  86. package/src/proxy/forward.ts +111 -0
  87. package/src/queue/index.ts +111 -0
  88. package/src/sessions/actor.ts +53 -0
  89. package/src/sessions/bus.ts +155 -0
  90. package/src/sessions/driver.ts +818 -0
  91. package/src/sessions/grader.ts +120 -0
  92. package/src/sessions/interrupt.ts +14 -0
  93. package/src/sessions/sweeper.ts +136 -0
  94. package/src/sessions/threads.ts +126 -0
  95. package/src/sessions/tools.ts +50 -0
  96. package/src/shutdown.ts +78 -0
  97. package/src/sprite/client.ts +294 -0
  98. package/src/sprite/exec.ts +161 -0
  99. package/src/sprite/lifecycle.ts +339 -0
  100. package/src/sprite/pool.ts +65 -0
  101. package/src/sprite/setup.ts +159 -0
  102. package/src/state.ts +61 -0
  103. package/src/types.ts +339 -0
  104. package/src/util/clock.ts +7 -0
  105. package/src/util/ids.ts +11 -0
@@ -0,0 +1,625 @@
1
+ /**
2
+ * Zod schemas for the Managed Agents API surface.
3
+ *
4
+ * These are the source of truth for the OpenAPI document generated at
5
+ * `GET /v1/openapi.json`. Mirror `lib/types.ts` resource shapes exactly and
6
+ * mirror the inline request-body schemas currently defined in each route
7
+ * handler.
8
+ *
9
+ * All schemas registered via `registry.register(...)` become named `$ref`
10
+ * entries in the OpenAPI `components.schemas` section. Inline object
11
+ * schemas are embedded directly under the operation.
12
+ */
13
+ import { z } from "zod";
14
+ import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
15
+ import { registry } from "./registry";
16
+
17
+ // Augment Zod with .openapi() metadata chainables. Must run before any
18
+ // schema is registered.
19
+ extendZodWithOpenApi(z);
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Primitives
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const UlidId = z.string().openapi({ example: "agent_01J0ABCDE..." });
26
+ const IsoTimestamp = z.string().datetime().openapi({ example: "2026-04-09T11:30:00.000Z" });
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Error envelope
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export const ErrorEnvelopeSchema = registry.register(
33
+ "Error",
34
+ z
35
+ .object({
36
+ type: z.literal("error"),
37
+ error: z.object({
38
+ type: z.enum([
39
+ "invalid_request_error",
40
+ "authentication_error",
41
+ "permission_error",
42
+ "not_found_error",
43
+ "rate_limit_error",
44
+ "server_busy",
45
+ "server_error",
46
+ ]),
47
+ message: z.string(),
48
+ }),
49
+ })
50
+ .openapi({
51
+ description:
52
+ "Error envelope returned from every `/v1/*` endpoint on failure. Status code is reflected by the `error.type` field.",
53
+ }),
54
+ );
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Tool config (agent_toolset / custom)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ const AgentToolsetTool = z
61
+ .object({
62
+ type: z.literal("agent_toolset_20260401"),
63
+ configs: z
64
+ .array(z.object({ name: z.string(), enabled: z.boolean().optional() }))
65
+ .optional(),
66
+ default_config: z.object({ enabled: z.boolean().optional() }).optional(),
67
+ })
68
+ .openapi({
69
+ description:
70
+ "Enables claude's built-in tool surface. Use `configs[]` to toggle individual tools and `default_config.enabled=false` to invert the default (start empty and whitelist).",
71
+ });
72
+
73
+ const CustomTool = z
74
+ .object({
75
+ type: z.literal("custom"),
76
+ name: z.string().min(1),
77
+ description: z.string(),
78
+ input_schema: z.record(z.unknown()),
79
+ })
80
+ .openapi({
81
+ description:
82
+ "Custom tool defined by the client. v1 accepts these in the agent record but does not yet bridge them to claude's tool-use loop — clients that can drive tool_use events directly can round-trip them via the user.custom_tool_result event.",
83
+ });
84
+
85
+ export const ToolConfigSchema = registry.register(
86
+ "ToolConfig",
87
+ z.union([AgentToolsetTool, CustomTool]),
88
+ );
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // MCP server config
92
+ // ---------------------------------------------------------------------------
93
+
94
+ export const McpServerConfigSchema = registry.register(
95
+ "McpServerConfig",
96
+ z.object({
97
+ type: z.enum(["stdio", "http", "sse"]).optional(),
98
+ url: z.string().optional(),
99
+ command: z.union([z.string(), z.array(z.string())]).optional(),
100
+ args: z.array(z.string()).optional(),
101
+ headers: z.record(z.string()).optional(),
102
+ env: z.record(z.string()).optional(),
103
+ }),
104
+ );
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // Agent
108
+ // ---------------------------------------------------------------------------
109
+
110
+ export const AgentSchema = registry.register(
111
+ "Agent",
112
+ z.object({
113
+ id: UlidId,
114
+ version: z.number().int().positive(),
115
+ name: z.string(),
116
+ model: z.string(),
117
+ system: z.string().nullable(),
118
+ tools: z.array(ToolConfigSchema),
119
+ mcp_servers: z.record(McpServerConfigSchema),
120
+ backend: z.enum(["claude", "opencode", "codex", "anthropic", "gemini", "factory"]).openapi({
121
+ description:
122
+ "Which CLI engine powers this agent. `claude` drives `claude -p`; `opencode` drives sst/opencode-ai's `opencode run`; `gemini` drives Google's `gemini -p`; `factory` drives Factory's `droid exec`. Immutable after agent creation.",
123
+ }),
124
+ webhook_url: z.string().nullable().openapi({
125
+ description: "URL to POST webhook notifications to. Best-effort delivery with 5s timeout.",
126
+ }),
127
+ webhook_events: z.array(z.string()).openapi({
128
+ description: "Event types that trigger webhook delivery. Defaults to status + error events.",
129
+ }),
130
+ threads_enabled: z.boolean().openapi({
131
+ description: "Whether this agent can spawn sub-agents via the spawn_agent tool.",
132
+ }),
133
+ confirmation_mode: z.boolean().openapi({
134
+ description: "Whether this agent requires tool confirmation via user.tool_confirmation events. When true, claude runs with --permission-mode default and a PermissionRequest hook bridges tool approvals to the MA API.",
135
+ }),
136
+ created_at: IsoTimestamp,
137
+ updated_at: IsoTimestamp,
138
+ }),
139
+ );
140
+
141
+ export const CreateAgentRequestSchema = registry.register(
142
+ "CreateAgentRequest",
143
+ z
144
+ .object({
145
+ name: z.string().min(1).openapi({ example: "my-agent" }),
146
+ model: z.string().min(1).openapi({ example: "claude-sonnet-4-6" }),
147
+ system: z.string().nullish().openapi({ example: "You are a helpful assistant." }),
148
+ tools: z.array(ToolConfigSchema).optional(),
149
+ mcp_servers: z.record(McpServerConfigSchema).optional(),
150
+ backend: z.enum(["claude", "opencode", "codex", "anthropic", "gemini", "factory"]).optional().openapi({
151
+ description:
152
+ "Backend CLI engine. Defaults to `claude`. Opencode agents must set `model` to a `provider/model` string (e.g. `anthropic/claude-sonnet-4-6`) and must NOT declare `tools` — opencode manages its tool surface internally. Gemini agents require GEMINI_API_KEY. Factory agents require FACTORY_API_KEY.",
153
+ example: "claude",
154
+ }),
155
+ webhook_url: z.string().url().optional().openapi({
156
+ description: "URL to POST webhook notifications to.",
157
+ }),
158
+ webhook_events: z.array(z.string()).optional().openapi({
159
+ description: "Event types to deliver via webhook. Defaults to [\"session.status_idle\",\"session.status_running\",\"session.error\"].",
160
+ }),
161
+ threads_enabled: z.boolean().optional().openapi({
162
+ description: "Enable multi-agent threads. When true, spawn_agent tool is available.",
163
+ }),
164
+ confirmation_mode: z.boolean().optional().openapi({
165
+ description: "Enable tool confirmation mode. When true, claude requires explicit user approval for tool calls via user.tool_confirmation events.",
166
+ }),
167
+ })
168
+ .openapi({
169
+ example: {
170
+ name: "my-agent",
171
+ model: "claude-sonnet-4-6",
172
+ system: "You are a helpful assistant.",
173
+ tools: [{ type: "agent_toolset_20260401" }],
174
+ },
175
+ }),
176
+ );
177
+
178
+ export const UpdateAgentRequestSchema = registry.register(
179
+ "UpdateAgentRequest",
180
+ z.object({
181
+ name: z.string().min(1).optional(),
182
+ model: z.string().min(1).optional(),
183
+ system: z.string().nullish(),
184
+ tools: z.array(ToolConfigSchema).optional(),
185
+ mcp_servers: z.record(McpServerConfigSchema).optional(),
186
+ }),
187
+ );
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Environment
191
+ // ---------------------------------------------------------------------------
192
+
193
+ const EnvironmentPackages = z
194
+ .object({
195
+ apt: z.array(z.string()).optional(),
196
+ cargo: z.array(z.string()).optional(),
197
+ gem: z.array(z.string()).optional(),
198
+ go: z.array(z.string()).optional(),
199
+ npm: z.array(z.string()).optional(),
200
+ pip: z.array(z.string()).optional(),
201
+ })
202
+ .openapi({
203
+ description:
204
+ "Package lists installed into the sprite during environment setup. All are optional.",
205
+ });
206
+
207
+ const EnvironmentNetworking = z.union([
208
+ z.object({ type: z.literal("unrestricted") }),
209
+ z.object({
210
+ type: z.literal("limited"),
211
+ allowed_hosts: z.array(z.string()).optional(),
212
+ allow_mcp_servers: z.boolean().optional(),
213
+ allow_package_managers: z.boolean().optional(),
214
+ }),
215
+ ]);
216
+
217
+ export const EnvironmentConfigSchema = registry.register(
218
+ "EnvironmentConfig",
219
+ z.object({
220
+ type: z.literal("cloud"),
221
+ provider: z.enum(["sprites", "docker", "apple", "podman", "e2b", "vercel", "daytona", "fly", "modal"]).optional().openapi({
222
+ description:
223
+ "Container provider for this environment. `sprites` uses sprites.dev cloud containers (default); `docker` uses local Docker containers; `apple` uses Apple Containers on macOS 26+ (Apple Silicon only); `podman` uses Podman containers; `e2b` uses E2B cloud sandboxes; `vercel` uses Vercel Sandboxes; `daytona` uses Daytona workspaces; `fly` uses Fly.io Machines; `modal` uses Modal sandboxes.",
224
+ }),
225
+ packages: EnvironmentPackages.optional(),
226
+ networking: EnvironmentNetworking.optional(),
227
+ }),
228
+ );
229
+
230
+ export const EnvironmentSchema = registry.register(
231
+ "Environment",
232
+ z.object({
233
+ id: UlidId,
234
+ name: z.string(),
235
+ config: EnvironmentConfigSchema,
236
+ state: z.enum(["preparing", "ready", "failed"]),
237
+ state_message: z.string().nullable(),
238
+ created_at: IsoTimestamp,
239
+ archived_at: IsoTimestamp.nullable(),
240
+ }),
241
+ );
242
+
243
+ export const CreateEnvironmentRequestSchema = registry.register(
244
+ "CreateEnvironmentRequest",
245
+ z
246
+ .object({
247
+ name: z.string().min(1).openapi({ example: "my-env" }),
248
+ config: EnvironmentConfigSchema,
249
+ })
250
+ .openapi({
251
+ example: {
252
+ name: "my-env",
253
+ config: { type: "cloud", networking: { type: "unrestricted" } },
254
+ },
255
+ }),
256
+ );
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // Session
260
+ // ---------------------------------------------------------------------------
261
+
262
+ const SessionStatsSchema = z.object({
263
+ turn_count: z.number().int().nonnegative(),
264
+ tool_calls_count: z.number().int().nonnegative(),
265
+ active_seconds: z.number().nonnegative(),
266
+ duration_seconds: z.number().nonnegative(),
267
+ });
268
+
269
+ const SessionUsageSchema = z.object({
270
+ input_tokens: z.number().int().nonnegative(),
271
+ output_tokens: z.number().int().nonnegative(),
272
+ cache_read_input_tokens: z.number().int().nonnegative(),
273
+ cache_creation_input_tokens: z.number().int().nonnegative(),
274
+ cost_usd: z.number().nonnegative(),
275
+ });
276
+
277
+ export const SessionStatusSchema = registry.register(
278
+ "SessionStatus",
279
+ z.enum(["idle", "running", "rescheduling", "terminated"]),
280
+ );
281
+
282
+ export const SessionSchema = registry.register(
283
+ "Session",
284
+ z.object({
285
+ id: UlidId,
286
+ agent: z.object({ id: UlidId, version: z.number().int().positive() }),
287
+ environment_id: UlidId,
288
+ status: SessionStatusSchema,
289
+ stop_reason: z.string().nullable(),
290
+ title: z.string().nullable(),
291
+ metadata: z.record(z.unknown()),
292
+ max_budget_usd: z.number().nullable().openapi({
293
+ description: "Maximum spend for this session in USD. Turn start is rejected once cumulative usage_cost_usd reaches this cap.",
294
+ }),
295
+ outcome: z.record(z.unknown()).nullable().openapi({
296
+ description: "Outcome criteria set via user.define_outcome event.",
297
+ }),
298
+ resources: z.array(z.object({
299
+ type: z.enum(["uri", "text"]),
300
+ uri: z.string().optional(),
301
+ content: z.string().optional(),
302
+ })).nullable().openapi({
303
+ description: "Resources attached to the session, downloaded into the container at /tmp/resources/.",
304
+ }),
305
+ vault_ids: z.array(z.string()).nullable().openapi({
306
+ description: "Vault IDs mounted into the container at /tmp/vaults/.",
307
+ }),
308
+ parent_session_id: z.string().nullable().openapi({
309
+ description: "Parent session ID if this is a child thread session.",
310
+ }),
311
+ thread_depth: z.number().int().nonnegative().openapi({
312
+ description: "Thread nesting depth. 0 for top-level sessions.",
313
+ }),
314
+ stats: SessionStatsSchema,
315
+ usage: SessionUsageSchema,
316
+ created_at: IsoTimestamp,
317
+ updated_at: IsoTimestamp,
318
+ archived_at: IsoTimestamp.nullable(),
319
+ }),
320
+ );
321
+
322
+ export const CreateSessionRequestSchema = registry.register(
323
+ "CreateSessionRequest",
324
+ z
325
+ .object({
326
+ agent: z.union([
327
+ UlidId,
328
+ z.object({
329
+ id: UlidId,
330
+ version: z.number().int(),
331
+ type: z.literal("agent").optional(),
332
+ }),
333
+ ]),
334
+ environment_id: UlidId,
335
+ title: z.string().nullish(),
336
+ metadata: z.record(z.unknown()).optional(),
337
+ max_budget_usd: z.number().positive().optional().openapi({
338
+ description: "Maximum spend for this session in USD. Once exceeded, turns are rejected with a budget_exceeded error.",
339
+ }),
340
+ resources: z.array(z.object({
341
+ type: z.enum(["uri", "text"]),
342
+ uri: z.string().optional(),
343
+ content: z.string().optional(),
344
+ })).optional(),
345
+ vault_ids: z.array(z.string()).optional(),
346
+ })
347
+ .openapi({
348
+ example: {
349
+ agent: "agent_01ABCDEFG...",
350
+ environment_id: "env_01ABCDEFG...",
351
+ },
352
+ }),
353
+ );
354
+
355
+ export const UpdateSessionRequestSchema = registry.register(
356
+ "UpdateSessionRequest",
357
+ z.object({
358
+ title: z.string().nullish(),
359
+ metadata: z.record(z.unknown()).optional(),
360
+ vault_ids: z.array(z.string()).optional(),
361
+ }),
362
+ );
363
+
364
+ // ---------------------------------------------------------------------------
365
+ // Events
366
+ // ---------------------------------------------------------------------------
367
+
368
+ const TextBlock = z.object({ type: z.literal("text"), text: z.string() });
369
+
370
+ const UserMessageEvent = z
371
+ .object({
372
+ type: z.literal("user.message"),
373
+ content: z.array(TextBlock).min(1),
374
+ })
375
+ .openapi({
376
+ description: "Append a user message to the session. Triggers a new turn.",
377
+ });
378
+
379
+ const UserInterruptEvent = z
380
+ .object({
381
+ type: z.literal("user.interrupt"),
382
+ })
383
+ .openapi({
384
+ description:
385
+ "Interrupt the currently-running turn. No-op if session is idle. Mid-batch follow-up user.message events are executed after the interrupt.",
386
+ });
387
+
388
+ const UserCustomToolResultEvent = z
389
+ .object({
390
+ type: z.literal("user.custom_tool_result"),
391
+ custom_tool_use_id: z.string(),
392
+ content: z.array(z.unknown()),
393
+ })
394
+ .openapi({
395
+ description:
396
+ "Client-provided result for a custom tool call. Only meaningful after an agent.custom_tool_use event with matching id.",
397
+ });
398
+
399
+ const UserToolConfirmationEvent = z
400
+ .object({
401
+ type: z.literal("user.tool_confirmation"),
402
+ tool_use_id: z.string().optional().openapi({
403
+ description: "ID of the tool use to confirm. Optional — if omitted, applies to the current pending confirmation.",
404
+ }),
405
+ result: z.enum(["allow", "deny"]).optional().openapi({
406
+ description: "Whether to allow or deny the tool use. Defaults to 'allow'.",
407
+ }),
408
+ deny_message: z.string().optional().openapi({
409
+ description: "Optional message explaining why the tool use was denied.",
410
+ }),
411
+ })
412
+ .openapi({
413
+ description:
414
+ "Confirm or deny a pending tool use. Only meaningful for agents with confirmation_mode enabled, after an agent.tool_confirmation_request event.",
415
+ });
416
+
417
+ const UserDefineOutcomeEvent = z
418
+ .object({
419
+ type: z.literal("user.define_outcome"),
420
+ description: z.string().min(1).openapi({
421
+ description: "Description of the desired outcome for the agent to achieve.",
422
+ }),
423
+ rubric: z.string().optional().openapi({
424
+ description: "Markdown rubric used by the grader to evaluate the agent's output.",
425
+ }),
426
+ max_iterations: z.number().int().min(1).max(20).optional().openapi({
427
+ description: "Maximum grading iterations before giving up. Defaults to 3.",
428
+ }),
429
+ })
430
+ .openapi({
431
+ description:
432
+ "Define an outcome for the agent to work toward. A grader evaluates the agent's output against the rubric after each turn, cycling until satisfied or max_iterations reached.",
433
+ });
434
+
435
+ export const UserEventSchema = registry.register(
436
+ "UserEvent",
437
+ z.union([UserMessageEvent, UserInterruptEvent, UserCustomToolResultEvent, UserToolConfirmationEvent, UserDefineOutcomeEvent]),
438
+ );
439
+
440
+ export const UserEventBatchRequestSchema = registry.register(
441
+ "UserEventBatchRequest",
442
+ z
443
+ .object({
444
+ events: z.array(UserEventSchema).min(1),
445
+ })
446
+ .openapi({
447
+ example: {
448
+ events: [
449
+ {
450
+ type: "user.message",
451
+ content: [{ type: "text", text: "hello" }],
452
+ },
453
+ ],
454
+ },
455
+ }),
456
+ );
457
+
458
+ export const ManagedEventSchema = registry.register(
459
+ "ManagedEvent",
460
+ z
461
+ .object({
462
+ id: UlidId,
463
+ seq: z.number().int().positive(),
464
+ session_id: UlidId,
465
+ type: z.string(),
466
+ processed_at: IsoTimestamp.nullable(),
467
+ })
468
+ .catchall(z.unknown())
469
+ .openapi({
470
+ description:
471
+ "Envelope for any Managed Agents event. Event-specific fields are mixed into the top level alongside id/seq/type — refer to the managed-agents-2026-04-01 spec for per-type shapes.",
472
+ }),
473
+ );
474
+
475
+ // ---------------------------------------------------------------------------
476
+ // Vault
477
+ // ---------------------------------------------------------------------------
478
+
479
+ export const VaultSchema = registry.register(
480
+ "Vault",
481
+ z.object({
482
+ id: UlidId,
483
+ agent_id: UlidId,
484
+ name: z.string(),
485
+ created_at: IsoTimestamp,
486
+ updated_at: IsoTimestamp,
487
+ }),
488
+ );
489
+
490
+ export const VaultEntrySchema = registry.register(
491
+ "VaultEntry",
492
+ z.object({
493
+ key: z.string(),
494
+ value: z.string(),
495
+ }),
496
+ );
497
+
498
+ export const CreateVaultRequestSchema = registry.register(
499
+ "CreateVaultRequest",
500
+ z.object({
501
+ agent_id: UlidId.openapi({ example: "agent_01ABCDEFG..." }),
502
+ name: z.string().min(1).openapi({ example: "my-vault" }),
503
+ }),
504
+ );
505
+
506
+ export const SetVaultEntryRequestSchema = registry.register(
507
+ "SetVaultEntryRequest",
508
+ z.object({
509
+ value: z.string().openapi({ example: "some value" }),
510
+ }),
511
+ );
512
+
513
+ export const VaultDeletedResponseSchema = registry.register(
514
+ "VaultDeletedResponse",
515
+ z.object({ id: UlidId, type: z.literal("vault_deleted") }),
516
+ );
517
+
518
+ export const VaultEntryDeletedResponseSchema = registry.register(
519
+ "VaultEntryDeletedResponse",
520
+ z.object({ key: z.string(), type: z.literal("entry_deleted") }),
521
+ );
522
+
523
+ // ---------------------------------------------------------------------------
524
+ // Batch
525
+ // ---------------------------------------------------------------------------
526
+
527
+ export const BatchOperationSchema = registry.register(
528
+ "BatchOperation",
529
+ z.object({
530
+ method: z.string().openapi({ example: "POST" }),
531
+ path: z.string().openapi({ example: "/v1/agents" }),
532
+ body: z.record(z.unknown()).optional(),
533
+ }),
534
+ );
535
+
536
+ export const BatchRequestSchema = registry.register(
537
+ "BatchRequest",
538
+ z.object({
539
+ operations: z.array(z.object({
540
+ method: z.string(),
541
+ path: z.string(),
542
+ body: z.record(z.unknown()).optional(),
543
+ })).min(1).max(50),
544
+ }),
545
+ );
546
+
547
+ export const BatchResultSchema = registry.register(
548
+ "BatchResult",
549
+ z.object({
550
+ status: z.number().int(),
551
+ body: z.unknown(),
552
+ }),
553
+ );
554
+
555
+ export const BatchResponseSchema = registry.register(
556
+ "BatchResponse",
557
+ z.object({
558
+ results: z.array(z.object({
559
+ status: z.number().int(),
560
+ body: z.unknown(),
561
+ })),
562
+ }),
563
+ );
564
+
565
+ // ---------------------------------------------------------------------------
566
+ // List envelopes
567
+ // ---------------------------------------------------------------------------
568
+
569
+ function listEnvelope<T extends z.ZodTypeAny>(
570
+ name: string,
571
+ item: T,
572
+ ): z.ZodObject<{ data: z.ZodArray<T>; next_page: z.ZodNullable<z.ZodString> }> {
573
+ const schema = z.object({
574
+ data: z.array(item),
575
+ next_page: z.string().nullable(),
576
+ });
577
+ return registry.register(name, schema) as unknown as typeof schema;
578
+ }
579
+
580
+ export const AgentListResponseSchema = listEnvelope("AgentListResponse", AgentSchema);
581
+ export const EnvironmentListResponseSchema = listEnvelope(
582
+ "EnvironmentListResponse",
583
+ EnvironmentSchema,
584
+ );
585
+ export const SessionListResponseSchema = listEnvelope("SessionListResponse", SessionSchema);
586
+ export const EventListResponseSchema = listEnvelope("EventListResponse", ManagedEventSchema);
587
+
588
+ // Vault list uses a simpler shape (no next_page cursor).
589
+ export const VaultListResponseSchema = registry.register(
590
+ "VaultListResponse",
591
+ z.object({ data: z.array(VaultSchema) }),
592
+ );
593
+
594
+ export const VaultEntryListResponseSchema = registry.register(
595
+ "VaultEntryListResponse",
596
+ z.object({ data: z.array(VaultEntrySchema) }),
597
+ );
598
+
599
+ // ---------------------------------------------------------------------------
600
+ // Delete responses
601
+ // ---------------------------------------------------------------------------
602
+
603
+ export const AgentDeletedResponseSchema = registry.register(
604
+ "AgentDeletedResponse",
605
+ z.object({ id: UlidId, type: z.literal("agent_deleted") }),
606
+ );
607
+
608
+ export const SessionDeletedResponseSchema = registry.register(
609
+ "SessionDeletedResponse",
610
+ z.object({ id: UlidId, type: z.literal("session_deleted") }),
611
+ );
612
+
613
+ export const EnvironmentDeletedResponseSchema = registry.register(
614
+ "EnvironmentDeletedResponse",
615
+ z.object({ id: UlidId, type: z.literal("environment_deleted") }),
616
+ );
617
+
618
+ // ---------------------------------------------------------------------------
619
+ // Event append response
620
+ // ---------------------------------------------------------------------------
621
+
622
+ export const UserEventAppendResponseSchema = registry.register(
623
+ "UserEventAppendResponse",
624
+ z.object({ events: z.array(ManagedEventSchema) }),
625
+ );