@mongodb-js/agent-engine-runner-shared 0.11.3

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 (220) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/LICENSE.md +201 -0
  3. package/README.md +29 -0
  4. package/dist/agent_config.d.ts +167 -0
  5. package/dist/agent_config.d.ts.map +1 -0
  6. package/dist/agent_config.js +544 -0
  7. package/dist/call_interrupted.d.ts +12 -0
  8. package/dist/call_interrupted.d.ts.map +1 -0
  9. package/dist/call_interrupted.js +11 -0
  10. package/dist/checkpoint_workspace.d.ts +25 -0
  11. package/dist/checkpoint_workspace.d.ts.map +1 -0
  12. package/dist/checkpoint_workspace.js +44 -0
  13. package/dist/context.d.ts +235 -0
  14. package/dist/context.d.ts.map +1 -0
  15. package/dist/context.js +322 -0
  16. package/dist/db_config.d.ts +28 -0
  17. package/dist/db_config.d.ts.map +1 -0
  18. package/dist/db_config.js +66 -0
  19. package/dist/db_naming.d.ts +54 -0
  20. package/dist/db_naming.d.ts.map +1 -0
  21. package/dist/db_naming.js +94 -0
  22. package/dist/error_reporting.d.ts +67 -0
  23. package/dist/error_reporting.d.ts.map +1 -0
  24. package/dist/error_reporting.js +311 -0
  25. package/dist/generated/workflow/v1/activity_pb.d.ts +342 -0
  26. package/dist/generated/workflow/v1/activity_pb.d.ts.map +1 -0
  27. package/dist/generated/workflow/v1/activity_pb.js +115 -0
  28. package/dist/generated/workflow/v1/common_pb.d.ts +184 -0
  29. package/dist/generated/workflow/v1/common_pb.d.ts.map +1 -0
  30. package/dist/generated/workflow/v1/common_pb.js +86 -0
  31. package/dist/generated/workflow/v1/runtime_pb.d.ts +200 -0
  32. package/dist/generated/workflow/v1/runtime_pb.d.ts.map +1 -0
  33. package/dist/generated/workflow/v1/runtime_pb.js +40 -0
  34. package/dist/generated/workflow/v1/state_pb.d.ts +254 -0
  35. package/dist/generated/workflow/v1/state_pb.d.ts.map +1 -0
  36. package/dist/generated/workflow/v1/state_pb.js +68 -0
  37. package/dist/guardrails_evaluator/core.d.ts +23 -0
  38. package/dist/guardrails_evaluator/core.d.ts.map +1 -0
  39. package/dist/guardrails_evaluator/core.js +122 -0
  40. package/dist/guardrails_evaluator/index.d.ts +10 -0
  41. package/dist/guardrails_evaluator/index.d.ts.map +1 -0
  42. package/dist/guardrails_evaluator/index.js +11 -0
  43. package/dist/guardrails_evaluator/regex.d.ts +20 -0
  44. package/dist/guardrails_evaluator/regex.d.ts.map +1 -0
  45. package/dist/guardrails_evaluator/regex.js +233 -0
  46. package/dist/hooks.d.ts +109 -0
  47. package/dist/hooks.d.ts.map +1 -0
  48. package/dist/hooks.js +216 -0
  49. package/dist/http_path.d.ts +18 -0
  50. package/dist/http_path.d.ts.map +1 -0
  51. package/dist/http_path.js +53 -0
  52. package/dist/index.d.ts +35 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +41 -0
  55. package/dist/launcher.d.ts +130 -0
  56. package/dist/launcher.d.ts.map +1 -0
  57. package/dist/launcher.js +325 -0
  58. package/dist/logger.d.ts +96 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +204 -0
  61. package/dist/mcp_oauth.d.ts +51 -0
  62. package/dist/mcp_oauth.d.ts.map +1 -0
  63. package/dist/mcp_oauth.js +389 -0
  64. package/dist/mcp_oauth_secret.d.ts +21 -0
  65. package/dist/mcp_oauth_secret.d.ts.map +1 -0
  66. package/dist/mcp_oauth_secret.js +122 -0
  67. package/dist/mcp_tools.d.ts +71 -0
  68. package/dist/mcp_tools.d.ts.map +1 -0
  69. package/dist/mcp_tools.js +301 -0
  70. package/dist/memory_appbound.d.ts +42 -0
  71. package/dist/memory_appbound.d.ts.map +1 -0
  72. package/dist/memory_appbound.js +159 -0
  73. package/dist/memory_writer.d.ts +49 -0
  74. package/dist/memory_writer.d.ts.map +1 -0
  75. package/dist/memory_writer.js +171 -0
  76. package/dist/metrics.d.ts +84 -0
  77. package/dist/metrics.d.ts.map +1 -0
  78. package/dist/metrics.js +205 -0
  79. package/dist/models.d.ts +1458 -0
  80. package/dist/models.d.ts.map +1 -0
  81. package/dist/models.js +1726 -0
  82. package/dist/node_logger.d.ts +43 -0
  83. package/dist/node_logger.d.ts.map +1 -0
  84. package/dist/node_logger.js +158 -0
  85. package/dist/owner_callback.d.ts +16 -0
  86. package/dist/owner_callback.d.ts.map +1 -0
  87. package/dist/owner_callback.js +40 -0
  88. package/dist/progress.d.ts +57 -0
  89. package/dist/progress.d.ts.map +1 -0
  90. package/dist/progress.js +140 -0
  91. package/dist/runtime.d.ts +131 -0
  92. package/dist/runtime.d.ts.map +1 -0
  93. package/dist/runtime.js +351 -0
  94. package/dist/secure_llm_proxy.d.ts +115 -0
  95. package/dist/secure_llm_proxy.d.ts.map +1 -0
  96. package/dist/secure_llm_proxy.js +922 -0
  97. package/dist/secure_wrapper.d.ts +332 -0
  98. package/dist/secure_wrapper.d.ts.map +1 -0
  99. package/dist/secure_wrapper.js +1249 -0
  100. package/dist/server/aer.d.ts +61 -0
  101. package/dist/server/aer.d.ts.map +1 -0
  102. package/dist/server/aer.js +1124 -0
  103. package/dist/server/auth.d.ts +56 -0
  104. package/dist/server/auth.d.ts.map +1 -0
  105. package/dist/server/auth.js +132 -0
  106. package/dist/server/base.d.ts +104 -0
  107. package/dist/server/base.d.ts.map +1 -0
  108. package/dist/server/base.js +150 -0
  109. package/dist/server/callInterrupt.d.ts +49 -0
  110. package/dist/server/callInterrupt.d.ts.map +1 -0
  111. package/dist/server/callInterrupt.js +68 -0
  112. package/dist/server/callback_delivery.d.ts +14 -0
  113. package/dist/server/callback_delivery.d.ts.map +1 -0
  114. package/dist/server/callback_delivery.js +141 -0
  115. package/dist/server/chunk_types.d.ts +50 -0
  116. package/dist/server/chunk_types.d.ts.map +1 -0
  117. package/dist/server/chunk_types.js +62 -0
  118. package/dist/server/cors.d.ts +52 -0
  119. package/dist/server/cors.d.ts.map +1 -0
  120. package/dist/server/cors.js +107 -0
  121. package/dist/server/drain.d.ts +169 -0
  122. package/dist/server/drain.d.ts.map +1 -0
  123. package/dist/server/drain.js +455 -0
  124. package/dist/server/function.d.ts +77 -0
  125. package/dist/server/function.d.ts.map +1 -0
  126. package/dist/server/function.js +337 -0
  127. package/dist/server/http_retry.d.ts +37 -0
  128. package/dist/server/http_retry.d.ts.map +1 -0
  129. package/dist/server/http_retry.js +157 -0
  130. package/dist/server/index.d.ts +7 -0
  131. package/dist/server/index.d.ts.map +1 -0
  132. package/dist/server/index.js +5 -0
  133. package/dist/server/metadata.d.ts +50 -0
  134. package/dist/server/metadata.d.ts.map +1 -0
  135. package/dist/server/metadata.js +193 -0
  136. package/dist/server/oe_url.d.ts +36 -0
  137. package/dist/server/oe_url.d.ts.map +1 -0
  138. package/dist/server/oe_url.js +50 -0
  139. package/dist/server/owner_url.d.ts +35 -0
  140. package/dist/server/owner_url.d.ts.map +1 -0
  141. package/dist/server/owner_url.js +146 -0
  142. package/dist/server/query.d.ts +42 -0
  143. package/dist/server/query.d.ts.map +1 -0
  144. package/dist/server/query.js +28 -0
  145. package/dist/server/tool.d.ts +138 -0
  146. package/dist/server/tool.d.ts.map +1 -0
  147. package/dist/server/tool.js +1017 -0
  148. package/dist/span_names.d.ts +21 -0
  149. package/dist/span_names.d.ts.map +1 -0
  150. package/dist/span_names.js +31 -0
  151. package/dist/structured_logging/constants.d.ts +17 -0
  152. package/dist/structured_logging/constants.d.ts.map +1 -0
  153. package/dist/structured_logging/constants.js +71 -0
  154. package/dist/structured_logging/env.d.ts +18 -0
  155. package/dist/structured_logging/env.d.ts.map +1 -0
  156. package/dist/structured_logging/env.js +39 -0
  157. package/dist/structured_logging/install.d.ts +56 -0
  158. package/dist/structured_logging/install.d.ts.map +1 -0
  159. package/dist/structured_logging/install.js +107 -0
  160. package/dist/structured_logging/layout.d.ts +9 -0
  161. package/dist/structured_logging/layout.d.ts.map +1 -0
  162. package/dist/structured_logging/layout.js +144 -0
  163. package/dist/structured_logging/serialize.d.ts +27 -0
  164. package/dist/structured_logging/serialize.d.ts.map +1 -0
  165. package/dist/structured_logging/serialize.js +61 -0
  166. package/dist/structured_logging/stdio_capture.d.ts +59 -0
  167. package/dist/structured_logging/stdio_capture.d.ts.map +1 -0
  168. package/dist/structured_logging/stdio_capture.js +164 -0
  169. package/dist/structured_logging/uncaught.d.ts +14 -0
  170. package/dist/structured_logging/uncaught.d.ts.map +1 -0
  171. package/dist/structured_logging/uncaught.js +58 -0
  172. package/dist/structured_logging.d.ts +48 -0
  173. package/dist/structured_logging.d.ts.map +1 -0
  174. package/dist/structured_logging.js +47 -0
  175. package/dist/tls_client.d.ts +61 -0
  176. package/dist/tls_client.d.ts.map +1 -0
  177. package/dist/tls_client.js +298 -0
  178. package/dist/tool_api_error.d.ts +62 -0
  179. package/dist/tool_api_error.d.ts.map +1 -0
  180. package/dist/tool_api_error.js +399 -0
  181. package/dist/tool_memory_ownership.d.ts +10 -0
  182. package/dist/tool_memory_ownership.d.ts.map +1 -0
  183. package/dist/tool_memory_ownership.js +36 -0
  184. package/dist/toolpod_handlers.d.ts +126 -0
  185. package/dist/toolpod_handlers.d.ts.map +1 -0
  186. package/dist/toolpod_handlers.js +1016 -0
  187. package/dist/tracing/exporters.d.ts +51 -0
  188. package/dist/tracing/exporters.d.ts.map +1 -0
  189. package/dist/tracing/exporters.js +327 -0
  190. package/dist/tracing/index.d.ts +3 -0
  191. package/dist/tracing/index.d.ts.map +1 -0
  192. package/dist/tracing/index.js +2 -0
  193. package/dist/tracing/setup.d.ts +76 -0
  194. package/dist/tracing/setup.d.ts.map +1 -0
  195. package/dist/tracing/setup.js +436 -0
  196. package/dist/utils.d.ts +204 -0
  197. package/dist/utils.d.ts.map +1 -0
  198. package/dist/utils.js +867 -0
  199. package/dist/workflow/activity.d.ts +71 -0
  200. package/dist/workflow/activity.d.ts.map +1 -0
  201. package/dist/workflow/activity.js +357 -0
  202. package/dist/workflow/attempt.d.ts +12 -0
  203. package/dist/workflow/attempt.d.ts.map +1 -0
  204. package/dist/workflow/attempt.js +96 -0
  205. package/dist/workflow/client.d.ts +46 -0
  206. package/dist/workflow/client.d.ts.map +1 -0
  207. package/dist/workflow/client.js +299 -0
  208. package/dist/workflow/context.d.ts +37 -0
  209. package/dist/workflow/context.d.ts.map +1 -0
  210. package/dist/workflow/context.js +350 -0
  211. package/dist/workflow/heartbeat.d.ts +15 -0
  212. package/dist/workflow/heartbeat.d.ts.map +1 -0
  213. package/dist/workflow/heartbeat.js +78 -0
  214. package/dist/workflow/index.d.ts +14 -0
  215. package/dist/workflow/index.d.ts.map +1 -0
  216. package/dist/workflow/index.js +10 -0
  217. package/dist/workflow/memory.d.ts +17 -0
  218. package/dist/workflow/memory.d.ts.map +1 -0
  219. package/dist/workflow/memory.js +184 -0
  220. package/package.json +73 -0
package/dist/models.js ADDED
@@ -0,0 +1,1726 @@
1
+ /**
2
+ * Shared wire-protocol models for Runner SDK components.
3
+ *
4
+ * Defines the API contracts between:
5
+ * - Orchestration Engine (OE)
6
+ * - Agent Execution Runtime (AER)
7
+ * - Tool Executor Pod
8
+ *
9
+ * Each Pydantic model in `agent_engine_runner_shared/models.py` maps to:
10
+ * - a Zod schema (`FooSchema`) for runtime validation, and
11
+ * - the inferred TS type (`Foo = z.infer<typeof FooSchema>`).
12
+ *
13
+ * Models with non-trivial methods (`LLMResult`) follow the agent-engine-sdk
14
+ * pattern of class + separate schema.
15
+ */
16
+ import { z } from "zod";
17
+ import { recordSuspendRequest } from "./context.js";
18
+ import { ToolAPIErrorSchema } from "./tool_api_error.js";
19
+ import { JsonValueSchema, LLMInvocationOptions, LLMInvocationOptionsSchema, LLMResponse, LLMResponseSchema, LLMTokenUsage, LLMTokenUsageSchema, LLMToolCall, LLMToolCallSchema, LLMToolSchema, LLMToolSchemaValidator, MessageSchema, serializeMessage, ToolCallChunkSchema, } from "@mongodb-js/agent-engine-sdk";
20
+ // =============================================================================
21
+ // Execution Status
22
+ // =============================================================================
23
+ /** Status of an agent execution. */
24
+ export const ExecutionStatusSchema = z.enum([
25
+ "pending",
26
+ "running",
27
+ "suspended",
28
+ "resuming",
29
+ "completed",
30
+ "error",
31
+ "cancelled",
32
+ ]);
33
+ export const ExecutionStatus = {
34
+ PENDING: "pending",
35
+ RUNNING: "running",
36
+ SUSPENDED: "suspended",
37
+ RESUMING: "resuming",
38
+ COMPLETED: "completed",
39
+ ERROR: "error",
40
+ CANCELLED: "cancelled",
41
+ };
42
+ // =============================================================================
43
+ // Suspend Payload (Agent → Platform)
44
+ // =============================================================================
45
+ /**
46
+ * Payload returned by an agent tool to trigger human-in-the-loop suspension.
47
+ *
48
+ * Agent tools signal a suspend by returning a JSON string containing these
49
+ * fields. The `__suspend__` flag is stripped before the payload is passed
50
+ * to the framework's interrupt() handler.
51
+ *
52
+ * Example usage in an agent tool:
53
+ *
54
+ * import { suspendPayloadToJson } from '@mongodb-js/agent-engine-runner-shared'
55
+ * return suspendPayloadToJson({
56
+ * suspend_reason: 'awaiting_human_review',
57
+ * suspend_context: { claim_id: 'C-123', task_id: 'T-456' },
58
+ * })
59
+ */
60
+ export const SuspendPayloadSchema = z.object({
61
+ /** Why the agent is suspending (e.g. 'awaiting_human_review'). */
62
+ suspend_reason: z.string(),
63
+ /** Arbitrary context the human reviewer needs to make a decision. */
64
+ suspend_context: z.record(z.string(), z.unknown()).default({}),
65
+ });
66
+ /**
67
+ * Serialize a SuspendPayload to the suspend wire marker, and record an
68
+ * out-of-band suspend request on the current execution frame.
69
+ *
70
+ * The Tool Pod honors suspend from that recorded signal — set only here, in
71
+ * the tool author's own code — not by sniffing tool-result content, so
72
+ * untrusted data a tool relays can no longer forge a HITL suspend. The marker
73
+ * string is still returned unchanged for wire/replay compatibility.
74
+ */
75
+ export function suspendPayloadToJson(payload) {
76
+ const marker = { ...payload, __suspend__: true };
77
+ recordSuspendRequest(marker);
78
+ return JSON.stringify(marker);
79
+ }
80
+ // =============================================================================
81
+ // Streaming / Interrupt Results (AER internal)
82
+ // =============================================================================
83
+ /**
84
+ * Result from agent execution when the agent is suspended.
85
+ *
86
+ * Contains the suspend payload directly (framework-agnostic) rather than
87
+ * wrapping framework-specific Interrupt objects. Any framework-specific
88
+ * state needed to resume (LangGraph checkpoint id, ADK function-call
89
+ * correlation, etc.) is carried opaquely in `metadata` — the AER never
90
+ * inspects it; the framework adapter writes it on suspend and reads it back
91
+ * from `RequestContext.metadata` on resume.
92
+ */
93
+ export const PendingInterruptSchema = z.object({
94
+ id: z.string().min(1),
95
+ value: z.unknown(),
96
+ });
97
+ export const InterruptResultSchema = z.object({
98
+ suspend_payload: z.unknown(),
99
+ interrupts: z.array(PendingInterruptSchema).optional(),
100
+ resume_schema: z.record(z.string(), z.unknown()).optional(),
101
+ /** Opaque framework-owned resume state, round-tripped via the OE. */
102
+ metadata: z.record(z.string(), z.unknown()).default({}),
103
+ /** Assistant/tool messages produced before suspension, for Memory ingestion. */
104
+ messages: z.array(MessageSchema).default([]),
105
+ });
106
+ /**
107
+ * Result from agent execution for normal completion.
108
+ *
109
+ * Returned by _execute_via_agent_stream when the agent finishes without
110
+ * suspend. The caller uses content for the final response and messages
111
+ * for memory writing.
112
+ */
113
+ export const StreamingResultSchema = z.object({
114
+ /** Sanitized final AI response (thinking stripped, trailing empties skipped). */
115
+ content: z.string().default(""),
116
+ /** All messages from agent execution (sdk-core Message objects), for memory writer. */
117
+ messages: z.array(MessageSchema).default([]),
118
+ /** Opaque framework-owned state produced by the completed execution. */
119
+ metadata: z.record(z.string(), z.unknown()).optional(),
120
+ });
121
+ // =============================================================================
122
+ // Agent Start (Client → OE)
123
+ // =============================================================================
124
+ /** Request to invoke the agent (compatible with agent-runtime). */
125
+ export const InvokeRequestSchema = z.object({
126
+ /** User message. */
127
+ message: z.string(),
128
+ /** Session ID for trace correlation. */
129
+ session_id: z.string().optional(),
130
+ // Multi-tenant context
131
+ /** Organization ID for multi-tenant isolation. */
132
+ org_id: z.string().optional(),
133
+ /** User ID for personalization. */
134
+ user_id: z.string().optional(),
135
+ /** Workspace identifier for cost tracking. */
136
+ workspace_id: z.string().optional(),
137
+ /** Project ID for project-level scoping. */
138
+ project_id: z.string().optional(),
139
+ // Per-agent AER routing
140
+ /** AER HTTP endpoint for this agent; overrides default AER_URL. */
141
+ aer_url: z.string().optional(),
142
+ /** Tool HTTP endpoint for this agent; overrides default TOOL_URL. */
143
+ tool_url: z.string().optional(),
144
+ // Sync mode (default: wait for completion)
145
+ /** If true, wait for completion and return result. */
146
+ wait: z.boolean().default(true),
147
+ /** Caller-provided custom headers (X-Mdb-Agent-Engine-Custom-* HTTP headers, prefix-stripped and lowercased). */
148
+ custom_headers: z.record(z.string(), z.string()).optional(),
149
+ });
150
+ /** Response from invoking the agent (compatible with agent-runtime). */
151
+ export const InvokeResponseSchema = z.object({
152
+ /** The agent's response. */
153
+ result: z.unknown().optional(),
154
+ /** Session ID. */
155
+ session_id: z.string().optional(),
156
+ /** User ID used. */
157
+ user_id: z.string().optional(),
158
+ // Runner-specific fields (for async mode and tracking)
159
+ /** Unique execution identifier. */
160
+ execution_id: z.string().optional(),
161
+ /** Execution status. */
162
+ status: z.string().optional(),
163
+ /** Error message if failed. */
164
+ error: z.string().optional(),
165
+ });
166
+ // =============================================================================
167
+ // Execute Request (OE → AER)
168
+ // =============================================================================
169
+ /** Request to execute an agent in AER. */
170
+ export const ExecuteRequestSchema = z.object({
171
+ platform_trace_id: z.string().nullable().optional(),
172
+ /** Unique execution identifier. */
173
+ execution_id: z.string(),
174
+ /**
175
+ * User message. Defaults to empty. Promotion of `payload.message` into an
176
+ * empty top-level message happens in the AER (`resolveInvocationParams`),
177
+ * NOT at the model level — mirrors Python's `ExecuteRequest`. Keeping it
178
+ * out of the model lets the AER reject a message supplied in both places
179
+ * as a clean 400 instead of a 422 that would echo the payload into logs.
180
+ */
181
+ message: z.string().default(""),
182
+ /** URL of the OE for callbacks. */
183
+ platform_api_url: z.string(),
184
+ /** Suspension generation expected by this dispatch. */
185
+ suspend_generation: z
186
+ .number()
187
+ .int()
188
+ .nonnegative()
189
+ .nullable()
190
+ .transform((value) => value ?? undefined)
191
+ .optional(),
192
+ /** Replica-specific OE owner URL for callback fallback. */
193
+ platform_api_owner_url: z.string().nullable().optional(),
194
+ /** Whether this is a resume after SUSPEND. */
195
+ resume: z.boolean().default(false),
196
+ /** Step to resume from. */
197
+ resume_from_step: z.number().int().optional(),
198
+ /** Structured data to inject on resume. */
199
+ resume_data: z.record(z.string(), z.unknown()).optional(),
200
+ // Multi-tenant context
201
+ /** Organization ID for multi-tenant isolation. */
202
+ org_id: z.string().optional(),
203
+ /** User ID for personalization. */
204
+ user_id: z.string().optional(),
205
+ /** Session ID; the AER adapter uses this as the LangGraph thread_id. */
206
+ session_id: z.string().optional(),
207
+ /** Workspace identifier for cost tracking. */
208
+ workspace_id: z.string().optional(),
209
+ /** Project ID for project-level scoping. */
210
+ project_id: z.string().optional(),
211
+ /** Caller-provided custom headers. */
212
+ custom_headers: z.record(z.string(), z.string()).optional(),
213
+ /**
214
+ * Opaque caller-provided input forwarded unchanged on every dispatch;
215
+ * `payload.message` fills the top-level message when it is empty.
216
+ */
217
+ payload: z.record(z.string(), z.unknown()).optional(),
218
+ /**
219
+ * Opaque framework-owned state passed through to the selected adapter.
220
+ */
221
+ metadata: z.record(z.string(), z.unknown()).optional(),
222
+ /**
223
+ * True when this session's most recent prior execution was cancelled (pod
224
+ * torn down mid-run); the framework adapter fences off that run's partial
225
+ * checkpoint writes before running this turn.
226
+ */
227
+ previous_execution_cancelled: z.boolean().default(false),
228
+ });
229
+ // =============================================================================
230
+ // Tool Execution (AER → OE → AER/ToolPod)
231
+ // =============================================================================
232
+ /**
233
+ * Request to execute a tool (AER → OE for approval).
234
+ */
235
+ export const ToolExecuteRequestSchema = z.object({
236
+ /** Execution identifier. */
237
+ execution_id: z.string(),
238
+ /** Name of the tool to execute. */
239
+ tool_name: z.string(),
240
+ /** Tool arguments. */
241
+ arguments: z.record(z.string(), z.unknown()),
242
+ /** Step number in execution sequence. */
243
+ step_number: z.number().int(),
244
+ /** Stable LLM tool-call id; joins this call's execution-log records to the session message. */
245
+ tool_call_id: z.string().optional(),
246
+ /** Explicit observability kind. */
247
+ kind: z.string().optional(),
248
+ /** Whether OE should return the approved call to the current AER stack. */
249
+ is_local: z.boolean().default(true),
250
+ /** Top-level tool argument names to redact from execution logs. */
251
+ redact_fields: z.array(z.string()).default([]),
252
+ /** Credential provider type for delegated auth. */
253
+ provider_type: z.string().nullish(),
254
+ /** Requested OAuth scopes for delegated auth. */
255
+ scopes: z.array(z.string()).default([]),
256
+ /** Observability metadata. */
257
+ metadata: z.record(z.string(), z.unknown()).default({}),
258
+ /** Caller-provided headers carried only for this active request. */
259
+ custom_headers: z.record(z.string(), z.string()).optional(),
260
+ /** Active OTel trace ID, for OE execution-log correlation. */
261
+ trace_id: z.string().nullish(),
262
+ /** Active OTel span ID, for OE execution-log correlation. */
263
+ span_id: z.string().nullish(),
264
+ });
265
+ /** Authorization details returned when broker consent is required. */
266
+ export const ElicitationInfoSchema = z.object({
267
+ elicitation_id: z.string(),
268
+ authorization_url: z.string(),
269
+ message: z.string().default(""),
270
+ created: z.boolean().default(false),
271
+ });
272
+ /** Identity of the policy that caused a guardrail block or require_review halt. */
273
+ export const GuardrailMetaSchema = z.object({
274
+ /** ID of the policy that caused the halt. */
275
+ guardrail_id: z.string(),
276
+ /** Category of the policy that caused the halt. */
277
+ guardrail_category: z.string(),
278
+ });
279
+ /** Response from OE for tool execution request. */
280
+ const ToolExecuteResponseObjectSchema = z.object({
281
+ /** Whether to proceed with execution. */
282
+ proceed: z.boolean(),
283
+ /** Cached result if replaying. */
284
+ cached_result: z.unknown().nullish(),
285
+ /** URL to route to (callback or OE-owned stream relay). Null when OE returns result directly. */
286
+ route_to: z.string().nullish(),
287
+ /** Reason if blocked. */
288
+ reason: z.string().nullish(),
289
+ /** Authorization elicitation details when broker consent is required. */
290
+ elicitation: ElicitationInfoSchema.nullish(),
291
+ /** Final status when OE directly executes the intercepted tool. */
292
+ status: z.string().nullish(),
293
+ /** Final result from OE-owned execution. */
294
+ result: z.unknown().nullish(),
295
+ /** Execution error message. */
296
+ error: z.string().nullish(),
297
+ /** Machine-readable failure classification for an invoke_llm error. */
298
+ error_code: z.string().nullish(),
299
+ /** Whether retry is safe. */
300
+ retryable: z.boolean().default(false),
301
+ /** Highest step number observed during nested execution. */
302
+ latest_step_number: z.number().int().nullish(),
303
+ /** Whether replay cache answered. */
304
+ from_cache: z.boolean().default(false),
305
+ /** Execution duration. */
306
+ duration_ms: z.number().nullish(),
307
+ /** Execution pod name. */
308
+ pod_name: z.string().nullish(),
309
+ /** Policy identity when a guardrail halt fires. */
310
+ guardrail_meta: GuardrailMetaSchema.nullish(),
311
+ /** Structured external API failure classification. */
312
+ tool_api_error: ToolAPIErrorSchema.nullish(),
313
+ });
314
+ /**
315
+ * Assemble `guardrail_meta` from the flat `guardrail_id`/`guardrail_category`
316
+ * fields the OE wire format sends, so callers work with a single structured
317
+ * object. Mirrors Python's `_assemble_guardrail_meta` model validator.
318
+ */
319
+ export const ToolExecuteResponseSchema = z.preprocess((data) => {
320
+ if (data != null && typeof data === "object" && !Array.isArray(data)) {
321
+ const d = data;
322
+ if (d.guardrail_id && d.guardrail_category && d.guardrail_meta == null) {
323
+ return {
324
+ ...d,
325
+ guardrail_meta: {
326
+ guardrail_id: d.guardrail_id,
327
+ guardrail_category: d.guardrail_category,
328
+ },
329
+ };
330
+ }
331
+ }
332
+ return data;
333
+ }, ToolExecuteResponseObjectSchema);
334
+ /**
335
+ * Report tool execution result (AER → OE).
336
+ */
337
+ export const ToolResultRequestSchema = z.object({
338
+ /** Execution identifier. */
339
+ execution_id: z.string(),
340
+ /** Step number. */
341
+ step_number: z.number().int(),
342
+ /** Tool name. */
343
+ tool_name: z.string(),
344
+ /** Stable LLM tool-call id; joins this result's execution-log record to its call and the session message. */
345
+ tool_call_id: z.string().optional(),
346
+ /** Execution status: success, error, suspend, interrupted. */
347
+ status: z.string(),
348
+ /** Tool result if successful. */
349
+ result: z.unknown().optional(),
350
+ /** Error message if failed. */
351
+ error: z.string().optional(),
352
+ /** Execution duration in milliseconds. */
353
+ duration_ms: z.number(),
354
+ /** Hostname/pod name where tool was executed. */
355
+ pod_name: z.string().optional(),
356
+ // Token usage fields (populated for invoke_llm calls)
357
+ /** Prompt/input tokens used. */
358
+ prompt_tokens: z.number().int().optional(),
359
+ /** Completion/output tokens used. */
360
+ completion_tokens: z.number().int().optional(),
361
+ /** Total tokens used. */
362
+ total_tokens: z.number().int().optional(),
363
+ /** LLM model name. */
364
+ model: z.string().optional(),
365
+ /** Workspace identifier. */
366
+ workspace_id: z.string().optional(),
367
+ /** Explicit observability kind. */
368
+ kind: z.string().optional(),
369
+ /** Observability metadata. */
370
+ metadata: z.record(z.string(), z.unknown()).default({}),
371
+ /** Active OTel trace ID, for OE execution-log correlation. */
372
+ trace_id: z.string().nullish(),
373
+ /** Active OTel span ID, for OE execution-log correlation. */
374
+ span_id: z.string().nullish(),
375
+ /** Structured external API failure classification. */
376
+ tool_api_error: ToolAPIErrorSchema.nullish(),
377
+ });
378
+ // =============================================================================
379
+ // Tool Pod Execution (OE → ToolPod)
380
+ // =============================================================================
381
+ export const MAX_TOOL_ARGUMENT_BYTES = 16 * 1024 * 1024;
382
+ const ToolArgumentsSchema = z
383
+ .record(z.string(), JsonValueSchema)
384
+ .superRefine((toolArguments, context) => {
385
+ let serialized;
386
+ try {
387
+ serialized = JSON.stringify(toolArguments);
388
+ }
389
+ catch {
390
+ context.addIssue({
391
+ code: "custom",
392
+ message: "tool arguments must contain JSON values",
393
+ });
394
+ return;
395
+ }
396
+ if (Buffer.byteLength(serialized, "utf-8") > MAX_TOOL_ARGUMENT_BYTES) {
397
+ context.addIssue({
398
+ code: "custom",
399
+ message: `tool arguments exceed ${MAX_TOOL_ARGUMENT_BYTES} bytes`,
400
+ });
401
+ }
402
+ });
403
+ /** Delegated credential injected by OE for tool execution. */
404
+ export const ToolAuthorizationSchema = z.object({
405
+ /** Bearer token for third-party API access. */
406
+ token: z.string(),
407
+ /** Token expiry as Unix seconds. */
408
+ expires_at: z.number().int().nullish(),
409
+ });
410
+ /** Request to execute a tool in a Tool Pod. */
411
+ export const ToolPodExecuteRequestSchema = z.object({
412
+ platform_trace_id: z.string().nullable().optional(),
413
+ /** Execution identifier. */
414
+ execution_id: z.string(),
415
+ /** Name of the tool. */
416
+ tool_name: z.string(),
417
+ /** Tool arguments. */
418
+ arguments: ToolArgumentsSchema,
419
+ /** Tool call ID from LLM. */
420
+ tool_call_id: z.string().optional(),
421
+ /**
422
+ * Step number of this call in the execution sequence, so the per-call
423
+ * interrupt (POST /interrupt/call) can address exactly this work.
424
+ * Optional for backward compatibility with older OE dispatchers: absent,
425
+ * the call is only drain-addressable.
426
+ */
427
+ step_number: z.number().int().nonnegative().optional(),
428
+ /**
429
+ * Session ID for memory context. For framework integrations, this is
430
+ * the framework's thread ID. Must be non-empty.
431
+ */
432
+ session_id: z.string().min(1),
433
+ /** User ID for context. */
434
+ user_id: z.string().optional(),
435
+ /** OE callback URL for memory and other operations. */
436
+ oe_url: z.string().optional(),
437
+ /** Replica-specific OE owner callback URL. */
438
+ oe_owner_url: z.string().nullable().optional(),
439
+ /** Delegated credential injected by OE via the credential broker. */
440
+ authorization: ToolAuthorizationSchema.optional(),
441
+ /** Caller-provided custom headers. */
442
+ custom_headers: z.record(z.string(), z.string()).optional(),
443
+ /**
444
+ * Opaque caller-provided invocation payload, forwarded so tools can read it
445
+ * via `getCurrentPayload()`.
446
+ */
447
+ payload: z.record(z.string(), z.unknown()).optional(),
448
+ /**
449
+ * Tool execution metadata forwarded by OE (recognized keys: mcp_server,
450
+ * mcp_tool). Accepted for wire-compatibility; MCP tool resolution is not
451
+ * ported to TS, so it is currently unused here.
452
+ */
453
+ metadata: z.record(z.string(), z.unknown()).default({}),
454
+ });
455
+ /** Response from Tool Pod execution. */
456
+ export const ToolPodExecuteResponseSchema = z.object({
457
+ /** Execution status: success, error. */
458
+ status: z.string(),
459
+ /** Tool result. */
460
+ result: z.unknown().optional(),
461
+ /** Error message if failed. */
462
+ error: z.string().optional(),
463
+ /** Hostname/pod name where tool was executed. */
464
+ pod_name: z.string().optional(),
465
+ /** Explicit observability kind. */
466
+ kind: z.string().optional(),
467
+ /** Observability metadata. */
468
+ metadata: z.record(z.string(), z.unknown()).default({}),
469
+ /**
470
+ * True when this runner reports HITL suspend out of band via `status`.
471
+ * Absent on older runners; the OE treats that absence as a
472
+ * legacy pod that still signals suspend through in-band result content.
473
+ */
474
+ oob_suspend_supported: z.boolean().optional(),
475
+ /** Structured external API failure classification. */
476
+ tool_api_error: ToolAPIErrorSchema.nullish(),
477
+ });
478
+ // =============================================================================
479
+ // Guardrails Runtime Check (OE → ToolPod)
480
+ // =============================================================================
481
+ /** Runtime stage where OE is asking the Tool Pod to evaluate guardrails. */
482
+ export const GuardrailRuntimeStageSchema = z.enum([
483
+ "llm_input",
484
+ "llm_output",
485
+ "tool_input",
486
+ "tool_output",
487
+ ]);
488
+ export const GuardrailRuntimeStage = {
489
+ LLM_INPUT: "llm_input",
490
+ LLM_OUTPUT: "llm_output",
491
+ TOOL_INPUT: "tool_input",
492
+ TOOL_OUTPUT: "tool_output",
493
+ };
494
+ /** Decision returned by the Tool Pod guardrails evaluator. */
495
+ export const GuardrailCheckDecisionSchema = z.enum([
496
+ "allow",
497
+ "block",
498
+ "modify",
499
+ "require_review",
500
+ "log_only",
501
+ ]);
502
+ export const GuardrailCheckDecision = {
503
+ ALLOW: "allow",
504
+ BLOCK: "block",
505
+ MODIFY: "modify",
506
+ REQUIRE_REVIEW: "require_review",
507
+ LOG_ONLY: "log_only",
508
+ };
509
+ /** OE-selected guardrail policy sent to the Tool Pod for evaluation. */
510
+ export const GuardrailRuntimePolicySchema = z.object({
511
+ /** Guardrail policy identifier. */
512
+ id: z.string(),
513
+ /** Guardrail policy type. */
514
+ type: z.string(),
515
+ /** Guardrail policy status. */
516
+ status: z.string().default("active"),
517
+ /** Action requested when the policy triggers. */
518
+ action: z.string(),
519
+ /** Runtime stages this policy applies to; empty means all stages. */
520
+ stage_filter: z.array(z.string()).default([]),
521
+ /** Evaluator-specific policy configuration. */
522
+ config: z.record(z.string(), JsonValueSchema).default({}),
523
+ });
524
+ /**
525
+ * Whether `policy` applies at `stage`. Inactive policies never apply; an empty
526
+ * stage filter applies to all stages; otherwise the stage must match (case- and
527
+ * whitespace-insensitive). Mirrors `GuardrailRuntimePolicy.applies_to_stage`.
528
+ */
529
+ export function appliesToStage(policy, stage) {
530
+ if (policy.status.trim().toLowerCase() !== "active") {
531
+ return false;
532
+ }
533
+ if (policy.stage_filter.length === 0) {
534
+ return true;
535
+ }
536
+ return policy.stage_filter.some((value) => value.trim().toLowerCase() === stage);
537
+ }
538
+ /**
539
+ * Resolve the decision a triggered policy requests, from its `action` (falling
540
+ * back to `config.on_fail`). Mirrors `GuardrailRuntimePolicy.check_decision`.
541
+ */
542
+ export function checkDecision(policy) {
543
+ const action = policy.action.trim().toLowerCase();
544
+ if (["modify", "transform", "fix"].includes(action)) {
545
+ return GuardrailCheckDecision.MODIFY;
546
+ }
547
+ if (["noop", "no_op", "warn", "log_only"].includes(action)) {
548
+ return GuardrailCheckDecision.LOG_ONLY;
549
+ }
550
+ if (action === "require_review") {
551
+ return GuardrailCheckDecision.REQUIRE_REVIEW;
552
+ }
553
+ if (["block", "exception"].includes(action)) {
554
+ return GuardrailCheckDecision.BLOCK;
555
+ }
556
+ const onFail = policy.config.on_fail;
557
+ if (typeof onFail === "string") {
558
+ const normalized = onFail.trim().toLowerCase();
559
+ if (normalized === "fix") {
560
+ return GuardrailCheckDecision.MODIFY;
561
+ }
562
+ if (["noop", "warn", "log_only"].includes(normalized)) {
563
+ return GuardrailCheckDecision.LOG_ONLY;
564
+ }
565
+ if (["block", "exception"].includes(normalized)) {
566
+ return GuardrailCheckDecision.BLOCK;
567
+ }
568
+ }
569
+ return GuardrailCheckDecision.ALLOW;
570
+ }
571
+ /** Runtime content and metadata to evaluate. */
572
+ export const GuardrailCheckInputSchema = z.object({
573
+ /** Text content to evaluate. */
574
+ text: z.string(),
575
+ /** Runtime metadata such as model, tool name, or source. */
576
+ metadata: z.record(z.string(), JsonValueSchema).default({}),
577
+ });
578
+ /** Execution context for a guardrail check. */
579
+ export const GuardrailCheckContextSchema = z.object({
580
+ /** Organization ID. */
581
+ org_id: z.string(),
582
+ /** Project ID. */
583
+ project_id: z.string(),
584
+ /** Workspace ID. */
585
+ workspace_id: z.string().nullish(),
586
+ /** Session ID. */
587
+ session_id: z.string().nullish(),
588
+ /** User ID. */
589
+ user_id: z.string().nullish(),
590
+ });
591
+ /** Request from OE to Tool Pod to evaluate selected guardrail policies. */
592
+ export const GuardrailCheckRequestSchema = z.object({
593
+ /** Execution identifier. */
594
+ execution_id: z.string(),
595
+ /** Runtime stage being evaluated. */
596
+ stage: GuardrailRuntimeStageSchema,
597
+ /** Content to evaluate. */
598
+ input: GuardrailCheckInputSchema,
599
+ /** Execution context. */
600
+ context: GuardrailCheckContextSchema,
601
+ /** OE-selected policies to evaluate. */
602
+ policies: z.array(GuardrailRuntimePolicySchema).default([]),
603
+ });
604
+ /** Evidence explaining why a guardrail policy triggered. */
605
+ export const GuardrailCheckEvidenceSchema = z.object({
606
+ /** Triggered policy identifier. */
607
+ policy_id: z.string(),
608
+ /** Human-readable evidence summary. */
609
+ message: z.string().default(""),
610
+ /** Evaluator-specific evidence metadata. */
611
+ metadata: z.record(z.string(), JsonValueSchema).default({}),
612
+ });
613
+ /** Decision returned by the Tool Pod guardrails evaluator. */
614
+ export const GuardrailCheckResponseSchema = z.object({
615
+ /** Guardrails decision. */
616
+ decision: GuardrailCheckDecisionSchema,
617
+ /** Whether execution may continue. */
618
+ allowed: z.boolean(),
619
+ /** Modified text when decision is modify, or original text for allow/no-op. */
620
+ transformed_text: z.string().nullish(),
621
+ /** Policy IDs that triggered. */
622
+ triggered_policy_ids: z.array(z.string()).default([]),
623
+ /** Policy trigger evidence. */
624
+ evidence: z.array(GuardrailCheckEvidenceSchema).default([]),
625
+ /** Decision reason. */
626
+ reason: z.string().nullish(),
627
+ /** Evaluator-specific response metadata. */
628
+ metadata: z.record(z.string(), JsonValueSchema).default({}),
629
+ });
630
+ // =============================================================================
631
+ // LLM Pod Execution (AER → ToolPod)
632
+ // =============================================================================
633
+ /**
634
+ * Typed invoke_llm arguments forwarded through OE and tool pods.
635
+ *
636
+ * Python uses Pydantic aliases:
637
+ * - validation alias `stop` ↔ `stop_sequences`, serialization alias `stop`
638
+ * - pre-validator renames `kwargs` → `options`
639
+ *
640
+ * In Zod we replicate this with a `z.preprocess` that normalizes incoming
641
+ * payloads into the canonical schema (using `stop_sequences` and
642
+ * `options`). For outgoing serialization that needs the wire alias `stop`,
643
+ * use `serializeInvokeLLMRequestArguments()`.
644
+ */
645
+ export const InvokeLLMRequestArgumentsSchema = z.preprocess((value) => {
646
+ if (value === null || typeof value !== "object" || Array.isArray(value))
647
+ return value;
648
+ const obj = value;
649
+ const normalized = { ...obj };
650
+ // `stop` ↔ `stop_sequences` validation alias.
651
+ if (!("stop_sequences" in normalized) && "stop" in normalized) {
652
+ normalized["stop_sequences"] = normalized["stop"];
653
+ delete normalized["stop"];
654
+ }
655
+ // `kwargs` → `options` pre-validator.
656
+ if (!("options" in normalized) && "kwargs" in normalized) {
657
+ normalized["options"] = normalized["kwargs"];
658
+ delete normalized["kwargs"];
659
+ }
660
+ return normalized;
661
+ }, z.object({
662
+ /** Model name (e.g. 'gpt-5.4-mini', 'gemini-3-flash-preview'). */
663
+ model: z.string(),
664
+ /** Conversation in sdk-core Message wire format. */
665
+ messages: z.array(MessageSchema),
666
+ /**
667
+ * Stable identifier for the LLM instance registered via
668
+ * `app.llm({ llmId })`; the tool pod resolves this id against its
669
+ * named-LLM registry. Unnamed `app.llm()` calls register under the
670
+ * sentinel id `"__default__"`. Defaults to `"__default__"` for backward
671
+ * compatibility with callers that omit this field.
672
+ */
673
+ llm_id: z.string().default("__default__"),
674
+ /** Stop sequences forwarded to the underlying LLM provider. */
675
+ stop_sequences: z.array(z.string()).optional(),
676
+ /** Serialized tool schemas for bind_tools. */
677
+ tools: z.array(LLMToolSchemaValidator).optional(),
678
+ /**
679
+ * Forced tool selection forwarded to the tool pod's bind_tools call
680
+ * (e.g. a function name from withStructuredOutput). LangChain translates
681
+ * the value against the bound tools.
682
+ */
683
+ tool_choice: JsonValueSchema.optional(),
684
+ /** Explicit provider/model invocation options (e.g. max_tokens). */
685
+ options: LLMInvocationOptionsSchema.optional(),
686
+ /** Whether OE should approve and route a real streaming invoke_llm call. */
687
+ stream: z.boolean().default(false),
688
+ }));
689
+ /**
690
+ * Serialize InvokeLLMRequestArguments to the snake_case wire shape, the
691
+ * equivalent of Python's `model_dump(by_alias=True)`:
692
+ * - `stop_sequences` → `stop` (Pydantic `serialization_alias="stop"`), and
693
+ * - each Message is dumped camel→snake via `serializeMessage`.
694
+ *
695
+ * Use this when sending to a Python consumer (OE / Tool Pod) that expects the
696
+ * snake_case wire fields.
697
+ */
698
+ export function serializeInvokeLLMRequestArguments(args) {
699
+ const { stop_sequences, messages, ...rest } = args;
700
+ const out = { ...rest };
701
+ out["messages"] = messages.map(serializeMessage);
702
+ if (stop_sequences !== undefined)
703
+ out["stop"] = stop_sequences;
704
+ return out;
705
+ }
706
+ /**
707
+ * Request to invoke LLM on a tool executor pod.
708
+ *
709
+ * Python pre-validator `_normalize_flat_payload`: if `arguments` is
710
+ * missing but `execution_id` is present, treat the remaining keys as
711
+ * the arguments payload. Same logic replicated via `z.preprocess`.
712
+ */
713
+ export const LLMPodInvokeRequestSchema = z.preprocess((value) => {
714
+ if (value === null || typeof value !== "object" || Array.isArray(value))
715
+ return value;
716
+ const obj = value;
717
+ if ("arguments" in obj || !("execution_id" in obj))
718
+ return value;
719
+ // Routing metadata stays top-level rather than being folded into the
720
+ // legacy flat payload's LLM arguments.
721
+ const { execution_id, platform_trace_id, step_number, ...args } = obj;
722
+ const out = {
723
+ execution_id,
724
+ platform_trace_id,
725
+ arguments: args,
726
+ };
727
+ if (step_number !== undefined && step_number !== null) {
728
+ out["step_number"] = step_number;
729
+ }
730
+ return out;
731
+ }, z.object({
732
+ platform_trace_id: z.string().nullable().optional(),
733
+ /** Execution identifier. */
734
+ execution_id: z.string(),
735
+ /** Typed invoke_llm arguments. */
736
+ arguments: InvokeLLMRequestArgumentsSchema,
737
+ /**
738
+ * Step number of this LLM call, so the per-call interrupt
739
+ * (POST /interrupt/call) can address exactly this invocation. Optional
740
+ * for backward compatibility with older OE dispatchers.
741
+ */
742
+ step_number: z.number().int().nonnegative().optional(),
743
+ }));
744
+ /**
745
+ * Normalized result from an LLM invocation.
746
+ *
747
+ * Provides a consistent shape for LLM responses regardless of the
748
+ * underlying provider (OpenAI, Gemini, Anthropic, etc.). Implemented as
749
+ * a class to mirror `LLMResponse` in agent-engine-sdk and to host the
750
+ * `fromResponse` / `toResponse` / `extractUsage` conversion methods.
751
+ */
752
+ export class LLMResult {
753
+ content;
754
+ toolCalls;
755
+ usage;
756
+ metadata;
757
+ id;
758
+ name;
759
+ additionalKwargs;
760
+ responseMetadata;
761
+ constructor(data) {
762
+ this.content = data.content ?? "";
763
+ this.toolCalls = data.toolCalls ?? [];
764
+ this.usage = data.usage;
765
+ this.metadata = data.metadata ?? {};
766
+ this.id = data.id;
767
+ this.name = data.name;
768
+ this.additionalKwargs = data.additionalKwargs;
769
+ this.responseMetadata = data.responseMetadata;
770
+ }
771
+ /**
772
+ * Extract a normalized result from any LLM response object.
773
+ *
774
+ * Accepts an sdk-core `LLMResponse` directly, or a duck-typed object
775
+ * (e.g. raw provider response) by falling back to string-coerced
776
+ * content extraction.
777
+ */
778
+ static fromResponse(response) {
779
+ if (response instanceof LLMResponse) {
780
+ return new LLMResult({
781
+ content: response.content,
782
+ toolCalls: response.toolCalls ?? [],
783
+ usage: response.usage,
784
+ metadata: jsonSafeMetadata(response.metadata) ?? {},
785
+ id: response.id,
786
+ name: response.name,
787
+ additionalKwargs: jsonSafeMetadata(response.additionalKwargs),
788
+ responseMetadata: jsonSafeMetadata(response.responseMetadata),
789
+ });
790
+ }
791
+ const isObj = response !== null &&
792
+ response !== undefined &&
793
+ typeof response === "object";
794
+ const obj = (isObj ? response : {});
795
+ // Mirror Python `response.content if hasattr(response, "content") else str(response)`:
796
+ // if the response carries a `content` attribute we use it (coerced to string),
797
+ // otherwise we fall back to String(response).
798
+ const content = isObj && "content" in obj
799
+ ? typeof obj["content"] === "string"
800
+ ? obj["content"]
801
+ : obj["content"] === undefined || obj["content"] === null
802
+ ? ""
803
+ : String(obj["content"])
804
+ : String(response);
805
+ const toolCallsRaw = obj["tool_calls"];
806
+ const toolCalls = Array.isArray(toolCallsRaw)
807
+ ? toolCallsRaw
808
+ : [];
809
+ const usage = LLMResult.extractUsage(response);
810
+ const metadata = jsonSafeMetadata(obj["metadata"]) ?? {};
811
+ const responseMetadata = jsonSafeMetadata(obj["response_metadata"]);
812
+ const additionalKwargs = jsonSafeMetadata(obj["additional_kwargs"]);
813
+ const id = typeof obj["id"] === "string" ? obj["id"] : undefined;
814
+ const name = typeof obj["name"] === "string" ? obj["name"] : undefined;
815
+ return new LLMResult({
816
+ content,
817
+ toolCalls,
818
+ usage,
819
+ metadata,
820
+ id,
821
+ name,
822
+ additionalKwargs,
823
+ responseMetadata,
824
+ });
825
+ }
826
+ /**
827
+ * Extract token usage metadata from any response or chunk.
828
+ *
829
+ * Checks (in order): sdk-core `LLMResponse.usage`, `usage_metadata`,
830
+ * `response_metadata.usage`, `response_metadata.token_usage`,
831
+ * `usage`, `metadata.usage`.
832
+ */
833
+ static extractUsage(response) {
834
+ try {
835
+ if (response instanceof LLMResponse && response.usage !== undefined) {
836
+ return response.usage;
837
+ }
838
+ const obj = (response ?? {});
839
+ const fromMeta = coerceTokenUsage(obj["usage_metadata"]);
840
+ if (fromMeta !== undefined)
841
+ return fromMeta;
842
+ const respMeta = obj["response_metadata"];
843
+ if (isRecord(respMeta)) {
844
+ for (const key of ["usage", "token_usage"]) {
845
+ const coerced = coerceTokenUsage(respMeta[key]);
846
+ if (coerced !== undefined)
847
+ return coerced;
848
+ }
849
+ }
850
+ const fromUsageAttr = coerceTokenUsage(obj["usage"]);
851
+ if (fromUsageAttr !== undefined)
852
+ return fromUsageAttr;
853
+ const metadata = obj["metadata"];
854
+ if (isRecord(metadata)) {
855
+ return coerceTokenUsage(metadata["usage"]);
856
+ }
857
+ return undefined;
858
+ }
859
+ catch {
860
+ return undefined;
861
+ }
862
+ }
863
+ /** Convert the normalized runner result to sdk-core's LLMResponse shape. */
864
+ toResponse() {
865
+ let metadata = { ...this.metadata };
866
+ if (this.usage !== undefined && Object.keys(metadata).length === 0) {
867
+ // Mirror Python: dump usage (excluding undefined) into metadata when
868
+ // metadata was otherwise empty.
869
+ const dump = {};
870
+ if (this.usage.inputTokens !== undefined)
871
+ dump["input_tokens"] = this.usage.inputTokens;
872
+ if (this.usage.outputTokens !== undefined)
873
+ dump["output_tokens"] = this.usage.outputTokens;
874
+ if (this.usage.promptTokens !== undefined)
875
+ dump["prompt_tokens"] = this.usage.promptTokens;
876
+ if (this.usage.completionTokens !== undefined)
877
+ dump["completion_tokens"] = this.usage.completionTokens;
878
+ if (this.usage.totalTokens !== undefined)
879
+ dump["total_tokens"] = this.usage.totalTokens;
880
+ if (this.usage.model !== undefined)
881
+ dump["model"] = this.usage.model;
882
+ metadata = dump;
883
+ }
884
+ return new LLMResponse({
885
+ content: this.content,
886
+ toolCalls: this.toolCalls.length > 0 ? this.toolCalls : undefined,
887
+ metadata,
888
+ usage: this.usage,
889
+ id: this.id,
890
+ name: this.name,
891
+ additionalKwargs: this.additionalKwargs,
892
+ responseMetadata: this.responseMetadata,
893
+ });
894
+ }
895
+ }
896
+ function isRecord(value) {
897
+ return value !== null && typeof value === "object" && !Array.isArray(value);
898
+ }
899
+ function usageInt(value) {
900
+ if (typeof value === "boolean" || value == null)
901
+ return undefined;
902
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0)
903
+ return Math.trunc(value);
904
+ return undefined;
905
+ }
906
+ function hasTokenCounts(usage) {
907
+ return (usage.inputTokens !== undefined ||
908
+ usage.outputTokens !== undefined ||
909
+ usage.promptTokens !== undefined ||
910
+ usage.completionTokens !== undefined ||
911
+ usage.totalTokens !== undefined);
912
+ }
913
+ function promptOf(usage) {
914
+ return usage.inputTokens ?? usage.promptTokens;
915
+ }
916
+ function completionOf(usage) {
917
+ return usage.outputTokens ?? usage.completionTokens;
918
+ }
919
+ /** Coerce a provider usage blob into LLMTokenUsage, or undefined. */
920
+ export function coerceTokenUsage(value) {
921
+ try {
922
+ return coerceTokenUsageInner(value);
923
+ }
924
+ catch {
925
+ return undefined;
926
+ }
927
+ }
928
+ function readUsageInt(rec, ...keys) {
929
+ for (const key of keys) {
930
+ try {
931
+ const parsed = usageInt(rec[key]);
932
+ if (parsed !== undefined)
933
+ return parsed;
934
+ }
935
+ catch {
936
+ continue;
937
+ }
938
+ }
939
+ return undefined;
940
+ }
941
+ function coerceTokenUsageInner(value) {
942
+ if (value == null)
943
+ return undefined;
944
+ if (value instanceof LLMTokenUsage) {
945
+ return hasTokenCounts(value) ? value : undefined;
946
+ }
947
+ if (typeof value !== "object" || Array.isArray(value))
948
+ return undefined;
949
+ const rec = value;
950
+ const data = {};
951
+ const input = readUsageInt(rec, "input_tokens", "inputTokens");
952
+ const output = readUsageInt(rec, "output_tokens", "outputTokens");
953
+ const prompt = readUsageInt(rec, "prompt_tokens", "promptTokens");
954
+ const completion = readUsageInt(rec, "completion_tokens", "completionTokens");
955
+ const total = readUsageInt(rec, "total_tokens", "totalTokens");
956
+ if (input !== undefined)
957
+ data.input_tokens = input;
958
+ if (output !== undefined)
959
+ data.output_tokens = output;
960
+ if (prompt !== undefined)
961
+ data.prompt_tokens = prompt;
962
+ if (completion !== undefined)
963
+ data.completion_tokens = completion;
964
+ if (total !== undefined)
965
+ data.total_tokens = total;
966
+ try {
967
+ const model = rec["model"];
968
+ if (typeof model === "string" && model)
969
+ data.model = model;
970
+ }
971
+ catch {
972
+ // ignore
973
+ }
974
+ if (data.input_tokens === undefined &&
975
+ data.output_tokens === undefined &&
976
+ data.prompt_tokens === undefined &&
977
+ data.completion_tokens === undefined &&
978
+ data.total_tokens === undefined) {
979
+ return undefined;
980
+ }
981
+ return new LLMTokenUsage(data);
982
+ }
983
+ /** Merge stream usage field-by-field; incoming non-null fields win. */
984
+ export function mergeTokenUsage(existing, incoming) {
985
+ const next = incoming instanceof LLMTokenUsage && hasTokenCounts(incoming)
986
+ ? incoming
987
+ : coerceTokenUsage(incoming);
988
+ if (next === undefined)
989
+ return existing;
990
+ if (existing === undefined)
991
+ return next;
992
+ const incomingPrompt = promptOf(next);
993
+ const incomingCompletion = completionOf(next);
994
+ const prompt = incomingPrompt ?? promptOf(existing);
995
+ const completion = incomingCompletion ?? completionOf(existing);
996
+ const model = next.model ?? existing.model;
997
+ let total = next.totalTokens ?? existing.totalTokens;
998
+ if (total === undefined && prompt !== undefined && completion !== undefined) {
999
+ total = prompt + completion;
1000
+ }
1001
+ return new LLMTokenUsage({
1002
+ input_tokens: prompt,
1003
+ output_tokens: completion,
1004
+ total_tokens: total,
1005
+ model,
1006
+ });
1007
+ }
1008
+ const JSON_SAFE_DEPTH = 8;
1009
+ const USAGE_META_KEYS = new Set(["usage", "token_usage"]);
1010
+ function usageJson(usage) {
1011
+ return usage.toJSON();
1012
+ }
1013
+ function jsonSafeValue(value, depth) {
1014
+ if (value === null || typeof value === "string" || typeof value === "boolean")
1015
+ return value;
1016
+ if (typeof value === "number")
1017
+ return Number.isFinite(value) ? value : undefined;
1018
+ if (value instanceof LLMTokenUsage)
1019
+ return usageJson(value);
1020
+ if (depth >= JSON_SAFE_DEPTH)
1021
+ return undefined;
1022
+ if (Array.isArray(value)) {
1023
+ const items = [];
1024
+ for (const item of value) {
1025
+ if (item === null) {
1026
+ items.push(null);
1027
+ continue;
1028
+ }
1029
+ const safe = jsonSafeValue(item, depth + 1);
1030
+ if (safe !== undefined)
1031
+ items.push(safe);
1032
+ }
1033
+ return items;
1034
+ }
1035
+ if (isRecord(value))
1036
+ return jsonSafeMetadata(value, depth);
1037
+ const coerced = coerceTokenUsage(value);
1038
+ return coerced !== undefined ? usageJson(coerced) : undefined;
1039
+ }
1040
+ /** Copy provider metadata into JSON-safe values, or undefined. */
1041
+ export function jsonSafeMetadata(value, depth = 0) {
1042
+ if (!isRecord(value) || Object.keys(value).length === 0)
1043
+ return undefined;
1044
+ if (depth >= JSON_SAFE_DEPTH)
1045
+ return undefined;
1046
+ const out = {};
1047
+ for (const [key, raw] of Object.entries(value)) {
1048
+ if (USAGE_META_KEYS.has(key)) {
1049
+ const coerced = coerceTokenUsage(raw);
1050
+ if (coerced !== undefined)
1051
+ out[key] = usageJson(coerced);
1052
+ continue;
1053
+ }
1054
+ if (raw === null) {
1055
+ out[key] = null;
1056
+ continue;
1057
+ }
1058
+ const safe = jsonSafeValue(raw, depth + 1);
1059
+ if (safe !== undefined)
1060
+ out[key] = safe;
1061
+ }
1062
+ return Object.keys(out).length > 0 ? out : undefined;
1063
+ }
1064
+ /** Sum LangChain-style additive usage deltas into a cumulative snapshot. */
1065
+ export function addTokenUsage(existing, incoming) {
1066
+ const next = incoming instanceof LLMTokenUsage && hasTokenCounts(incoming)
1067
+ ? incoming
1068
+ : coerceTokenUsage(incoming);
1069
+ if (next === undefined)
1070
+ return existing;
1071
+ if (existing === undefined)
1072
+ return next;
1073
+ const prompt = (promptOf(existing) ?? 0) + (promptOf(next) ?? 0);
1074
+ const completion = (completionOf(existing) ?? 0) + (completionOf(next) ?? 0);
1075
+ const model = next.model ?? existing.model;
1076
+ let total;
1077
+ if (existing.totalTokens !== undefined || next.totalTokens !== undefined) {
1078
+ total = (existing.totalTokens ?? 0) + (next.totalTokens ?? 0);
1079
+ }
1080
+ else {
1081
+ total = prompt + completion;
1082
+ }
1083
+ return new LLMTokenUsage({
1084
+ input_tokens: prompt,
1085
+ output_tokens: completion,
1086
+ total_tokens: total,
1087
+ model,
1088
+ });
1089
+ }
1090
+ function looksLikeUsageDelta(prior, incoming) {
1091
+ const pairs = [
1092
+ [promptOf(prior), promptOf(incoming)],
1093
+ [completionOf(prior), completionOf(incoming)],
1094
+ [prior.totalTokens, incoming.totalTokens],
1095
+ ];
1096
+ return pairs.some(([prev, nxt]) => prev !== undefined && nxt !== undefined && nxt < prev);
1097
+ }
1098
+ /** Fold a stream chunk's usage into a running snapshot.
1099
+ *
1100
+ * Cumulative providers repeat growing totals (`10/1` then `10/2`);
1101
+ * last-wins merge keeps `10/2`. Additive providers zero-fill the
1102
+ * unchanged side (`18/1` then `0/4`); those deltas are summed.
1103
+ */
1104
+ export function accumulateStreamUsage(existing, incoming) {
1105
+ const next = incoming instanceof LLMTokenUsage && hasTokenCounts(incoming)
1106
+ ? incoming
1107
+ : coerceTokenUsage(incoming);
1108
+ if (next === undefined)
1109
+ return existing;
1110
+ if (existing === undefined)
1111
+ return next;
1112
+ if (looksLikeUsageDelta(existing, next))
1113
+ return addTokenUsage(existing, next);
1114
+ return mergeTokenUsage(existing, next);
1115
+ }
1116
+ /** Runtime validation schema for LLMResult wire data. `content` is the only required field. */
1117
+ export const LLMResultSchema = z.looseObject({
1118
+ content: z.string().default(""),
1119
+ tool_calls: z.array(z.unknown()).default([]),
1120
+ usage: LLMTokenUsageSchema.optional(),
1121
+ metadata: z.record(z.string(), z.unknown()).default({}),
1122
+ id: z.string().optional(),
1123
+ name: z.string().optional(),
1124
+ additional_kwargs: z.record(z.string(), JsonValueSchema).optional(),
1125
+ response_metadata: z.record(z.string(), JsonValueSchema).optional(),
1126
+ });
1127
+ /**
1128
+ * Response from LLM invocation on a tool executor pod.
1129
+ *
1130
+ * Python `_sync_usage_with_result` is a model_validator that mutates
1131
+ * `result.usage` ↔ `usage`. TS `LLMResponse.usage` is readonly, so
1132
+ * mutation isn't possible; callers should use
1133
+ * `normalizeLLMPodInvokeResponse()` after parsing to sync the two fields
1134
+ * by constructing a fresh LLMResponse when needed.
1135
+ */
1136
+ export const LLMPodInvokeResponseSchema = z.object({
1137
+ /** Execution status: success, error. */
1138
+ status: z.string(),
1139
+ /** LLM result with content, tool_calls, and usage. */
1140
+ result: LLMResponseSchema.optional(),
1141
+ /** Error message if failed. */
1142
+ error: z.string().optional(),
1143
+ /** Machine-readable failure classification (e.g. llm_credential_rejected). */
1144
+ error_code: z.string().optional(),
1145
+ /** Hostname/pod name where LLM was executed. */
1146
+ pod_name: z.string(),
1147
+ /** Execution duration in milliseconds. */
1148
+ duration_ms: z.number(),
1149
+ /** Token usage (input_tokens, output_tokens, total_tokens). */
1150
+ usage: LLMTokenUsageSchema.optional(),
1151
+ });
1152
+ /**
1153
+ * Sync `usage` and `result.usage` on a parsed LLMPodInvokeResponse,
1154
+ * mirroring Python's `_sync_usage_with_result` model_validator.
1155
+ *
1156
+ * - If `result.usage` is unset and `usage` is present, returns a new
1157
+ * response whose result carries the top-level usage.
1158
+ * - If `usage` is unset and `result.usage` is present, lifts that
1159
+ * `result.usage` to the top level.
1160
+ *
1161
+ * Returns the response (possibly with a freshly-built `result`/`usage`).
1162
+ */
1163
+ export function normalizeLLMPodInvokeResponse(resp) {
1164
+ const resultObj = resp.result;
1165
+ const resultHasUsage = resultObj !== undefined &&
1166
+ isRecord(resultObj["usage"]) &&
1167
+ resultObj["usage"] !== null;
1168
+ let nextResult = resp.result;
1169
+ let nextUsage = resp.usage;
1170
+ if (resultObj !== undefined && !resultHasUsage && resp.usage !== undefined) {
1171
+ nextResult = {
1172
+ ...resultObj,
1173
+ usage: resp.usage,
1174
+ };
1175
+ }
1176
+ if (resp.usage === undefined && resultObj !== undefined && resultHasUsage) {
1177
+ nextUsage = resultObj["usage"];
1178
+ }
1179
+ return { ...resp, result: nextResult, usage: nextUsage };
1180
+ }
1181
+ /** SSE event emitted by a tool pod during invoke_llm streaming. */
1182
+ export const LLMPodStreamEventSchema = z.object({
1183
+ /** Generated text delta. */
1184
+ content: z.string().optional(),
1185
+ /** Partial tool-call deltas. */
1186
+ tool_call_chunks: z.array(ToolCallChunkSchema).optional(),
1187
+ /** Complete tool calls for compatibility with older stream senders. */
1188
+ tool_calls: z.array(z.record(z.string(), z.unknown())).optional(),
1189
+ /** Provider message identifier. */
1190
+ id: z.string().optional(),
1191
+ /** Provider message name. */
1192
+ name: z.string().optional(),
1193
+ /** LangChain additional message kwargs. */
1194
+ additional_kwargs: z.record(z.string(), JsonValueSchema).optional(),
1195
+ /** LangChain response metadata. */
1196
+ response_metadata: z.record(z.string(), JsonValueSchema).optional(),
1197
+ /** Whether the stream is complete. */
1198
+ done: z.boolean().optional(),
1199
+ /** OE stopped this call on interrupt request; terminal, distinct from done/error. */
1200
+ interrupted: z.boolean().optional(),
1201
+ /** Error message if streaming failed. */
1202
+ error: z.string().optional(),
1203
+ /** Machine-readable failure classification (e.g. llm_credential_rejected). */
1204
+ error_code: z.string().optional(),
1205
+ /** When true with error, the same stream URL may be retried. */
1206
+ retryable: z.boolean().optional(),
1207
+ /** When set with retryable, wait this many milliseconds before retrying. */
1208
+ retry_after_ms: z.number().optional(),
1209
+ /** Hostname/pod name. */
1210
+ pod_name: z.string().optional(),
1211
+ /** Stream duration. */
1212
+ duration_ms: z.number().optional(),
1213
+ /** Final token usage. */
1214
+ usage: LLMTokenUsageSchema.optional(),
1215
+ });
1216
+ // =============================================================================
1217
+ // Executor Callback (AER → OE)
1218
+ // =============================================================================
1219
+ /** Callback from AER to OE when execution completes or suspends. */
1220
+ export const ExecutorCallbackRequestSchema = z.object({
1221
+ /** Execution identifier. */
1222
+ execution_id: z.string(),
1223
+ /** Status: COMPLETED, SUSPENDED, ERROR. */
1224
+ status: z.string(),
1225
+ /** Suspension generation expected by the originating dispatch. */
1226
+ suspend_generation: z
1227
+ .number()
1228
+ .int()
1229
+ .nonnegative()
1230
+ .nullable()
1231
+ .transform((value) => value ?? undefined)
1232
+ .optional(),
1233
+ /** Final result if completed. */
1234
+ result: z.unknown().optional(),
1235
+ /** Error message if failed. */
1236
+ error: z.string().optional(),
1237
+ /** Reason for suspension. */
1238
+ suspend_reason: z.string().optional(),
1239
+ suspend_context: z.record(z.string(), z.unknown()).optional(),
1240
+ interrupts: z.array(PendingInterruptSchema).optional(),
1241
+ resume_schema: z.record(z.string(), z.unknown()).optional(),
1242
+ /** Opaque framework-owned state returned by the selected adapter. */
1243
+ metadata: z.record(z.string(), z.unknown()).optional(),
1244
+ });
1245
+ // =============================================================================
1246
+ // Resume (Client → OE)
1247
+ // =============================================================================
1248
+ /** Data provided by human reviewer when resuming a suspended execution. */
1249
+ export const HumanReviewDataSchema = z.object({
1250
+ /** Review decision: 'approved', 'rejected', or custom value. */
1251
+ decision: z.string(),
1252
+ /** Optional notes from the reviewer. */
1253
+ reviewer_notes: z.string().optional(),
1254
+ });
1255
+ /** Request to resume a suspended execution. */
1256
+ export const AgentResumeRequestSchema = z.object({
1257
+ /** Human review decision data (for human-in-the-loop resume). */
1258
+ human_review: HumanReviewDataSchema,
1259
+ /** Caller-provided custom headers. */
1260
+ custom_headers: z.record(z.string(), z.string()).optional(),
1261
+ });
1262
+ /** Response from resuming an execution. */
1263
+ export const AgentResumeResponseSchema = z.object({
1264
+ /** Execution identifier. */
1265
+ execution_id: z.string(),
1266
+ /** Execution status. */
1267
+ status: z.string(),
1268
+ });
1269
+ // =============================================================================
1270
+ // Execution Status Query
1271
+ // =============================================================================
1272
+ /** Response for execution status query. */
1273
+ export const ExecutionStatusResponseSchema = z.object({
1274
+ /** Execution identifier. */
1275
+ execution_id: z.string(),
1276
+ /** Current status. */
1277
+ status: ExecutionStatusSchema,
1278
+ /** Result if completed. */
1279
+ result: z.unknown().optional(),
1280
+ /** Error if failed. */
1281
+ error: z.string().optional(),
1282
+ /** Reason if suspended. */
1283
+ suspend_reason: z.string().optional(),
1284
+ /** Context if suspended. */
1285
+ suspend_context: z.record(z.string(), z.unknown()).optional(),
1286
+ /** Creation timestamp. ISO strings are coerced to Date, matching Pydantic. */
1287
+ created_at: z.coerce.date(),
1288
+ /** Last update timestamp. ISO strings are coerced to Date, matching Pydantic. */
1289
+ updated_at: z.coerce.date(),
1290
+ });
1291
+ // =============================================================================
1292
+ // Database Models
1293
+ // =============================================================================
1294
+ /** Execution record stored in TenantDB. */
1295
+ export const ExecutionSchema = z.object({
1296
+ /** Unique execution identifier. */
1297
+ id: z.string(),
1298
+ /** Current status. */
1299
+ status: ExecutionStatusSchema,
1300
+ /** Original user message. */
1301
+ message: z.string(),
1302
+ /** Final result. */
1303
+ result: z.unknown().optional(),
1304
+ /** Error message. */
1305
+ error: z.string().optional(),
1306
+ /** Reason for suspension. */
1307
+ suspend_reason: z.string().optional(),
1308
+ /** Context for resume. */
1309
+ suspend_context: z.record(z.string(), z.unknown()).optional(),
1310
+ // Multi-tenant context
1311
+ /** Session ID for trace correlation. */
1312
+ session_id: z.string().optional(),
1313
+ /** Organization ID for multi-tenant isolation. */
1314
+ org_id: z.string().optional(),
1315
+ /** User ID for personalization. */
1316
+ user_id: z.string().optional(),
1317
+ /** Workspace identifier for cost tracking. */
1318
+ workspace_id: z.string().optional(),
1319
+ /** Project ID for project-level scoping. */
1320
+ project_id: z.string().optional(),
1321
+ // Per-agent AER routing
1322
+ /** AER HTTP endpoint for this agent; overrides default AER_URL. */
1323
+ aer_url: z.string().optional(),
1324
+ /** Tool HTTP endpoint for this agent; overrides default TOOL_URL. */
1325
+ tool_url: z.string().optional(),
1326
+ // Timestamps. ISO strings are coerced to Date, matching Pydantic and the
1327
+ // other datetime fields in this file (ExecutionStatusResponse, NodeExecutionRequest).
1328
+ created_at: z.coerce.date().default(() => new Date()),
1329
+ updated_at: z.coerce.date().default(() => new Date()),
1330
+ });
1331
+ /**
1332
+ * Normalize a value (Date, ISO string, or null/undefined) to a UTC Date.
1333
+ *
1334
+ * MongoDB stores naive datetimes in UTC; this helper rehydrates them as
1335
+ * proper Date instances so callers don't mix string and Date types.
1336
+ *
1337
+ * Mirrors Python `Execution._ensure_utc`. JS `Date` has no naive/aware
1338
+ * distinction (every Date is internally UTC ms-since-epoch), so the TS
1339
+ * version exercises the same "normalize input to a canonical Date" intent
1340
+ * by coercing ISO strings and passing Date instances through unchanged.
1341
+ */
1342
+ export function ensureUtc(value) {
1343
+ if (value === null || value === undefined)
1344
+ return undefined;
1345
+ if (value instanceof Date)
1346
+ return value;
1347
+ if (typeof value === "string") {
1348
+ const d = new Date(value);
1349
+ return Number.isNaN(d.getTime()) ? undefined : d;
1350
+ }
1351
+ return undefined;
1352
+ }
1353
+ /**
1354
+ * Convert an Execution to a MongoDB persistence document.
1355
+ *
1356
+ * `updatedAt` defaults to now if omitted.
1357
+ */
1358
+ export function executionToPersistenceDoc(exec, updatedAt) {
1359
+ return {
1360
+ execution_id: exec.id,
1361
+ status: exec.status,
1362
+ message: exec.message,
1363
+ session_id: exec.session_id,
1364
+ user_id: exec.user_id,
1365
+ org_id: exec.org_id,
1366
+ workspace_id: exec.workspace_id,
1367
+ project_id: exec.project_id,
1368
+ result: exec.result,
1369
+ error: exec.error,
1370
+ suspend_reason: exec.suspend_reason,
1371
+ suspend_context: exec.suspend_context,
1372
+ updated_at: updatedAt ?? new Date(),
1373
+ };
1374
+ }
1375
+ /**
1376
+ * Rehydrate an Execution from a MongoDB document.
1377
+ *
1378
+ * Uses default values for Optional fields so documents created before
1379
+ * new fields were added still deserialize safely.
1380
+ */
1381
+ export function executionFromPersistenceDoc(doc) {
1382
+ const now = new Date();
1383
+ return ExecutionSchema.parse({
1384
+ id: doc["execution_id"],
1385
+ status: doc["status"],
1386
+ message: doc["message"] ?? "",
1387
+ result: doc["result"],
1388
+ error: doc["error"],
1389
+ suspend_reason: doc["suspend_reason"],
1390
+ suspend_context: doc["suspend_context"],
1391
+ session_id: doc["session_id"],
1392
+ org_id: doc["org_id"],
1393
+ user_id: doc["user_id"],
1394
+ workspace_id: doc["workspace_id"],
1395
+ project_id: doc["project_id"],
1396
+ created_at: ensureUtc(doc["created_at"]) ?? ensureUtc(doc["updated_at"]) ?? now,
1397
+ updated_at: ensureUtc(doc["updated_at"]) ?? now,
1398
+ });
1399
+ }
1400
+ /** Execution step record stored in TenantDB. */
1401
+ export const ExecutionStepSchema = z.object({
1402
+ /** Unique step identifier. */
1403
+ id: z.string(),
1404
+ /** Parent execution ID. */
1405
+ execution_id: z.string(),
1406
+ /** Step sequence number. */
1407
+ step_number: z.number().int(),
1408
+ /** Tool or operation name. */
1409
+ tool_name: z.string(),
1410
+ /** Arguments passed. */
1411
+ arguments: z.record(z.string(), z.unknown()),
1412
+ /** Step status: pending, success, error. */
1413
+ status: z.string(),
1414
+ /** Step result. */
1415
+ result: z.unknown().optional(),
1416
+ /** Error message. */
1417
+ error: z.string().optional(),
1418
+ /** Execution duration. */
1419
+ duration_ms: z.number().optional(),
1420
+ /** ISO strings are coerced to Date, matching Pydantic. */
1421
+ timestamp: z.coerce.date().default(() => new Date()),
1422
+ });
1423
+ /**
1424
+ * Reconstruct an ExecutionStep from an execution_logs MongoDB document.
1425
+ *
1426
+ * The execution_logs collection stores tool start/result events with
1427
+ * fields: execution_id, step_number, tool, inputs, status, output,
1428
+ * error, duration_ms, timestamp. This maps those fields back to the
1429
+ * ExecutionStep model for step-cache rehydration after OE restart.
1430
+ */
1431
+ export function executionStepFromLogDoc(doc) {
1432
+ return ExecutionStepSchema.parse({
1433
+ id: doc["id"] ?? crypto.randomUUID(),
1434
+ execution_id: doc["execution_id"],
1435
+ step_number: doc["step_number"] ?? 0,
1436
+ tool_name: doc["tool"] ?? "",
1437
+ arguments: doc["inputs"] ?? {},
1438
+ status: doc["status"] ?? "error",
1439
+ result: doc["output"],
1440
+ error: doc["error"],
1441
+ duration_ms: doc["duration_ms"],
1442
+ timestamp: ensureUtc(doc["timestamp"]) ?? new Date(),
1443
+ });
1444
+ }
1445
+ // =============================================================================
1446
+ // Health Check
1447
+ // =============================================================================
1448
+ /** Health status of a component. */
1449
+ export const HealthStatusSchema = z.enum(["healthy", "unhealthy", "degraded"]);
1450
+ export const HealthStatus = {
1451
+ HEALTHY: "healthy",
1452
+ UNHEALTHY: "unhealthy",
1453
+ DEGRADED: "degraded",
1454
+ };
1455
+ /** Health check response. */
1456
+ export const HealthResponseSchema = z.object({
1457
+ /** Health status. */
1458
+ status: HealthStatusSchema,
1459
+ /** Component name. */
1460
+ component: z.string(),
1461
+ /** Runtime mode. */
1462
+ mode: z.string(),
1463
+ /** Component version. */
1464
+ version: z.string().default("1.0.0"),
1465
+ /** Additional details. */
1466
+ details: z.record(z.string(), z.unknown()).default({}),
1467
+ });
1468
+ // =============================================================================
1469
+ // Tool Definition
1470
+ // =============================================================================
1471
+ /** Definition of a registered tool. */
1472
+ export const ToolDefinitionSchema = z.object({
1473
+ /** Tool name. */
1474
+ name: z.string(),
1475
+ /** Tool description. */
1476
+ description: z.string().default(""),
1477
+ /** Whether tool runs locally. */
1478
+ is_local: z.boolean().default(true),
1479
+ /** Credential provider type for delegated auth. */
1480
+ provider_type: z.string().nullish(),
1481
+ /** Requested OAuth scopes for delegated auth. */
1482
+ scopes: z.array(z.string()).default([]),
1483
+ /** JSON schema for parameters. */
1484
+ parameters: z.record(z.string(), z.unknown()).optional(),
1485
+ });
1486
+ // =============================================================================
1487
+ // AER Route Response Models
1488
+ // =============================================================================
1489
+ /**
1490
+ * Response from AER /execute endpoint.
1491
+ *
1492
+ * Returned on both normal completion and HITL suspension.
1493
+ */
1494
+ export const AERExecuteResponseSchema = z.object({
1495
+ /** Execution outcome: 'completed' or 'suspended'. */
1496
+ status: z.string(),
1497
+ /** Final agent response text (present when status is 'completed'). */
1498
+ result: z.string().optional(),
1499
+ /** Why the agent suspended (present when status is 'suspended'). */
1500
+ suspend_reason: z.string().optional(),
1501
+ });
1502
+ /** Response from /tools endpoint listing registered tools. */
1503
+ export const ToolsListResponseSchema = z.object({
1504
+ /** Registered tool definitions with name and metadata. */
1505
+ tools: z.array(z.record(z.string(), z.unknown())),
1506
+ /** Number of registered tools (included by Tool Pod). */
1507
+ count: z.number().int().optional(),
1508
+ });
1509
+ // =============================================================================
1510
+ // Streaming
1511
+ // =============================================================================
1512
+ /**
1513
+ * A chunk of streaming response from the agent.
1514
+ *
1515
+ * Used for real-time streaming of agent responses via SSE or gRPC.
1516
+ */
1517
+ export const StreamChunkSchema = z.object({
1518
+ /**
1519
+ * Chunk type string. AER-emitted values are defined in
1520
+ * `server/chunk_types` ('text', 'done', 'error'). OE may inject
1521
+ * additional infrastructure types (e.g. 'metadata', 'tool_call',
1522
+ * 'tool_result') before forwarding to clients.
1523
+ */
1524
+ chunk_type: z.string(),
1525
+ /** Content of the chunk. */
1526
+ content: z.string().default(""),
1527
+ /** Optional metadata. */
1528
+ metadata: z.record(z.string(), z.string()).default({}),
1529
+ /** Error message if chunk_type is 'error'. */
1530
+ error: z.string().optional(),
1531
+ /** Machine-readable error code if chunk_type is 'error' (e.g. AGENT_UNAVAILABLE). */
1532
+ code: z.string().optional(),
1533
+ // Tool-specific fields
1534
+ /** Tool name for tool_call/tool_result chunks. */
1535
+ tool_name: z.string().optional(),
1536
+ /** Tool call ID. */
1537
+ tool_call_id: z.string().optional(),
1538
+ // Execution context
1539
+ /** Execution ID. */
1540
+ execution_id: z.string().optional(),
1541
+ /** Step number in execution. */
1542
+ step_number: z.number().int().optional(),
1543
+ });
1544
+ /** Request to start an agent execution with streaming response. */
1545
+ export const AgentStartStreamRequestSchema = z.object({
1546
+ /** User message. */
1547
+ message: z.string(),
1548
+ /** Session ID for conversation continuity. */
1549
+ session_id: z.string().optional(),
1550
+ /** Organization ID for multi-tenant isolation. */
1551
+ org_id: z.string().optional(),
1552
+ /** User ID for personalization. */
1553
+ user_id: z.string().optional(),
1554
+ });
1555
+ // =============================================================================
1556
+ // Query Response Models — Execution Logs & Node Executions
1557
+ // =============================================================================
1558
+ /** Response for execution logs query (used by API Gateway proxy). */
1559
+ export const ExecutionLogsQueryResponseSchema = z.object({
1560
+ logs: z.array(z.record(z.string(), z.unknown())).default([]),
1561
+ count: z.number().int().default(0),
1562
+ });
1563
+ /** Response for node executions query (used by API Gateway proxy). */
1564
+ export const NodeExecutionsQueryResponseSchema = z.object({
1565
+ executions: z.array(z.record(z.string(), z.unknown())).default([]),
1566
+ count: z.number().int().default(0),
1567
+ });
1568
+ // =============================================================================
1569
+ // Query Response Models — Sessions
1570
+ // =============================================================================
1571
+ /** A single session entry returned by /query/sessions. */
1572
+ export const SessionInfoSchema = z.object({
1573
+ session_id: z.string(),
1574
+ last_activity: z.string(),
1575
+ created_at: z.string(),
1576
+ message_count: z.number().int().default(0),
1577
+ last_message_preview: z.string().default(""),
1578
+ visibility: z.string().default("PRIVATE"),
1579
+ user_id: z.string().default(""),
1580
+ project_id: z.string().default(""),
1581
+ workspace_id: z.string().default(""),
1582
+ });
1583
+ /** Response for sessions list query (used by API Gateway proxy). */
1584
+ export const SessionsQueryResponseSchema = z.object({
1585
+ sessions: z.array(SessionInfoSchema).default([]),
1586
+ total_count: z.number().int().default(0),
1587
+ offset: z.number().int().default(0),
1588
+ limit: z.number().int().default(50),
1589
+ });
1590
+ /** A single message within a session. */
1591
+ export const SessionMessageSchema = z.object({
1592
+ id: z.string(),
1593
+ role: z.string(),
1594
+ content: z.string(),
1595
+ timestamp: z.string(),
1596
+ session_id: z.string(),
1597
+ name: z.string().optional(),
1598
+ tool_calls: z.array(LLMToolCallSchema).nullable().optional(),
1599
+ tool_call_id: z.string().nullable().optional(),
1600
+ });
1601
+ /** Response for session messages query (used by API Gateway proxy). */
1602
+ export const SessionMessagesQueryResponseSchema = z.object({
1603
+ messages: z.array(SessionMessageSchema).default([]),
1604
+ });
1605
+ // =============================================================================
1606
+ // Cost Dashboard Query Response Models
1607
+ // =============================================================================
1608
+ /** Aggregate cost metrics for the requested period. */
1609
+ export const CostSummarySchema = z.object({
1610
+ total_cost_usd: z.number().default(0.0),
1611
+ total_tokens: z.number().int().default(0),
1612
+ total_prompt_tokens: z.number().int().default(0),
1613
+ total_completion_tokens: z.number().int().default(0),
1614
+ total_llm_calls: z.number().int().default(0),
1615
+ unpriced_llm_calls: z.number().int().default(0),
1616
+ });
1617
+ /** Cost breakdown for a single workspace. */
1618
+ export const CostByWorkspaceSchema = z.object({
1619
+ workspace_id: z.string(),
1620
+ total_cost_usd: z.number().default(0.0),
1621
+ total_tokens: z.number().int().default(0),
1622
+ call_count: z.number().int().default(0),
1623
+ percentage: z.number().default(0.0),
1624
+ });
1625
+ /** Cost breakdown for a single model. */
1626
+ export const CostByModelSchema = z.object({
1627
+ model: z.string(),
1628
+ total_cost_usd: z.number().default(0.0),
1629
+ total_tokens: z.number().int().default(0),
1630
+ call_count: z.number().int().default(0),
1631
+ percentage: z.number().default(0.0),
1632
+ });
1633
+ /** Cost data for a single day. */
1634
+ export const DailyCostEntrySchema = z.object({
1635
+ date: z.string(),
1636
+ total_cost_usd: z.number().default(0.0),
1637
+ total_tokens: z.number().int().default(0),
1638
+ call_count: z.number().int().default(0),
1639
+ });
1640
+ /** Response for cost dashboard aggregation (used by API Gateway proxy). */
1641
+ export const CostDashboardResponseSchema = z.object({
1642
+ summary: CostSummarySchema.default({
1643
+ total_cost_usd: 0.0,
1644
+ total_tokens: 0,
1645
+ total_prompt_tokens: 0,
1646
+ total_completion_tokens: 0,
1647
+ total_llm_calls: 0,
1648
+ unpriced_llm_calls: 0,
1649
+ }),
1650
+ by_workspace: z.array(CostByWorkspaceSchema).default([]),
1651
+ by_model: z.array(CostByModelSchema).default([]),
1652
+ daily_trend: z.array(DailyCostEntrySchema).default([]),
1653
+ });
1654
+ // =============================================================================
1655
+ // Executions Query Response Models
1656
+ // =============================================================================
1657
+ /** An execution document as stored in the platform database. */
1658
+ export const ExecutionDocumentSchema = z.object({
1659
+ execution_id: z.string(),
1660
+ status: z.string().default(""),
1661
+ message: z.string().default(""),
1662
+ session_id: z.string().default(""),
1663
+ user_id: z.string().default(""),
1664
+ org_id: z.string().default(""),
1665
+ project_id: z.string().nullable().default(""),
1666
+ workspace_id: z.string().nullable().default(""),
1667
+ result: z.unknown().optional(),
1668
+ error: z.string().optional(),
1669
+ suspend_reason: z.string().optional(),
1670
+ suspend_context: z.record(z.string(), z.unknown()).optional(),
1671
+ created_at: z.string().optional(),
1672
+ updated_at: z.string().optional(),
1673
+ });
1674
+ /** Response for executions list query (used by API Gateway proxy). */
1675
+ export const ExecutionsListQueryResponseSchema = z.object({
1676
+ success: z.boolean().default(true),
1677
+ executions: z.array(ExecutionDocumentSchema).default([]),
1678
+ count: z.number().int().default(0),
1679
+ });
1680
+ /** Response for single execution detail query (used by API Gateway proxy). */
1681
+ export const ExecutionDetailQueryResponseSchema = z.object({
1682
+ success: z.boolean().default(true),
1683
+ execution: ExecutionDocumentSchema.optional(),
1684
+ error: z.string().optional(),
1685
+ });
1686
+ // =============================================================================
1687
+ // Node Execution (AER → OE)
1688
+ // =============================================================================
1689
+ /**
1690
+ * Report node execution event (AER → OE for logging).
1691
+ */
1692
+ export const NodeExecutionRequestSchema = z.object({
1693
+ /** Execution identifier. */
1694
+ execution_id: z.string(),
1695
+ /** Name of the framework node. */
1696
+ node_name: z.string(),
1697
+ /** Node status: started, success, error, suspend. */
1698
+ status: z.string(),
1699
+ /** Event timestamp. ISO strings are coerced to Date, matching Pydantic. */
1700
+ timestamp: z.coerce.date(),
1701
+ /** Framework run ID for this node execution. */
1702
+ run_id: z.string(),
1703
+ /** Parent run ID if nested. */
1704
+ parent_run_id: z.string().optional(),
1705
+ /** Session ID for correlation. */
1706
+ session_id: z.string().optional(),
1707
+ /** User ID for personalization. */
1708
+ user_id: z.string().optional(),
1709
+ /** Node inputs (for started status). */
1710
+ inputs: z.record(z.string(), z.unknown()).optional(),
1711
+ /** Node outputs (for success status). */
1712
+ outputs: z.record(z.string(), z.unknown()).optional(),
1713
+ /** Error message (for error status). */
1714
+ error: z.string().optional(),
1715
+ /** Execution duration in milliseconds. */
1716
+ duration_ms: z.number().optional(),
1717
+ /** Organization ID. */
1718
+ org_id: z.string().optional(),
1719
+ /** Project ID. */
1720
+ project_id: z.string().optional(),
1721
+ /** Active OTel trace ID, for OE execution-log correlation. */
1722
+ trace_id: z.string().nullish(),
1723
+ /** Active OTel span ID, for OE execution-log correlation. */
1724
+ span_id: z.string().nullish(),
1725
+ });
1726
+ export { LLMResponse, LLMTokenUsage, LLMToolCall, LLMToolSchema, LLMInvocationOptions, };