@openbox-ai/openbox-mastra-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.
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/openbox-client.d.ts +42 -0
- package/dist/client/openbox-client.js +405 -0
- package/dist/client/openbox-client.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/openbox-config.d.ts +54 -0
- package/dist/config/openbox-config.js +162 -0
- package/dist/config/openbox-config.js.map +1 -0
- package/dist/governance/activity-runtime.d.ts +42 -0
- package/dist/governance/activity-runtime.js +712 -0
- package/dist/governance/activity-runtime.js.map +1 -0
- package/dist/governance/approval-registry.d.ts +17 -0
- package/dist/governance/approval-registry.js +32 -0
- package/dist/governance/approval-registry.js.map +1 -0
- package/dist/governance/context.d.ts +16 -0
- package/dist/governance/context.js +13 -0
- package/dist/governance/context.js.map +1 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mastra/index.d.ts +16 -0
- package/dist/mastra/index.js +5 -0
- package/dist/mastra/index.js.map +1 -0
- package/dist/mastra/with-openbox.d.ts +30 -0
- package/dist/mastra/with-openbox.js +243 -0
- package/dist/mastra/with-openbox.js.map +1 -0
- package/dist/mastra/wrap-agent.d.ts +14 -0
- package/dist/mastra/wrap-agent.js +1744 -0
- package/dist/mastra/wrap-agent.js.map +1 -0
- package/dist/mastra/wrap-tool.d.ts +18 -0
- package/dist/mastra/wrap-tool.js +49 -0
- package/dist/mastra/wrap-tool.js.map +1 -0
- package/dist/mastra/wrap-workflow.d.ts +14 -0
- package/dist/mastra/wrap-workflow.js +386 -0
- package/dist/mastra/wrap-workflow.js.map +1 -0
- package/dist/otel/index.d.ts +11 -0
- package/dist/otel/index.js +2 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/otel/setup-openbox-opentelemetry.d.ts +38 -0
- package/dist/otel/setup-openbox-opentelemetry.js +2249 -0
- package/dist/otel/setup-openbox-opentelemetry.js.map +1 -0
- package/dist/span/index.d.ts +5 -0
- package/dist/span/index.js +2 -0
- package/dist/span/index.js.map +1 -0
- package/dist/span/openbox-span-processor.d.ts +90 -0
- package/dist/span/openbox-span-processor.js +580 -0
- package/dist/span/openbox-span-processor.js.map +1 -0
- package/dist/types/errors.d.ts +25 -0
- package/dist/types/errors.js +40 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/governance-verdict-response.d.ts +57 -0
- package/dist/types/governance-verdict-response.js +84 -0
- package/dist/types/governance-verdict-response.js.map +1 -0
- package/dist/types/guardrails.d.ts +23 -0
- package/dist/types/guardrails.js +27 -0
- package/dist/types/guardrails.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/verdict.d.ts +22 -0
- package/dist/types/verdict.js +55 -0
- package/dist/types/verdict.js.map +1 -0
- package/dist/types/workflow-event-type.d.ts +10 -0
- package/dist/types/workflow-event-type.js +13 -0
- package/dist/types/workflow-event-type.js.map +1 -0
- package/dist/types/workflow-span-buffer.d.ts +31 -0
- package/dist/types/workflow-span-buffer.js +42 -0
- package/dist/types/workflow-span-buffer.js.map +1 -0
- package/docs/README.md +66 -0
- package/docs/api-reference.md +348 -0
- package/docs/approvals-and-guardrails.md +163 -0
- package/docs/architecture.md +186 -0
- package/docs/configuration.md +214 -0
- package/docs/event-model.md +215 -0
- package/docs/installation.md +108 -0
- package/docs/integration-patterns.md +214 -0
- package/docs/security-and-privacy.md +174 -0
- package/docs/telemetry.md +196 -0
- package/docs/troubleshooting.md +210 -0
- package/package.json +136 -0
|
@@ -0,0 +1,1744 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { trace } from "@opentelemetry/api";
|
|
3
|
+
import {
|
|
4
|
+
clearPendingApproval,
|
|
5
|
+
getPendingApproval,
|
|
6
|
+
markActivityApproved
|
|
7
|
+
} from "../governance/approval-registry.js";
|
|
8
|
+
import {
|
|
9
|
+
normalizeSpansForGovernance,
|
|
10
|
+
serializeValue
|
|
11
|
+
} from "../governance/activity-runtime.js";
|
|
12
|
+
import { runWithOpenBoxExecutionContext } from "../governance/context.js";
|
|
13
|
+
import {
|
|
14
|
+
ApprovalExpiredError,
|
|
15
|
+
ApprovalPendingError,
|
|
16
|
+
ApprovalRejectedError,
|
|
17
|
+
GovernanceAPIError,
|
|
18
|
+
GovernanceHaltError,
|
|
19
|
+
Verdict,
|
|
20
|
+
WorkflowEventType,
|
|
21
|
+
WorkflowSpanBuffer
|
|
22
|
+
} from "../types/index.js";
|
|
23
|
+
const OPENBOX_WRAPPED_AGENT = /* @__PURE__ */ Symbol.for("openbox.mastra.wrapAgent");
|
|
24
|
+
const OPENBOX_AGENT_STREAM_META = /* @__PURE__ */ Symbol.for("openbox.mastra.wrapAgent.streamMeta");
|
|
25
|
+
const AGENT_INPUT_SIGNAL_NAME = "user_input";
|
|
26
|
+
const AGENT_OUTPUT_SIGNAL_NAME = "agent_output";
|
|
27
|
+
const OPENBOX_AGENT_RUN_GOALS = /* @__PURE__ */ new Map();
|
|
28
|
+
const OPENBOX_AGENT_SIGNAL_SPAN_CURSOR = /* @__PURE__ */ new Map();
|
|
29
|
+
const MAX_AGENT_OUTPUT_SIGNAL_SPANS = 8;
|
|
30
|
+
const MAX_AGENT_SIGNAL_SPAN_BODY_CHARS = 12e3;
|
|
31
|
+
async function resolveAgentGoal(baseAgent, executionOptions = {}, interactionPayload) {
|
|
32
|
+
const configuredGoal = normalizeGoalCandidate(process.env.OPENBOX_AGENT_GOAL);
|
|
33
|
+
if (configuredGoal) {
|
|
34
|
+
return configuredGoal;
|
|
35
|
+
}
|
|
36
|
+
const runIdCandidate = executionOptions && typeof executionOptions === "object" && "runId" in executionOptions ? executionOptions.runId : void 0;
|
|
37
|
+
const runId = typeof runIdCandidate === "string" && runIdCandidate.trim().length > 0 ? runIdCandidate : void 0;
|
|
38
|
+
const persistedGoal = runId ? OPENBOX_AGENT_RUN_GOALS.get(runId) : void 0;
|
|
39
|
+
if (persistedGoal) {
|
|
40
|
+
return persistedGoal;
|
|
41
|
+
}
|
|
42
|
+
const interactionGoal = normalizeGoalCandidate(
|
|
43
|
+
extractGoalCandidateFromInteraction(interactionPayload)
|
|
44
|
+
);
|
|
45
|
+
if (interactionGoal) {
|
|
46
|
+
return interactionGoal;
|
|
47
|
+
}
|
|
48
|
+
const getInstructions = baseAgent.getInstructions;
|
|
49
|
+
if (typeof getInstructions !== "function") {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const requestContext = executionOptions && typeof executionOptions === "object" && "requestContext" in executionOptions ? executionOptions.requestContext : void 0;
|
|
54
|
+
const instructions = await Promise.resolve(
|
|
55
|
+
requestContext !== void 0 ? getInstructions.call(baseAgent, { requestContext }) : getInstructions.call(baseAgent)
|
|
56
|
+
);
|
|
57
|
+
return normalizeGoalCandidate(instructions);
|
|
58
|
+
} catch {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function normalizeGoalCandidate(value) {
|
|
63
|
+
const text = extractTextFromStructuredValue(value);
|
|
64
|
+
if (!text) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
const normalized = text.trim();
|
|
68
|
+
if (normalized.length === 0) {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
return truncateString(normalized, 1e3);
|
|
72
|
+
}
|
|
73
|
+
function extractGoalCandidateFromInteraction(value) {
|
|
74
|
+
const latestUserPrompt = extractLatestUserPrompt(value);
|
|
75
|
+
if (latestUserPrompt) {
|
|
76
|
+
return latestUserPrompt;
|
|
77
|
+
}
|
|
78
|
+
return extractTextFromStructuredValue(value);
|
|
79
|
+
}
|
|
80
|
+
function wrapAgent(agent, options) {
|
|
81
|
+
const baseAgent = agent;
|
|
82
|
+
if (baseAgent[OPENBOX_WRAPPED_AGENT]) {
|
|
83
|
+
return agent;
|
|
84
|
+
}
|
|
85
|
+
const workflowType = String(baseAgent.id ?? baseAgent.name ?? "agent");
|
|
86
|
+
const workflowId = `agent:${workflowType}`;
|
|
87
|
+
const originalGenerate = baseAgent.generate?.bind(baseAgent);
|
|
88
|
+
const originalStream = baseAgent.stream?.bind(baseAgent);
|
|
89
|
+
const originalResumeGenerate = baseAgent.resumeGenerate?.bind(baseAgent);
|
|
90
|
+
const originalResumeStream = baseAgent.resumeStream?.bind(baseAgent);
|
|
91
|
+
if (originalGenerate) {
|
|
92
|
+
baseAgent.generate = async (messages, executionOptions = {}) => {
|
|
93
|
+
const runId = String(executionOptions.runId ?? randomUUID());
|
|
94
|
+
const agentGoal = await resolveAgentGoal(
|
|
95
|
+
baseAgent,
|
|
96
|
+
executionOptions,
|
|
97
|
+
messages
|
|
98
|
+
);
|
|
99
|
+
const nextOptions = {
|
|
100
|
+
...executionOptions,
|
|
101
|
+
runId
|
|
102
|
+
};
|
|
103
|
+
const invocationModelInfo = resolveInvocationModelInfo(
|
|
104
|
+
baseAgent,
|
|
105
|
+
executionOptions
|
|
106
|
+
);
|
|
107
|
+
return executeAgentLifecycle(
|
|
108
|
+
{
|
|
109
|
+
messages,
|
|
110
|
+
operation: () => originalGenerate(messages, nextOptions),
|
|
111
|
+
options,
|
|
112
|
+
phase: "start",
|
|
113
|
+
runId,
|
|
114
|
+
workflowId,
|
|
115
|
+
workflowType,
|
|
116
|
+
defaultModelInfo: invocationModelInfo,
|
|
117
|
+
...agentGoal ? { agentGoal } : {}
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (originalStream) {
|
|
123
|
+
baseAgent.stream = async (messages, executionOptions = {}) => {
|
|
124
|
+
const runId = String(executionOptions.runId ?? randomUUID());
|
|
125
|
+
const agentGoal = await resolveAgentGoal(
|
|
126
|
+
baseAgent,
|
|
127
|
+
executionOptions,
|
|
128
|
+
messages
|
|
129
|
+
);
|
|
130
|
+
const nextOptions = {
|
|
131
|
+
...executionOptions,
|
|
132
|
+
runId
|
|
133
|
+
};
|
|
134
|
+
const invocationModelInfo = resolveInvocationModelInfo(
|
|
135
|
+
baseAgent,
|
|
136
|
+
executionOptions
|
|
137
|
+
);
|
|
138
|
+
const output = await executeAgentLifecycle(
|
|
139
|
+
{
|
|
140
|
+
messages,
|
|
141
|
+
operation: () => originalStream(messages, nextOptions),
|
|
142
|
+
options,
|
|
143
|
+
phase: "start",
|
|
144
|
+
runId,
|
|
145
|
+
workflowId,
|
|
146
|
+
workflowType,
|
|
147
|
+
defaultModelInfo: invocationModelInfo,
|
|
148
|
+
...agentGoal ? { agentGoal } : {}
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
if (output && typeof output === "object") {
|
|
152
|
+
const streamMeta = getAgentStreamMeta(output);
|
|
153
|
+
attachStreamLifecycleHandlers(output, {
|
|
154
|
+
onFailure: async (error) => {
|
|
155
|
+
await sendAgentFailure(
|
|
156
|
+
options,
|
|
157
|
+
runId,
|
|
158
|
+
workflowId,
|
|
159
|
+
workflowType,
|
|
160
|
+
error,
|
|
161
|
+
streamMeta,
|
|
162
|
+
agentGoal
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
onSuccess: async (fullOutput) => {
|
|
166
|
+
await finalizeAgentSuccess(
|
|
167
|
+
options,
|
|
168
|
+
runId,
|
|
169
|
+
workflowId,
|
|
170
|
+
workflowType,
|
|
171
|
+
fullOutput,
|
|
172
|
+
streamMeta,
|
|
173
|
+
invocationModelInfo,
|
|
174
|
+
agentGoal
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return output;
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (originalResumeGenerate) {
|
|
183
|
+
baseAgent.resumeGenerate = async (resumeData, executionOptions = {}) => {
|
|
184
|
+
const runId = executionOptions.runId ? String(executionOptions.runId) : void 0;
|
|
185
|
+
const agentGoal = await resolveAgentGoal(baseAgent, executionOptions);
|
|
186
|
+
await handleAgentResume(
|
|
187
|
+
options,
|
|
188
|
+
runId,
|
|
189
|
+
workflowId,
|
|
190
|
+
workflowType,
|
|
191
|
+
resumeData,
|
|
192
|
+
agentGoal
|
|
193
|
+
);
|
|
194
|
+
const invocationModelInfo = resolveInvocationModelInfo(
|
|
195
|
+
baseAgent,
|
|
196
|
+
executionOptions
|
|
197
|
+
);
|
|
198
|
+
return executeAgentLifecycle({
|
|
199
|
+
operation: () => originalResumeGenerate(resumeData, executionOptions),
|
|
200
|
+
options,
|
|
201
|
+
phase: "resume",
|
|
202
|
+
runId: runId ?? randomUUID(),
|
|
203
|
+
workflowId,
|
|
204
|
+
workflowType,
|
|
205
|
+
defaultModelInfo: invocationModelInfo,
|
|
206
|
+
...agentGoal ? { agentGoal } : {}
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (originalResumeStream) {
|
|
211
|
+
baseAgent.resumeStream = async (resumeData, executionOptions = {}) => {
|
|
212
|
+
const runId = executionOptions.runId ? String(executionOptions.runId) : void 0;
|
|
213
|
+
const resolvedRunId = runId ?? randomUUID();
|
|
214
|
+
const agentGoal = await resolveAgentGoal(baseAgent, executionOptions);
|
|
215
|
+
await handleAgentResume(
|
|
216
|
+
options,
|
|
217
|
+
runId,
|
|
218
|
+
workflowId,
|
|
219
|
+
workflowType,
|
|
220
|
+
resumeData,
|
|
221
|
+
agentGoal
|
|
222
|
+
);
|
|
223
|
+
const invocationModelInfo = resolveInvocationModelInfo(
|
|
224
|
+
baseAgent,
|
|
225
|
+
executionOptions
|
|
226
|
+
);
|
|
227
|
+
const output = await executeAgentLifecycle({
|
|
228
|
+
operation: () => originalResumeStream(resumeData, executionOptions),
|
|
229
|
+
options,
|
|
230
|
+
phase: "resume",
|
|
231
|
+
runId: resolvedRunId,
|
|
232
|
+
workflowId,
|
|
233
|
+
workflowType,
|
|
234
|
+
defaultModelInfo: invocationModelInfo,
|
|
235
|
+
...agentGoal ? { agentGoal } : {}
|
|
236
|
+
});
|
|
237
|
+
if (output && typeof output === "object") {
|
|
238
|
+
const streamMeta = getAgentStreamMeta(output);
|
|
239
|
+
attachStreamLifecycleHandlers(output, {
|
|
240
|
+
onFailure: async (error) => {
|
|
241
|
+
await sendAgentFailure(
|
|
242
|
+
options,
|
|
243
|
+
resolvedRunId,
|
|
244
|
+
workflowId,
|
|
245
|
+
workflowType,
|
|
246
|
+
error,
|
|
247
|
+
streamMeta,
|
|
248
|
+
agentGoal
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
onSuccess: async (fullOutput) => {
|
|
252
|
+
await finalizeAgentSuccess(
|
|
253
|
+
options,
|
|
254
|
+
resolvedRunId,
|
|
255
|
+
workflowId,
|
|
256
|
+
workflowType,
|
|
257
|
+
fullOutput,
|
|
258
|
+
streamMeta,
|
|
259
|
+
invocationModelInfo,
|
|
260
|
+
agentGoal
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return output;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
Object.defineProperty(baseAgent, OPENBOX_WRAPPED_AGENT, {
|
|
269
|
+
enumerable: false,
|
|
270
|
+
value: true
|
|
271
|
+
});
|
|
272
|
+
return agent;
|
|
273
|
+
}
|
|
274
|
+
async function executeAgentLifecycle({
|
|
275
|
+
messages,
|
|
276
|
+
operation,
|
|
277
|
+
options,
|
|
278
|
+
phase,
|
|
279
|
+
runId,
|
|
280
|
+
workflowId,
|
|
281
|
+
workflowType,
|
|
282
|
+
defaultModelInfo,
|
|
283
|
+
agentGoal
|
|
284
|
+
}) {
|
|
285
|
+
const effectiveGoal = agentGoal ?? normalizeGoalCandidate(extractGoalCandidateFromInteraction(messages));
|
|
286
|
+
if (effectiveGoal) {
|
|
287
|
+
OPENBOX_AGENT_RUN_GOALS.set(runId, effectiveGoal);
|
|
288
|
+
}
|
|
289
|
+
if (phase === "start" && !options.config.skipWorkflowTypes.has(workflowType) && options.config.sendStartEvent) {
|
|
290
|
+
const verdict = await evaluateAgentEvent(options, {
|
|
291
|
+
event_type: WorkflowEventType.WORKFLOW_STARTED,
|
|
292
|
+
...effectiveGoal ? { goal: effectiveGoal } : {},
|
|
293
|
+
run_id: runId,
|
|
294
|
+
task_queue: "mastra",
|
|
295
|
+
workflow_id: workflowId,
|
|
296
|
+
workflow_input: serializeWorkflowInputForGovernance(
|
|
297
|
+
messages,
|
|
298
|
+
effectiveGoal
|
|
299
|
+
),
|
|
300
|
+
workflow_type: workflowType
|
|
301
|
+
});
|
|
302
|
+
if (verdict && Verdict.shouldStop(verdict.verdict)) {
|
|
303
|
+
throw new GovernanceHaltError(
|
|
304
|
+
verdict.reason ?? "Agent blocked by governance"
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (phase === "start" && messages !== void 0 && !options.config.skipWorkflowTypes.has(workflowType) && !options.config.skipSignals.has(AGENT_INPUT_SIGNAL_NAME)) {
|
|
309
|
+
const verdict = await evaluateAgentEvent(options, {
|
|
310
|
+
event_type: WorkflowEventType.SIGNAL_RECEIVED,
|
|
311
|
+
...effectiveGoal ? { goal: effectiveGoal } : {},
|
|
312
|
+
run_id: runId,
|
|
313
|
+
signal_args: serializeAgentSignalArgs(messages, effectiveGoal),
|
|
314
|
+
signal_name: AGENT_INPUT_SIGNAL_NAME,
|
|
315
|
+
task_queue: "mastra",
|
|
316
|
+
workflow_id: workflowId,
|
|
317
|
+
workflow_type: workflowType
|
|
318
|
+
});
|
|
319
|
+
if (verdict && Verdict.shouldStop(verdict.verdict)) {
|
|
320
|
+
throw new GovernanceHaltError(
|
|
321
|
+
verdict.reason ?? "Agent blocked by governance"
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
ensureAgentSpanBuffer(options, runId, workflowId, workflowType);
|
|
326
|
+
const startTimeMs = Date.now();
|
|
327
|
+
return runWithOpenBoxExecutionContext(
|
|
328
|
+
{
|
|
329
|
+
agentId: workflowType,
|
|
330
|
+
...effectiveGoal ? { goal: effectiveGoal } : {},
|
|
331
|
+
runId,
|
|
332
|
+
source: "agent",
|
|
333
|
+
taskQueue: "mastra",
|
|
334
|
+
workflowId,
|
|
335
|
+
workflowType
|
|
336
|
+
},
|
|
337
|
+
async () => {
|
|
338
|
+
try {
|
|
339
|
+
const result = await trace.getTracer("openbox.mastra").startActiveSpan(`agent.${phase}.${workflowType}`, async (activeSpan) => {
|
|
340
|
+
activeSpan.setAttribute("openbox.workflow_id", workflowId);
|
|
341
|
+
activeSpan.setAttribute("openbox.activity_id", `agent:${workflowType}:${phase}`);
|
|
342
|
+
activeSpan.setAttribute("openbox.run_id", runId);
|
|
343
|
+
options.spanProcessor.registerTrace(
|
|
344
|
+
activeSpan.spanContext().traceId,
|
|
345
|
+
workflowId,
|
|
346
|
+
`agent:${workflowType}:${phase}`,
|
|
347
|
+
runId
|
|
348
|
+
);
|
|
349
|
+
try {
|
|
350
|
+
return await operation();
|
|
351
|
+
} finally {
|
|
352
|
+
activeSpan.end();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
const isStreamResult = result != null && typeof result === "object" && ("getFullOutput" in result || "fullStream" in result);
|
|
356
|
+
if (!isStreamResult) {
|
|
357
|
+
const finishReason = result != null && typeof result === "object" ? result.finishReason : void 0;
|
|
358
|
+
if (finishReason === "suspended") {
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
await finalizeAgentSuccess(
|
|
362
|
+
options,
|
|
363
|
+
runId,
|
|
364
|
+
workflowId,
|
|
365
|
+
workflowType,
|
|
366
|
+
result,
|
|
367
|
+
{
|
|
368
|
+
startTimeMs
|
|
369
|
+
},
|
|
370
|
+
defaultModelInfo,
|
|
371
|
+
effectiveGoal
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (isStreamResult) {
|
|
375
|
+
setAgentStreamMeta(result, {
|
|
376
|
+
startTimeMs
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
return result;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
await sendAgentFailure(
|
|
382
|
+
options,
|
|
383
|
+
runId,
|
|
384
|
+
workflowId,
|
|
385
|
+
workflowType,
|
|
386
|
+
error,
|
|
387
|
+
{
|
|
388
|
+
startTimeMs
|
|
389
|
+
},
|
|
390
|
+
effectiveGoal
|
|
391
|
+
);
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
async function handleAgentResume(options, runId, workflowId, workflowType, resumeData, agentGoal) {
|
|
398
|
+
if (!runId) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const effectiveGoal = agentGoal ?? OPENBOX_AGENT_RUN_GOALS.get(runId);
|
|
402
|
+
if (effectiveGoal) {
|
|
403
|
+
OPENBOX_AGENT_RUN_GOALS.set(runId, effectiveGoal);
|
|
404
|
+
}
|
|
405
|
+
if (!options.config.skipWorkflowTypes.has(workflowType) && !options.config.skipSignals.has("resume")) {
|
|
406
|
+
const verdict2 = await evaluateAgentEvent(options, {
|
|
407
|
+
event_type: WorkflowEventType.SIGNAL_RECEIVED,
|
|
408
|
+
...effectiveGoal ? { goal: effectiveGoal } : {},
|
|
409
|
+
run_id: runId,
|
|
410
|
+
signal_args: appendGoalToSignalArgs(
|
|
411
|
+
serializeValue(resumeData),
|
|
412
|
+
effectiveGoal
|
|
413
|
+
),
|
|
414
|
+
signal_name: "resume",
|
|
415
|
+
task_queue: "mastra",
|
|
416
|
+
workflow_id: workflowId,
|
|
417
|
+
workflow_type: workflowType
|
|
418
|
+
});
|
|
419
|
+
if (verdict2 && Verdict.shouldStop(verdict2.verdict)) {
|
|
420
|
+
throw new GovernanceHaltError(
|
|
421
|
+
verdict2.reason ?? "Agent blocked by governance"
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const pending = getPendingApproval(runId);
|
|
426
|
+
if (!pending) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const approval = await options.client.pollApproval({
|
|
430
|
+
activityId: pending.activityId,
|
|
431
|
+
runId: pending.runId,
|
|
432
|
+
workflowId: pending.workflowId
|
|
433
|
+
});
|
|
434
|
+
if (!approval) {
|
|
435
|
+
throw new ApprovalPendingError("Failed to check approval status, retrying...");
|
|
436
|
+
}
|
|
437
|
+
if (approval.expired) {
|
|
438
|
+
clearPendingApproval(runId);
|
|
439
|
+
throw new ApprovalExpiredError(
|
|
440
|
+
`Approval expired for activity ${pending.activityType}`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
const verdict = Verdict.fromString(
|
|
444
|
+
approval.verdict ?? approval.action
|
|
445
|
+
);
|
|
446
|
+
if (verdict === Verdict.ALLOW) {
|
|
447
|
+
markActivityApproved(pending.runId, pending.activityId);
|
|
448
|
+
clearPendingApproval(runId);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (Verdict.shouldStop(verdict)) {
|
|
452
|
+
clearPendingApproval(runId);
|
|
453
|
+
throw new ApprovalRejectedError(
|
|
454
|
+
`Activity rejected: ${String(approval.reason ?? "Activity rejected")}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
throw new ApprovalPendingError(
|
|
458
|
+
`Awaiting approval for activity ${pending.activityType}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
async function finalizeAgentSuccess(options, runId, workflowId, workflowType, output, streamMeta, defaultModelInfo = {}, agentGoal) {
|
|
462
|
+
if (options.config.skipWorkflowTypes.has(workflowType)) {
|
|
463
|
+
clearAgentRunState(runId);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
await emitAgentOutputSignal(
|
|
468
|
+
options,
|
|
469
|
+
runId,
|
|
470
|
+
workflowId,
|
|
471
|
+
workflowType,
|
|
472
|
+
output,
|
|
473
|
+
agentGoal
|
|
474
|
+
);
|
|
475
|
+
const workflowSpans = normalizeSpansForGovernance(
|
|
476
|
+
options.spanProcessor.getBuffer(workflowId, runId)?.spans ?? []
|
|
477
|
+
);
|
|
478
|
+
const modelInfo = resolveWorkflowModelInfo(
|
|
479
|
+
output,
|
|
480
|
+
defaultModelInfo,
|
|
481
|
+
workflowSpans
|
|
482
|
+
);
|
|
483
|
+
const resolvedOutput = applyDefaultModelInfo(output, modelInfo);
|
|
484
|
+
const basePayload = {
|
|
485
|
+
event_type: WorkflowEventType.WORKFLOW_COMPLETED,
|
|
486
|
+
...agentGoal ? { goal: agentGoal } : {},
|
|
487
|
+
run_id: runId,
|
|
488
|
+
workflow_id: workflowId,
|
|
489
|
+
workflow_type: workflowType
|
|
490
|
+
};
|
|
491
|
+
const workflowOutput = serializeWorkflowOutputForGovernance(resolvedOutput);
|
|
492
|
+
const telemetryPayload = buildWorkflowCompletedTelemetryPayload(
|
|
493
|
+
{
|
|
494
|
+
...basePayload,
|
|
495
|
+
workflow_output: workflowOutput
|
|
496
|
+
},
|
|
497
|
+
resolvedOutput,
|
|
498
|
+
workflowSpans,
|
|
499
|
+
modelInfo,
|
|
500
|
+
streamMeta
|
|
501
|
+
);
|
|
502
|
+
const compactPayload = buildWorkflowCompletedCompactPayload(
|
|
503
|
+
basePayload,
|
|
504
|
+
workflowOutput,
|
|
505
|
+
resolvedOutput,
|
|
506
|
+
modelInfo,
|
|
507
|
+
streamMeta
|
|
508
|
+
);
|
|
509
|
+
const ultraMinimalPayload = buildWorkflowCompletedUltraMinimalPayload(
|
|
510
|
+
basePayload,
|
|
511
|
+
resolvedOutput,
|
|
512
|
+
modelInfo,
|
|
513
|
+
streamMeta
|
|
514
|
+
);
|
|
515
|
+
const verdict = await evaluateAgentEvent(
|
|
516
|
+
options,
|
|
517
|
+
telemetryPayload,
|
|
518
|
+
compactPayload,
|
|
519
|
+
ultraMinimalPayload
|
|
520
|
+
);
|
|
521
|
+
if (verdict && Verdict.shouldStop(verdict.verdict)) {
|
|
522
|
+
throw new GovernanceHaltError(
|
|
523
|
+
verdict.reason ?? "Agent blocked by governance"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
} finally {
|
|
527
|
+
clearAgentRunState(runId);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function buildWorkflowCompletedCompactPayload(basePayload, workflowOutput, output, modelInfo, streamMeta) {
|
|
531
|
+
const endTimeMs = Date.now();
|
|
532
|
+
const startTimeMs = streamMeta?.startTimeMs;
|
|
533
|
+
const durationMs = typeof startTimeMs === "number" ? Math.max(0, endTimeMs - startTimeMs) : void 0;
|
|
534
|
+
const usage = extractUsageMetrics(output);
|
|
535
|
+
const telemetryModelId = toTelemetryModelId(modelInfo.modelId);
|
|
536
|
+
const syntheticSpans = buildWorkflowTelemetrySpans(
|
|
537
|
+
[],
|
|
538
|
+
modelInfo,
|
|
539
|
+
usage,
|
|
540
|
+
endTimeMs
|
|
541
|
+
);
|
|
542
|
+
const payload = {
|
|
543
|
+
event_type: basePayload.event_type,
|
|
544
|
+
run_id: basePayload.run_id,
|
|
545
|
+
workflow_id: basePayload.workflow_id,
|
|
546
|
+
workflow_type: basePayload.workflow_type,
|
|
547
|
+
...typeof durationMs === "number" ? { duration_ms: durationMs } : {},
|
|
548
|
+
...typeof startTimeMs === "number" ? { start_time: startTimeMs } : {},
|
|
549
|
+
end_time: endTimeMs,
|
|
550
|
+
...typeof usage.inputTokens === "number" ? { input_tokens: usage.inputTokens } : {},
|
|
551
|
+
...typeof usage.outputTokens === "number" ? { output_tokens: usage.outputTokens } : {},
|
|
552
|
+
...typeof usage.totalTokens === "number" ? { total_tokens: usage.totalTokens } : {},
|
|
553
|
+
...modelInfo.modelId ? { model_id: modelInfo.modelId } : {},
|
|
554
|
+
...telemetryModelId ? { model: telemetryModelId } : {},
|
|
555
|
+
...modelInfo.provider ? { model_provider: modelInfo.provider } : {},
|
|
556
|
+
...modelInfo.provider ? { provider: modelInfo.provider } : {},
|
|
557
|
+
...syntheticSpans.length > 0 ? {
|
|
558
|
+
span_count: syntheticSpans.length,
|
|
559
|
+
spans: syntheticSpans
|
|
560
|
+
} : {
|
|
561
|
+
span_count: 0
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
const compactOutput = compactWorkflowOutput(workflowOutput);
|
|
565
|
+
if (compactOutput !== void 0) {
|
|
566
|
+
payload.workflow_output = compactOutput;
|
|
567
|
+
}
|
|
568
|
+
return payload;
|
|
569
|
+
}
|
|
570
|
+
function buildWorkflowCompletedUltraMinimalPayload(basePayload, output, modelInfo, streamMeta) {
|
|
571
|
+
const endTimeMs = Date.now();
|
|
572
|
+
const startTimeMs = streamMeta?.startTimeMs;
|
|
573
|
+
const durationMs = typeof startTimeMs === "number" ? Math.max(0, endTimeMs - startTimeMs) : void 0;
|
|
574
|
+
const usage = extractUsageMetrics(output);
|
|
575
|
+
const telemetryModelId = toTelemetryModelId(modelInfo.modelId);
|
|
576
|
+
return {
|
|
577
|
+
...basePayload,
|
|
578
|
+
...typeof durationMs === "number" ? { duration_ms: durationMs } : {},
|
|
579
|
+
...typeof startTimeMs === "number" ? { start_time: startTimeMs } : {},
|
|
580
|
+
end_time: endTimeMs,
|
|
581
|
+
...typeof usage.inputTokens === "number" ? { input_tokens: usage.inputTokens } : {},
|
|
582
|
+
...typeof usage.outputTokens === "number" ? { output_tokens: usage.outputTokens } : {},
|
|
583
|
+
...typeof usage.totalTokens === "number" ? { total_tokens: usage.totalTokens } : {},
|
|
584
|
+
...modelInfo.modelId ? { model_id: modelInfo.modelId } : {},
|
|
585
|
+
...telemetryModelId ? { model: telemetryModelId } : {},
|
|
586
|
+
...modelInfo.provider ? { model_provider: modelInfo.provider } : {},
|
|
587
|
+
...modelInfo.provider ? { provider: modelInfo.provider } : {}
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function buildWorkflowCompletedTelemetryPayload(basePayload, output, workflowSpans, modelInfo, streamMeta) {
|
|
591
|
+
const endTimeMs = Date.now();
|
|
592
|
+
const startTimeMs = streamMeta?.startTimeMs;
|
|
593
|
+
const durationMs = typeof startTimeMs === "number" ? Math.max(0, endTimeMs - startTimeMs) : void 0;
|
|
594
|
+
const usage = extractUsageMetrics(output);
|
|
595
|
+
const telemetryModelId = toTelemetryModelId(modelInfo.modelId);
|
|
596
|
+
const spans = buildWorkflowTelemetrySpans(
|
|
597
|
+
workflowSpans,
|
|
598
|
+
modelInfo,
|
|
599
|
+
usage,
|
|
600
|
+
endTimeMs
|
|
601
|
+
);
|
|
602
|
+
return {
|
|
603
|
+
...basePayload,
|
|
604
|
+
...typeof durationMs === "number" ? { duration_ms: durationMs } : {},
|
|
605
|
+
...typeof startTimeMs === "number" ? { start_time: startTimeMs } : {},
|
|
606
|
+
end_time: endTimeMs,
|
|
607
|
+
...typeof usage.inputTokens === "number" ? { input_tokens: usage.inputTokens } : {},
|
|
608
|
+
...typeof usage.outputTokens === "number" ? { output_tokens: usage.outputTokens } : {},
|
|
609
|
+
...typeof usage.totalTokens === "number" ? { total_tokens: usage.totalTokens } : {},
|
|
610
|
+
...modelInfo.modelId ? { model_id: modelInfo.modelId } : {},
|
|
611
|
+
...telemetryModelId ? { model: telemetryModelId } : {},
|
|
612
|
+
...modelInfo.provider ? { model_provider: modelInfo.provider } : {},
|
|
613
|
+
...modelInfo.provider ? { provider: modelInfo.provider } : {},
|
|
614
|
+
span_count: spans.length,
|
|
615
|
+
spans
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
function extractUsageMetrics(output) {
|
|
619
|
+
const outputRecord = output && typeof output === "object" ? output : void 0;
|
|
620
|
+
const usageCandidates = [
|
|
621
|
+
outputRecord?.usage,
|
|
622
|
+
outputRecord?.totalUsage,
|
|
623
|
+
outputRecord?.output,
|
|
624
|
+
outputRecord?.stepResult
|
|
625
|
+
];
|
|
626
|
+
for (const candidate of usageCandidates) {
|
|
627
|
+
const usage = extractUsageRecord(candidate);
|
|
628
|
+
if (usage) {
|
|
629
|
+
return usage;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return {};
|
|
633
|
+
}
|
|
634
|
+
function extractUsageRecord(value) {
|
|
635
|
+
if (!value || typeof value !== "object") {
|
|
636
|
+
return void 0;
|
|
637
|
+
}
|
|
638
|
+
const record = value;
|
|
639
|
+
const usage = "usage" in record && record.usage && typeof record.usage === "object" ? record.usage : record;
|
|
640
|
+
const inputTokens = toNumber(usage.inputTokens);
|
|
641
|
+
const outputTokens = toNumber(usage.outputTokens);
|
|
642
|
+
const totalTokens = toNumber(usage.totalTokens);
|
|
643
|
+
if (inputTokens === void 0 && outputTokens === void 0 && totalTokens === void 0) {
|
|
644
|
+
return void 0;
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
648
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
649
|
+
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function toNumber(value) {
|
|
653
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
654
|
+
}
|
|
655
|
+
function serializeWorkflowOutputForGovernance(output) {
|
|
656
|
+
const serialized = serializeValue(output);
|
|
657
|
+
return compactWorkflowOutput(serialized) ?? {
|
|
658
|
+
summary: "Workflow output omitted by SDK compaction"
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function serializeWorkflowInputForGovernance(input, goal) {
|
|
662
|
+
const serializedInput = serializeValue(input);
|
|
663
|
+
const prompt = extractLatestUserPrompt(input);
|
|
664
|
+
if (!goal && prompt === void 0) {
|
|
665
|
+
return serializedInput;
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
...goal ? { goal } : {},
|
|
669
|
+
...prompt ? { prompt } : {},
|
|
670
|
+
input: serializedInput
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function compactWorkflowOutput(output) {
|
|
674
|
+
if (output == null) {
|
|
675
|
+
return output;
|
|
676
|
+
}
|
|
677
|
+
if (typeof output === "string") {
|
|
678
|
+
return truncateString(output, 4e3);
|
|
679
|
+
}
|
|
680
|
+
if (typeof output !== "object") {
|
|
681
|
+
return output;
|
|
682
|
+
}
|
|
683
|
+
const record = output;
|
|
684
|
+
const usage = extractUsageMetrics(output);
|
|
685
|
+
const compact = {
|
|
686
|
+
...typeof record.finishReason === "string" ? { finishReason: record.finishReason } : {},
|
|
687
|
+
...typeof record.status === "string" ? { status: record.status } : {},
|
|
688
|
+
...typeof record.text === "string" ? { text: truncateString(record.text, 4e3) } : {},
|
|
689
|
+
...typeof record.modelId === "string" ? { modelId: record.modelId } : {},
|
|
690
|
+
...typeof usage.inputTokens === "number" || typeof usage.outputTokens === "number" || typeof usage.totalTokens === "number" ? {
|
|
691
|
+
usage: {
|
|
692
|
+
...typeof usage.inputTokens === "number" ? { inputTokens: usage.inputTokens } : {},
|
|
693
|
+
...typeof usage.outputTokens === "number" ? { outputTokens: usage.outputTokens } : {},
|
|
694
|
+
...typeof usage.totalTokens === "number" ? { totalTokens: usage.totalTokens } : {}
|
|
695
|
+
}
|
|
696
|
+
} : {}
|
|
697
|
+
};
|
|
698
|
+
const warnings = record.warnings;
|
|
699
|
+
if (Array.isArray(warnings) && warnings.length > 0) {
|
|
700
|
+
compact.warnings = warnings.slice(0, 3).map((warning) => serializeValue(warning));
|
|
701
|
+
}
|
|
702
|
+
if (Object.keys(compact).length > 0) {
|
|
703
|
+
return compact;
|
|
704
|
+
}
|
|
705
|
+
const fallback = JSON.stringify(record);
|
|
706
|
+
return truncateString(fallback, 4e3);
|
|
707
|
+
}
|
|
708
|
+
function truncateString(value, maxChars) {
|
|
709
|
+
if (value.length <= maxChars) {
|
|
710
|
+
return value;
|
|
711
|
+
}
|
|
712
|
+
return `${value.slice(0, Math.max(0, maxChars - 16))}...[truncated]`;
|
|
713
|
+
}
|
|
714
|
+
function serializeAgentSignalArgs(messages, goal) {
|
|
715
|
+
const latestUserPrompt = extractLatestUserPrompt(messages);
|
|
716
|
+
if (latestUserPrompt) {
|
|
717
|
+
return appendGoalToSignalArgs([latestUserPrompt], goal);
|
|
718
|
+
}
|
|
719
|
+
const serialized = serializeValue(messages);
|
|
720
|
+
if (serialized == null) {
|
|
721
|
+
return appendGoalToSignalArgs([], goal);
|
|
722
|
+
}
|
|
723
|
+
return appendGoalToSignalArgs(
|
|
724
|
+
Array.isArray(serialized) ? serialized : [serialized],
|
|
725
|
+
goal
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
function serializeAgentOutputSignalArgs(output, goal) {
|
|
729
|
+
const serialized = compactWorkflowOutput(serializeValue(output));
|
|
730
|
+
if (serialized === void 0 || serialized === null) {
|
|
731
|
+
return appendGoalToSignalArgs([], goal);
|
|
732
|
+
}
|
|
733
|
+
return appendGoalToSignalArgs(
|
|
734
|
+
Array.isArray(serialized) ? serialized : [serialized],
|
|
735
|
+
goal
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
function appendGoalToSignalArgs(signalArgs, goal) {
|
|
739
|
+
const normalizedArgs = Array.isArray(signalArgs) ? [...signalArgs] : signalArgs === void 0 || signalArgs === null ? [] : [signalArgs];
|
|
740
|
+
if (!goal || goal.trim().length === 0) {
|
|
741
|
+
return normalizedArgs;
|
|
742
|
+
}
|
|
743
|
+
const trimmedGoal = goal.trim();
|
|
744
|
+
if (trimmedGoal.length === 0) {
|
|
745
|
+
return normalizedArgs;
|
|
746
|
+
}
|
|
747
|
+
if (normalizedArgs.length === 0) {
|
|
748
|
+
return [{ goal: trimmedGoal }];
|
|
749
|
+
}
|
|
750
|
+
const hasGoalObject = normalizedArgs.some((item) => {
|
|
751
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
const existingGoal = item.goal;
|
|
755
|
+
return typeof existingGoal === "string" && existingGoal.trim().length > 0;
|
|
756
|
+
});
|
|
757
|
+
if (hasGoalObject) {
|
|
758
|
+
return normalizedArgs;
|
|
759
|
+
}
|
|
760
|
+
const firstPrompt = typeof normalizedArgs[0] === "string" ? normalizedArgs[0].trim() : "";
|
|
761
|
+
if (firstPrompt.length > 0) {
|
|
762
|
+
return [...normalizedArgs, { goal: trimmedGoal, prompt: firstPrompt }];
|
|
763
|
+
}
|
|
764
|
+
return [...normalizedArgs, { goal: trimmedGoal }];
|
|
765
|
+
}
|
|
766
|
+
function extractLatestUserPrompt(messages) {
|
|
767
|
+
const candidates = normalizeMessageCandidates(messages);
|
|
768
|
+
for (let index = candidates.length - 1; index >= 0; index -= 1) {
|
|
769
|
+
const entry = candidates[index];
|
|
770
|
+
if (!entry || typeof entry !== "object") {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const record = entry;
|
|
774
|
+
const role = typeof record.role === "string" ? record.role.toLowerCase() : void 0;
|
|
775
|
+
if (role && role !== "user") {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const text = extractTextFromStructuredValue(record.content) ?? extractTextFromStructuredValue(record.parts) ?? extractTextFromStructuredValue(record.prompt) ?? extractTextFromStructuredValue(record.input);
|
|
779
|
+
if (text) {
|
|
780
|
+
return text;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return void 0;
|
|
784
|
+
}
|
|
785
|
+
function normalizeMessageCandidates(messages) {
|
|
786
|
+
if (Array.isArray(messages)) {
|
|
787
|
+
return messages;
|
|
788
|
+
}
|
|
789
|
+
if (!messages || typeof messages !== "object") {
|
|
790
|
+
return [];
|
|
791
|
+
}
|
|
792
|
+
const record = messages;
|
|
793
|
+
return Array.isArray(record.messages) ? record.messages : [];
|
|
794
|
+
}
|
|
795
|
+
function extractTextFromStructuredValue(value) {
|
|
796
|
+
if (typeof value === "string") {
|
|
797
|
+
const trimmed = value.trim();
|
|
798
|
+
if (trimmed.length === 0) {
|
|
799
|
+
return void 0;
|
|
800
|
+
}
|
|
801
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
802
|
+
try {
|
|
803
|
+
const parsed = JSON.parse(trimmed);
|
|
804
|
+
const extracted = extractTextFromStructuredValue(parsed);
|
|
805
|
+
if (extracted && extracted.trim().length > 0) {
|
|
806
|
+
return extracted.trim();
|
|
807
|
+
}
|
|
808
|
+
} catch {
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return trimmed;
|
|
812
|
+
}
|
|
813
|
+
if (Array.isArray(value)) {
|
|
814
|
+
const parts = value.map((item) => extractTextFromStructuredValue(item)).filter((item) => typeof item === "string" && item.length > 0);
|
|
815
|
+
if (parts.length === 0) {
|
|
816
|
+
return void 0;
|
|
817
|
+
}
|
|
818
|
+
const combined = parts.join("\n").trim();
|
|
819
|
+
return combined.length > 0 ? combined : void 0;
|
|
820
|
+
}
|
|
821
|
+
if (!value || typeof value !== "object") {
|
|
822
|
+
return void 0;
|
|
823
|
+
}
|
|
824
|
+
const record = value;
|
|
825
|
+
const text = extractTextFromStructuredValue(record.text) ?? extractTextFromStructuredValue(record.content) ?? extractTextFromStructuredValue(record.parts) ?? extractTextFromStructuredValue(record.prompt) ?? extractTextFromStructuredValue(record.input) ?? extractTextFromStructuredValue(record.messages);
|
|
826
|
+
if (!text) {
|
|
827
|
+
return void 0;
|
|
828
|
+
}
|
|
829
|
+
return text.trim().length > 0 ? text.trim() : void 0;
|
|
830
|
+
}
|
|
831
|
+
function extractModelInfo(output, fallback = {}) {
|
|
832
|
+
if (!output || typeof output !== "object") {
|
|
833
|
+
return {
|
|
834
|
+
...fallback.modelId ? { modelId: fallback.modelId } : {},
|
|
835
|
+
...fallback.provider ? { provider: fallback.provider } : {}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
const record = output;
|
|
839
|
+
const response = record.response && typeof record.response === "object" ? record.response : void 0;
|
|
840
|
+
const modelMetadata = response?.modelMetadata && typeof response.modelMetadata === "object" ? response.modelMetadata : void 0;
|
|
841
|
+
const parsedModelInfo = resolveModelInfoFromCandidates([
|
|
842
|
+
response?.modelId,
|
|
843
|
+
modelMetadata?.modelId,
|
|
844
|
+
response?.model,
|
|
845
|
+
record.modelId,
|
|
846
|
+
record.model
|
|
847
|
+
]);
|
|
848
|
+
const providerCandidates = [
|
|
849
|
+
modelMetadata?.provider,
|
|
850
|
+
response?.provider,
|
|
851
|
+
record.provider,
|
|
852
|
+
parsedModelInfo.provider,
|
|
853
|
+
extractProviderHint(record)
|
|
854
|
+
];
|
|
855
|
+
const modelId = parsedModelInfo.modelId;
|
|
856
|
+
let provider;
|
|
857
|
+
for (const candidate of providerCandidates) {
|
|
858
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
859
|
+
provider = normalizeProvider(candidate);
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
...modelId ? { modelId } : {},
|
|
865
|
+
...!modelId && fallback.modelId ? { modelId: fallback.modelId } : {},
|
|
866
|
+
...provider ? { provider } : {},
|
|
867
|
+
...!provider && fallback.provider ? { provider: fallback.provider } : {}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function extractAgentModelInfo(agentRecord) {
|
|
871
|
+
const directModelInfo = resolveModelInfoFromCandidates([agentRecord.model]);
|
|
872
|
+
const model = agentRecord.model && typeof agentRecord.model === "object" ? agentRecord.model : void 0;
|
|
873
|
+
const config = model?.config && typeof model.config === "object" ? model.config : void 0;
|
|
874
|
+
const nestedModelInfo = resolveModelInfoFromCandidates([
|
|
875
|
+
model?.modelId,
|
|
876
|
+
config?.modelId,
|
|
877
|
+
model?.id,
|
|
878
|
+
model?.name,
|
|
879
|
+
model?.model
|
|
880
|
+
]);
|
|
881
|
+
const providerCandidates = [
|
|
882
|
+
nestedModelInfo.provider,
|
|
883
|
+
config?.provider,
|
|
884
|
+
model?.provider,
|
|
885
|
+
directModelInfo.provider
|
|
886
|
+
];
|
|
887
|
+
const modelId = directModelInfo.modelId ?? nestedModelInfo.modelId;
|
|
888
|
+
let provider;
|
|
889
|
+
for (const candidate of providerCandidates) {
|
|
890
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
891
|
+
provider = normalizeProvider(candidate);
|
|
892
|
+
break;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
...modelId ? { modelId } : {},
|
|
897
|
+
...provider ? { provider } : {}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function resolveInvocationModelInfo(agentRecord, executionOptions) {
|
|
901
|
+
const agentModelInfo = extractAgentModelInfo(agentRecord);
|
|
902
|
+
const optionsModelInfo = extractModelInfo(executionOptions, {});
|
|
903
|
+
return {
|
|
904
|
+
...optionsModelInfo.modelId ? { modelId: optionsModelInfo.modelId } : agentModelInfo.modelId ? { modelId: agentModelInfo.modelId } : {},
|
|
905
|
+
...optionsModelInfo.provider ? { provider: optionsModelInfo.provider } : agentModelInfo.provider ? { provider: agentModelInfo.provider } : {}
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function applyDefaultModelInfo(output, fallback) {
|
|
909
|
+
if (!output || typeof output !== "object") {
|
|
910
|
+
return output;
|
|
911
|
+
}
|
|
912
|
+
const record = output;
|
|
913
|
+
const hasModelId = typeof record.modelId === "string" && record.modelId.trim().length > 0;
|
|
914
|
+
const hasProvider = typeof record.provider === "string" && record.provider.trim().length > 0;
|
|
915
|
+
if ((hasModelId || !fallback.modelId) && (hasProvider || !fallback.provider)) {
|
|
916
|
+
return output;
|
|
917
|
+
}
|
|
918
|
+
return {
|
|
919
|
+
...record,
|
|
920
|
+
...!hasModelId && fallback.modelId ? { modelId: fallback.modelId } : {},
|
|
921
|
+
...!hasProvider && fallback.provider ? { provider: fallback.provider } : {}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function extractProviderHint(outputRecord) {
|
|
925
|
+
const parsedModelInfo = resolveModelInfoFromCandidates([
|
|
926
|
+
outputRecord.modelId,
|
|
927
|
+
outputRecord.model
|
|
928
|
+
]);
|
|
929
|
+
if (parsedModelInfo.provider) {
|
|
930
|
+
return parsedModelInfo.provider;
|
|
931
|
+
}
|
|
932
|
+
const directProvider = inferProviderFromMetadata(outputRecord.providerMetadata);
|
|
933
|
+
if (directProvider) {
|
|
934
|
+
return directProvider;
|
|
935
|
+
}
|
|
936
|
+
const toolCallsProvider = inferProviderFromToolCalls(outputRecord.toolCalls);
|
|
937
|
+
if (toolCallsProvider) {
|
|
938
|
+
return toolCallsProvider;
|
|
939
|
+
}
|
|
940
|
+
const steps = Array.isArray(outputRecord.steps) ? outputRecord.steps : void 0;
|
|
941
|
+
if (!steps) {
|
|
942
|
+
return void 0;
|
|
943
|
+
}
|
|
944
|
+
for (const step of steps) {
|
|
945
|
+
if (!step || typeof step !== "object") {
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
const stepRecord = step;
|
|
949
|
+
const stepProvider = inferProviderFromMetadata(stepRecord.providerMetadata);
|
|
950
|
+
if (stepProvider) {
|
|
951
|
+
return stepProvider;
|
|
952
|
+
}
|
|
953
|
+
const stepToolProvider = inferProviderFromToolCalls(stepRecord.toolCalls);
|
|
954
|
+
if (stepToolProvider) {
|
|
955
|
+
return stepToolProvider;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return void 0;
|
|
959
|
+
}
|
|
960
|
+
function inferProviderFromToolCalls(toolCalls) {
|
|
961
|
+
if (!Array.isArray(toolCalls)) {
|
|
962
|
+
return void 0;
|
|
963
|
+
}
|
|
964
|
+
for (const entry of toolCalls) {
|
|
965
|
+
if (!entry || typeof entry !== "object") {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
const entryRecord = entry;
|
|
969
|
+
const directProvider = inferProviderFromMetadata(entryRecord.providerMetadata);
|
|
970
|
+
if (directProvider) {
|
|
971
|
+
return directProvider;
|
|
972
|
+
}
|
|
973
|
+
const payload = entryRecord.payload && typeof entryRecord.payload === "object" ? entryRecord.payload : void 0;
|
|
974
|
+
const payloadProvider = inferProviderFromMetadata(payload?.providerMetadata);
|
|
975
|
+
if (payloadProvider) {
|
|
976
|
+
return payloadProvider;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return void 0;
|
|
980
|
+
}
|
|
981
|
+
function inferProviderFromMetadata(metadata) {
|
|
982
|
+
if (!metadata || typeof metadata !== "object") {
|
|
983
|
+
return void 0;
|
|
984
|
+
}
|
|
985
|
+
const keys = Object.keys(metadata).map(
|
|
986
|
+
(key) => key.toLowerCase()
|
|
987
|
+
);
|
|
988
|
+
if (keys.includes("openai")) {
|
|
989
|
+
return "openai";
|
|
990
|
+
}
|
|
991
|
+
if (keys.includes("anthropic")) {
|
|
992
|
+
return "anthropic";
|
|
993
|
+
}
|
|
994
|
+
if (keys.includes("google") || keys.includes("gemini")) {
|
|
995
|
+
return "google";
|
|
996
|
+
}
|
|
997
|
+
return void 0;
|
|
998
|
+
}
|
|
999
|
+
function resolveModelInfoFromCandidates(candidates) {
|
|
1000
|
+
let modelId;
|
|
1001
|
+
let provider;
|
|
1002
|
+
for (const candidate of candidates) {
|
|
1003
|
+
if (typeof candidate !== "string" || candidate.trim().length === 0) {
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
const parsed = parseModelIdentifier(candidate);
|
|
1007
|
+
if (!modelId && parsed.modelId) {
|
|
1008
|
+
modelId = parsed.modelId;
|
|
1009
|
+
}
|
|
1010
|
+
if (!provider && parsed.provider) {
|
|
1011
|
+
provider = parsed.provider;
|
|
1012
|
+
}
|
|
1013
|
+
if (modelId && provider) {
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
...modelId ? { modelId } : {},
|
|
1019
|
+
...provider ? { provider } : {}
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
function parseModelIdentifier(candidate) {
|
|
1023
|
+
const trimmed = candidate.trim();
|
|
1024
|
+
if (!trimmed) {
|
|
1025
|
+
return {};
|
|
1026
|
+
}
|
|
1027
|
+
const slashParts = trimmed.split("/");
|
|
1028
|
+
if (slashParts.length >= 2) {
|
|
1029
|
+
const possibleProvider = slashParts[0]?.trim();
|
|
1030
|
+
const modelPart = slashParts.slice(1).join("/").trim();
|
|
1031
|
+
if (possibleProvider && modelPart && isProviderToken(possibleProvider)) {
|
|
1032
|
+
return {
|
|
1033
|
+
modelId: modelPart,
|
|
1034
|
+
provider: normalizeProvider(possibleProvider)
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return {
|
|
1039
|
+
modelId: trimmed
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function isProviderToken(candidate) {
|
|
1043
|
+
const normalized = candidate.trim().toLowerCase();
|
|
1044
|
+
return normalized.includes("openai") || normalized.includes("anthropic") || normalized.includes("google") || normalized.includes("gemini");
|
|
1045
|
+
}
|
|
1046
|
+
function normalizeProvider(candidate) {
|
|
1047
|
+
const normalized = candidate.trim().toLowerCase();
|
|
1048
|
+
if (normalized.includes("openai")) {
|
|
1049
|
+
return "openai";
|
|
1050
|
+
}
|
|
1051
|
+
if (normalized.includes("anthropic")) {
|
|
1052
|
+
return "anthropic";
|
|
1053
|
+
}
|
|
1054
|
+
if (normalized.includes("google") || normalized.includes("gemini")) {
|
|
1055
|
+
return "google";
|
|
1056
|
+
}
|
|
1057
|
+
return candidate.trim();
|
|
1058
|
+
}
|
|
1059
|
+
function toTelemetryModelId(modelId) {
|
|
1060
|
+
if (typeof modelId !== "string") {
|
|
1061
|
+
return void 0;
|
|
1062
|
+
}
|
|
1063
|
+
const trimmed = modelId.trim();
|
|
1064
|
+
if (trimmed.length === 0) {
|
|
1065
|
+
return void 0;
|
|
1066
|
+
}
|
|
1067
|
+
const sanitized = trimmed.replace(/[.:/\\\s]+/g, "-").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
1068
|
+
return sanitized.length > 0 ? sanitized : trimmed;
|
|
1069
|
+
}
|
|
1070
|
+
function buildWorkflowTelemetrySpans(spans, modelInfo, usage, endTimeMs) {
|
|
1071
|
+
const inputTokens = usage.inputTokens ?? 0;
|
|
1072
|
+
const outputTokens = usage.outputTokens ?? 0;
|
|
1073
|
+
if (inputTokens <= 0 && outputTokens <= 0) {
|
|
1074
|
+
return spans;
|
|
1075
|
+
}
|
|
1076
|
+
if (hasParseableModelUsageSpan(spans)) {
|
|
1077
|
+
return spans;
|
|
1078
|
+
}
|
|
1079
|
+
const providerUrl = resolveProviderUrl(modelInfo, spans);
|
|
1080
|
+
if (!providerUrl) {
|
|
1081
|
+
return spans;
|
|
1082
|
+
}
|
|
1083
|
+
const modelId = resolveSyntheticModelId(modelInfo, spans);
|
|
1084
|
+
const traceId = getTraceIdCandidate(spans);
|
|
1085
|
+
return [
|
|
1086
|
+
...spans,
|
|
1087
|
+
createSyntheticModelUsageSpan({
|
|
1088
|
+
endTimeMs,
|
|
1089
|
+
inputTokens,
|
|
1090
|
+
modelId,
|
|
1091
|
+
outputTokens,
|
|
1092
|
+
providerUrl,
|
|
1093
|
+
...modelInfo.provider ? { provider: modelInfo.provider } : {},
|
|
1094
|
+
...traceId ? { traceId } : {}
|
|
1095
|
+
})
|
|
1096
|
+
];
|
|
1097
|
+
}
|
|
1098
|
+
function hasParseableModelUsageSpan(spans) {
|
|
1099
|
+
return spans.some((span) => {
|
|
1100
|
+
const attributes = span.attributes && typeof span.attributes === "object" ? span.attributes : {};
|
|
1101
|
+
const rawUrl = attributes["http.url"] ?? attributes["url.full"];
|
|
1102
|
+
const url = typeof rawUrl === "string" ? rawUrl : void 0;
|
|
1103
|
+
if (!url || !isLlmProviderUrl(url)) {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
const responseBody = getStringField(span, "response_body", "responseBody");
|
|
1107
|
+
if (!responseBody) {
|
|
1108
|
+
return false;
|
|
1109
|
+
}
|
|
1110
|
+
if (!hasUsageInBody(responseBody)) {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
const modelFromResponse = extractModelIdFromBody(responseBody);
|
|
1114
|
+
if (modelFromResponse) {
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1117
|
+
const requestBody = getStringField(span, "request_body", "requestBody");
|
|
1118
|
+
if (!requestBody) {
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
return extractModelIdFromBody(requestBody) !== void 0;
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
function hasUsageInBody(body) {
|
|
1125
|
+
try {
|
|
1126
|
+
const parsed = JSON.parse(body);
|
|
1127
|
+
const usage = parsed.usage;
|
|
1128
|
+
return typeof usage?.prompt_tokens === "number" || typeof usage?.completion_tokens === "number" || typeof usage?.input_tokens === "number" || typeof usage?.output_tokens === "number";
|
|
1129
|
+
} catch {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
function extractModelIdFromBody(body) {
|
|
1134
|
+
try {
|
|
1135
|
+
const parsed = JSON.parse(body);
|
|
1136
|
+
const model = typeof parsed.model === "string" ? parsed.model.trim() : void 0;
|
|
1137
|
+
return model && model.length > 0 ? model : void 0;
|
|
1138
|
+
} catch {
|
|
1139
|
+
return void 0;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
function createSyntheticModelUsageSpan({
|
|
1143
|
+
endTimeMs,
|
|
1144
|
+
inputTokens,
|
|
1145
|
+
modelId,
|
|
1146
|
+
outputTokens,
|
|
1147
|
+
provider,
|
|
1148
|
+
providerUrl,
|
|
1149
|
+
traceId
|
|
1150
|
+
}) {
|
|
1151
|
+
const endTimeNs = Math.max(1, Math.floor(endTimeMs * 1e6));
|
|
1152
|
+
const startTimeNs = Math.max(0, endTimeNs - 1);
|
|
1153
|
+
const normalizedTraceId = normalizeHexId(traceId, 32);
|
|
1154
|
+
const spanId = normalizeHexId(void 0, 16);
|
|
1155
|
+
const telemetryModelId = toTelemetryModelId(modelId) ?? modelId;
|
|
1156
|
+
return {
|
|
1157
|
+
attributes: {
|
|
1158
|
+
"http.method": "POST",
|
|
1159
|
+
"http.url": providerUrl,
|
|
1160
|
+
"openbox.synthetic": true
|
|
1161
|
+
},
|
|
1162
|
+
duration_ns: 1,
|
|
1163
|
+
end_time: endTimeNs,
|
|
1164
|
+
events: [],
|
|
1165
|
+
kind: "CLIENT",
|
|
1166
|
+
name: "openbox.synthetic.model_usage",
|
|
1167
|
+
request_body: JSON.stringify({
|
|
1168
|
+
model: telemetryModelId,
|
|
1169
|
+
model_id: modelId,
|
|
1170
|
+
...provider ? { model_provider: provider } : {},
|
|
1171
|
+
...provider ? { provider } : {}
|
|
1172
|
+
}),
|
|
1173
|
+
response_body: JSON.stringify({
|
|
1174
|
+
model: telemetryModelId,
|
|
1175
|
+
model_id: modelId,
|
|
1176
|
+
...provider ? { model_provider: provider } : {},
|
|
1177
|
+
...provider ? { provider } : {},
|
|
1178
|
+
usage: {
|
|
1179
|
+
completion_tokens: outputTokens,
|
|
1180
|
+
input_tokens: inputTokens,
|
|
1181
|
+
output_tokens: outputTokens,
|
|
1182
|
+
prompt_tokens: inputTokens,
|
|
1183
|
+
total_tokens: inputTokens + outputTokens
|
|
1184
|
+
}
|
|
1185
|
+
}),
|
|
1186
|
+
semantic_type: "llm_completion",
|
|
1187
|
+
span_id: spanId,
|
|
1188
|
+
start_time: startTimeNs,
|
|
1189
|
+
status: {
|
|
1190
|
+
code: "OK"
|
|
1191
|
+
},
|
|
1192
|
+
trace_id: normalizedTraceId
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
function resolveProviderUrl(modelInfo, spans) {
|
|
1196
|
+
const provider = modelInfo.provider?.toLowerCase();
|
|
1197
|
+
const modelId = modelInfo.modelId?.toLowerCase();
|
|
1198
|
+
if (provider?.includes("openai")) {
|
|
1199
|
+
return "https://api.openai.com/v1/responses";
|
|
1200
|
+
}
|
|
1201
|
+
if (provider?.includes("anthropic")) {
|
|
1202
|
+
return "https://api.anthropic.com/v1/messages";
|
|
1203
|
+
}
|
|
1204
|
+
if (provider?.includes("google") || provider?.includes("gemini")) {
|
|
1205
|
+
return "https://generativelanguage.googleapis.com/v1beta/models";
|
|
1206
|
+
}
|
|
1207
|
+
for (const span of spans) {
|
|
1208
|
+
const attributes = span.attributes && typeof span.attributes === "object" ? span.attributes : {};
|
|
1209
|
+
const rawUrl = attributes["http.url"] ?? attributes["url.full"];
|
|
1210
|
+
const url = typeof rawUrl === "string" ? rawUrl : void 0;
|
|
1211
|
+
if (!url) {
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (url.includes("api.openai.com")) {
|
|
1215
|
+
return "https://api.openai.com/v1/responses";
|
|
1216
|
+
}
|
|
1217
|
+
if (url.includes("api.anthropic.com")) {
|
|
1218
|
+
return "https://api.anthropic.com/v1/messages";
|
|
1219
|
+
}
|
|
1220
|
+
if (url.includes("generativelanguage.googleapis.com")) {
|
|
1221
|
+
return "https://generativelanguage.googleapis.com/v1beta/models";
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
if (!modelId) {
|
|
1225
|
+
return void 0;
|
|
1226
|
+
}
|
|
1227
|
+
if (modelId.startsWith("gpt-") || modelId.startsWith("o1") || modelId.startsWith("o3")) {
|
|
1228
|
+
return "https://api.openai.com/v1/responses";
|
|
1229
|
+
}
|
|
1230
|
+
if (modelId.startsWith("claude-")) {
|
|
1231
|
+
return "https://api.anthropic.com/v1/messages";
|
|
1232
|
+
}
|
|
1233
|
+
if (modelId.startsWith("gemini")) {
|
|
1234
|
+
return "https://generativelanguage.googleapis.com/v1beta/models";
|
|
1235
|
+
}
|
|
1236
|
+
return void 0;
|
|
1237
|
+
}
|
|
1238
|
+
function resolveSyntheticModelId(modelInfo, spans) {
|
|
1239
|
+
for (const span of spans) {
|
|
1240
|
+
const responseBody = getStringField(span, "response_body", "responseBody");
|
|
1241
|
+
if (responseBody) {
|
|
1242
|
+
const modelFromResponse = extractModelIdFromBody(responseBody);
|
|
1243
|
+
if (modelFromResponse) {
|
|
1244
|
+
return modelFromResponse;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const requestBody = getStringField(span, "request_body", "requestBody");
|
|
1248
|
+
if (!requestBody) {
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
const modelFromRequest = extractModelIdFromBody(requestBody);
|
|
1252
|
+
if (modelFromRequest) {
|
|
1253
|
+
return modelFromRequest;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
if (modelInfo.modelId) {
|
|
1257
|
+
return modelInfo.modelId;
|
|
1258
|
+
}
|
|
1259
|
+
return "unknown-model";
|
|
1260
|
+
}
|
|
1261
|
+
function getTraceIdCandidate(spans) {
|
|
1262
|
+
for (const span of spans) {
|
|
1263
|
+
const traceId = getStringField(span, "trace_id", "traceId");
|
|
1264
|
+
if (traceId) {
|
|
1265
|
+
return traceId;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return void 0;
|
|
1269
|
+
}
|
|
1270
|
+
function getStringField(record, snakeKey, camelKey) {
|
|
1271
|
+
const snake = record[snakeKey];
|
|
1272
|
+
if (typeof snake === "string") {
|
|
1273
|
+
return snake;
|
|
1274
|
+
}
|
|
1275
|
+
const camel = record[camelKey];
|
|
1276
|
+
if (typeof camel === "string") {
|
|
1277
|
+
return camel;
|
|
1278
|
+
}
|
|
1279
|
+
return void 0;
|
|
1280
|
+
}
|
|
1281
|
+
function isLlmProviderUrl(url) {
|
|
1282
|
+
return url.includes("api.openai.com") || url.includes("api.anthropic.com") || url.includes("generativelanguage.googleapis.com");
|
|
1283
|
+
}
|
|
1284
|
+
function resolveWorkflowModelInfo(output, fallbackModelInfo, spans) {
|
|
1285
|
+
const outputModelInfo = extractModelInfo(output, {});
|
|
1286
|
+
const spanModelInfo = extractModelInfoFromSpans(spans);
|
|
1287
|
+
return {
|
|
1288
|
+
...outputModelInfo.modelId ? { modelId: outputModelInfo.modelId } : spanModelInfo.modelId ? { modelId: spanModelInfo.modelId } : fallbackModelInfo.modelId ? { modelId: fallbackModelInfo.modelId } : {},
|
|
1289
|
+
...outputModelInfo.provider ? { provider: outputModelInfo.provider } : spanModelInfo.provider ? { provider: spanModelInfo.provider } : fallbackModelInfo.provider ? { provider: fallbackModelInfo.provider } : {}
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function extractModelInfoFromSpans(spans) {
|
|
1293
|
+
let modelId;
|
|
1294
|
+
let provider;
|
|
1295
|
+
for (const span of spans) {
|
|
1296
|
+
const attributes = span.attributes && typeof span.attributes === "object" ? span.attributes : {};
|
|
1297
|
+
const rawUrl = attributes["http.url"] ?? attributes["url.full"];
|
|
1298
|
+
const url = typeof rawUrl === "string" ? rawUrl : void 0;
|
|
1299
|
+
if (url && !provider) {
|
|
1300
|
+
provider = inferProviderFromUrl(url);
|
|
1301
|
+
}
|
|
1302
|
+
const responseBody = getStringField(span, "response_body", "responseBody");
|
|
1303
|
+
if (!modelId && responseBody) {
|
|
1304
|
+
modelId = extractModelIdFromBody(responseBody);
|
|
1305
|
+
}
|
|
1306
|
+
const requestBody = getStringField(span, "request_body", "requestBody");
|
|
1307
|
+
if (!modelId && requestBody) {
|
|
1308
|
+
modelId = extractModelIdFromBody(requestBody);
|
|
1309
|
+
}
|
|
1310
|
+
if (modelId && provider) {
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
...modelId ? { modelId } : {},
|
|
1316
|
+
...provider ? { provider } : {}
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
function inferProviderFromUrl(url) {
|
|
1320
|
+
const normalized = url.toLowerCase();
|
|
1321
|
+
if (normalized.includes("api.openai.com")) {
|
|
1322
|
+
return "openai";
|
|
1323
|
+
}
|
|
1324
|
+
if (normalized.includes("api.anthropic.com")) {
|
|
1325
|
+
return "anthropic";
|
|
1326
|
+
}
|
|
1327
|
+
if (normalized.includes("generativelanguage.googleapis.com")) {
|
|
1328
|
+
return "google";
|
|
1329
|
+
}
|
|
1330
|
+
return void 0;
|
|
1331
|
+
}
|
|
1332
|
+
function normalizeHexId(candidate, width) {
|
|
1333
|
+
const source = (candidate ?? randomUUID().replaceAll("-", "")).toLowerCase();
|
|
1334
|
+
const filtered = source.replace(/[^a-f0-9]/g, "");
|
|
1335
|
+
if (filtered.length >= width) {
|
|
1336
|
+
return filtered.slice(0, width);
|
|
1337
|
+
}
|
|
1338
|
+
return filtered.padEnd(width, "0");
|
|
1339
|
+
}
|
|
1340
|
+
async function sendAgentFailure(options, runId, workflowId, workflowType, error, streamMeta, agentGoal) {
|
|
1341
|
+
if (options.config.skipWorkflowTypes.has(workflowType)) {
|
|
1342
|
+
clearAgentRunState(runId);
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
void streamMeta;
|
|
1346
|
+
try {
|
|
1347
|
+
await emitAgentOutputSignal(
|
|
1348
|
+
options,
|
|
1349
|
+
runId,
|
|
1350
|
+
workflowId,
|
|
1351
|
+
workflowType,
|
|
1352
|
+
{
|
|
1353
|
+
error: serializeError(error),
|
|
1354
|
+
status: "failed"
|
|
1355
|
+
},
|
|
1356
|
+
agentGoal
|
|
1357
|
+
);
|
|
1358
|
+
await evaluateAgentEvent(options, {
|
|
1359
|
+
error: serializeError(error),
|
|
1360
|
+
event_type: WorkflowEventType.WORKFLOW_FAILED,
|
|
1361
|
+
...agentGoal ? { goal: agentGoal } : {},
|
|
1362
|
+
run_id: runId,
|
|
1363
|
+
workflow_id: workflowId,
|
|
1364
|
+
workflow_type: workflowType
|
|
1365
|
+
});
|
|
1366
|
+
} finally {
|
|
1367
|
+
clearAgentRunState(runId);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
async function emitAgentOutputSignal(options, runId, workflowId, workflowType, output, goal) {
|
|
1371
|
+
if (options.config.skipSignals.has(AGENT_OUTPUT_SIGNAL_NAME)) {
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const signalArgs = serializeAgentOutputSignalArgs(output, goal);
|
|
1375
|
+
const outputSignalBasePayload = {
|
|
1376
|
+
event_type: WorkflowEventType.SIGNAL_RECEIVED,
|
|
1377
|
+
...goal ? { goal } : {},
|
|
1378
|
+
run_id: runId,
|
|
1379
|
+
signal_args: signalArgs,
|
|
1380
|
+
signal_name: AGENT_OUTPUT_SIGNAL_NAME,
|
|
1381
|
+
task_queue: "mastra",
|
|
1382
|
+
workflow_id: workflowId,
|
|
1383
|
+
workflow_type: workflowType
|
|
1384
|
+
};
|
|
1385
|
+
const outputSignalSpans = buildAgentOutputSignalSpans(
|
|
1386
|
+
options,
|
|
1387
|
+
workflowId,
|
|
1388
|
+
runId
|
|
1389
|
+
);
|
|
1390
|
+
const outputSignalPayload = outputSignalSpans.length > 0 ? {
|
|
1391
|
+
...outputSignalBasePayload,
|
|
1392
|
+
span_count: outputSignalSpans.length,
|
|
1393
|
+
spans: outputSignalSpans
|
|
1394
|
+
} : outputSignalBasePayload;
|
|
1395
|
+
await evaluateAgentEvent(
|
|
1396
|
+
options,
|
|
1397
|
+
outputSignalPayload,
|
|
1398
|
+
outputSignalBasePayload
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
async function evaluateAgentEvent(options, payload, fallbackPayload, minimalPayload) {
|
|
1402
|
+
const candidates = buildCandidatePayloads(
|
|
1403
|
+
payload,
|
|
1404
|
+
fallbackPayload,
|
|
1405
|
+
minimalPayload
|
|
1406
|
+
).filter((candidate, index, all) => {
|
|
1407
|
+
const isLast = index === all.length - 1;
|
|
1408
|
+
return !isPayloadOverBudget(
|
|
1409
|
+
candidate.payload,
|
|
1410
|
+
options.config.maxEvaluatePayloadBytes,
|
|
1411
|
+
isLast
|
|
1412
|
+
);
|
|
1413
|
+
});
|
|
1414
|
+
let resolvedError = null;
|
|
1415
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
1416
|
+
const candidate = candidates[index];
|
|
1417
|
+
if (!candidate) {
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
try {
|
|
1421
|
+
const result = await options.client.evaluate({
|
|
1422
|
+
source: "workflow-telemetry",
|
|
1423
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1424
|
+
...candidate.payload
|
|
1425
|
+
});
|
|
1426
|
+
if (result !== null) {
|
|
1427
|
+
return result;
|
|
1428
|
+
}
|
|
1429
|
+
continue;
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
resolvedError = error;
|
|
1432
|
+
const hasNext = index < candidates.length - 1;
|
|
1433
|
+
if (hasNext && isRecoverableGovernanceError(error)) {
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (options.config.onApiError === "fail_closed") {
|
|
1440
|
+
return {
|
|
1441
|
+
action: "stop",
|
|
1442
|
+
alignmentScore: void 0,
|
|
1443
|
+
approvalId: void 0,
|
|
1444
|
+
behavioralViolations: void 0,
|
|
1445
|
+
constraints: void 0,
|
|
1446
|
+
governanceEventId: void 0,
|
|
1447
|
+
guardrailsResult: void 0,
|
|
1448
|
+
metadata: void 0,
|
|
1449
|
+
policyId: void 0,
|
|
1450
|
+
reason: `Governance API error: ${resolvedError instanceof Error ? resolvedError.message : String(resolvedError)}`,
|
|
1451
|
+
riskScore: 0,
|
|
1452
|
+
trustTier: void 0,
|
|
1453
|
+
verdict: Verdict.HALT
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
return null;
|
|
1457
|
+
}
|
|
1458
|
+
function isBadRequestSchemaError(error) {
|
|
1459
|
+
return error instanceof GovernanceAPIError && /HTTP 400/i.test(error.message);
|
|
1460
|
+
}
|
|
1461
|
+
function isPayloadTooLargeError(error) {
|
|
1462
|
+
return error instanceof GovernanceAPIError && /(blob data size exceeds limit|payload too large|request entity too large|message too large)/i.test(
|
|
1463
|
+
error.message
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
function isTransientGovernanceError(error) {
|
|
1467
|
+
if (!(error instanceof GovernanceAPIError)) {
|
|
1468
|
+
return false;
|
|
1469
|
+
}
|
|
1470
|
+
return /HTTP\s(429|5\d\d)\b/i.test(error.message) || /(context deadline exceeded|temporarily unavailable|timeout|timed out|connection reset|econnreset|etimedout|upstream connect error)/i.test(
|
|
1471
|
+
error.message
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
function isRecoverableGovernanceError(error) {
|
|
1475
|
+
return isBadRequestSchemaError(error) || isPayloadTooLargeError(error) || isTransientGovernanceError(error);
|
|
1476
|
+
}
|
|
1477
|
+
function buildCandidatePayloads(payload, fallbackPayload, minimalPayload) {
|
|
1478
|
+
const candidates = [
|
|
1479
|
+
payload,
|
|
1480
|
+
fallbackPayload,
|
|
1481
|
+
minimalPayload
|
|
1482
|
+
].filter(
|
|
1483
|
+
(value) => value !== void 0
|
|
1484
|
+
);
|
|
1485
|
+
const deduped = [];
|
|
1486
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1487
|
+
for (const candidate of candidates) {
|
|
1488
|
+
const key = safeStringify(candidate);
|
|
1489
|
+
if (!seen.has(key)) {
|
|
1490
|
+
deduped.push({
|
|
1491
|
+
payload: candidate
|
|
1492
|
+
});
|
|
1493
|
+
seen.add(key);
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return deduped;
|
|
1497
|
+
}
|
|
1498
|
+
function isPayloadOverBudget(payload, maxBytes, isLastFallback) {
|
|
1499
|
+
const serialized = safeStringify(payload);
|
|
1500
|
+
const sizeBytes = Buffer.byteLength(serialized, "utf8");
|
|
1501
|
+
if (sizeBytes <= maxBytes || isLastFallback) {
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
return true;
|
|
1505
|
+
}
|
|
1506
|
+
function safeStringify(payload) {
|
|
1507
|
+
try {
|
|
1508
|
+
return JSON.stringify(payload);
|
|
1509
|
+
} catch {
|
|
1510
|
+
return "{}";
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
function serializeError(error) {
|
|
1514
|
+
if (error instanceof Error) {
|
|
1515
|
+
return {
|
|
1516
|
+
message: error.message,
|
|
1517
|
+
type: error.name
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
return {
|
|
1521
|
+
message: String(error),
|
|
1522
|
+
type: typeof error
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function clearAgentRunState(runId) {
|
|
1526
|
+
OPENBOX_AGENT_RUN_GOALS.delete(runId);
|
|
1527
|
+
OPENBOX_AGENT_SIGNAL_SPAN_CURSOR.delete(runId);
|
|
1528
|
+
}
|
|
1529
|
+
function buildAgentOutputSignalSpans(options, workflowId, runId) {
|
|
1530
|
+
const queuedHookSpans = options.spanProcessor.consumeAgentSignalHookSpans(
|
|
1531
|
+
workflowId,
|
|
1532
|
+
runId
|
|
1533
|
+
);
|
|
1534
|
+
if (queuedHookSpans.length > 0) {
|
|
1535
|
+
return queuedHookSpans.slice(-MAX_AGENT_OUTPUT_SIGNAL_SPANS).map((span) => compactAgentSignalSpan(span));
|
|
1536
|
+
}
|
|
1537
|
+
const workflowSpans = normalizeSpansForGovernance(
|
|
1538
|
+
options.spanProcessor.getBuffer(workflowId, runId)?.spans ?? []
|
|
1539
|
+
);
|
|
1540
|
+
const llmSpans = workflowSpans.filter((span) => isCompletedLlmAlignmentSpan(span));
|
|
1541
|
+
if (llmSpans.length === 0) {
|
|
1542
|
+
return [];
|
|
1543
|
+
}
|
|
1544
|
+
const cursor = OPENBOX_AGENT_SIGNAL_SPAN_CURSOR.get(runId) ?? 0;
|
|
1545
|
+
const normalizedCursor = cursor >= 0 && cursor <= llmSpans.length ? cursor : 0;
|
|
1546
|
+
const unsentSpans = llmSpans.slice(normalizedCursor);
|
|
1547
|
+
OPENBOX_AGENT_SIGNAL_SPAN_CURSOR.set(runId, llmSpans.length);
|
|
1548
|
+
if (unsentSpans.length === 0) {
|
|
1549
|
+
return [];
|
|
1550
|
+
}
|
|
1551
|
+
const maxSourceSpanCount = Math.max(
|
|
1552
|
+
1,
|
|
1553
|
+
Math.floor(MAX_AGENT_OUTPUT_SIGNAL_SPANS / 2)
|
|
1554
|
+
);
|
|
1555
|
+
return unsentSpans.slice(-maxSourceSpanCount).flatMap((span) => buildAgentOutputSignalSpanPhases(span)).map((span) => compactAgentSignalSpan(span));
|
|
1556
|
+
}
|
|
1557
|
+
function isCompletedLlmAlignmentSpan(span) {
|
|
1558
|
+
const stage = getStringField(span, "stage", "stage");
|
|
1559
|
+
if (stage && stage !== "completed") {
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
const hasCompletedTiming = typeof span.end_time === "number" || typeof span.duration_ns === "number";
|
|
1563
|
+
if (!stage && !hasCompletedTiming) {
|
|
1564
|
+
return false;
|
|
1565
|
+
}
|
|
1566
|
+
const semanticType = getStringField(span, "semantic_type", "semanticType");
|
|
1567
|
+
if (semanticType && semanticType.startsWith("llm_")) {
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
const attributes = span.attributes && typeof span.attributes === "object" ? span.attributes : {};
|
|
1571
|
+
const httpMethod = typeof attributes["http.method"] === "string" ? attributes["http.method"].trim().toUpperCase() : void 0;
|
|
1572
|
+
const rawUrl = attributes["http.url"] ?? attributes["url.full"];
|
|
1573
|
+
const httpUrl = typeof rawUrl === "string" ? rawUrl.toLowerCase() : "";
|
|
1574
|
+
if (httpMethod === "POST" && (httpUrl.includes("api.openai.com") || httpUrl.includes("api.anthropic.com") || httpUrl.includes("generativelanguage.googleapis.com"))) {
|
|
1575
|
+
return true;
|
|
1576
|
+
}
|
|
1577
|
+
const requestBody = getStringField(span, "request_body", "requestBody");
|
|
1578
|
+
const responseBody = getStringField(span, "response_body", "responseBody");
|
|
1579
|
+
return httpMethod === "POST" && looksLikeLlmPayload(requestBody, responseBody);
|
|
1580
|
+
}
|
|
1581
|
+
function looksLikeLlmPayload(requestBody, responseBody) {
|
|
1582
|
+
return typeof requestBody === "string" && extractModelIdFromBody(requestBody) !== void 0 || typeof responseBody === "string" && (extractModelIdFromBody(responseBody) !== void 0 || hasUsageInBody(responseBody));
|
|
1583
|
+
}
|
|
1584
|
+
function compactAgentSignalSpan(span) {
|
|
1585
|
+
const compacted = {
|
|
1586
|
+
...span
|
|
1587
|
+
};
|
|
1588
|
+
if (typeof compacted.request_body === "string") {
|
|
1589
|
+
compacted.request_body = truncateString(
|
|
1590
|
+
compacted.request_body,
|
|
1591
|
+
MAX_AGENT_SIGNAL_SPAN_BODY_CHARS
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
if (typeof compacted.response_body === "string") {
|
|
1595
|
+
compacted.response_body = truncateString(
|
|
1596
|
+
compacted.response_body,
|
|
1597
|
+
MAX_AGENT_SIGNAL_SPAN_BODY_CHARS
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
return compacted;
|
|
1601
|
+
}
|
|
1602
|
+
function buildAgentOutputSignalSpanPhases(span) {
|
|
1603
|
+
const completedSpan = normalizeAgentOutputSignalSpan(span, "completed");
|
|
1604
|
+
const startedSpan = normalizeAgentOutputSignalSpan(span, "started");
|
|
1605
|
+
return [startedSpan, completedSpan];
|
|
1606
|
+
}
|
|
1607
|
+
function normalizeAgentOutputSignalSpan(span, stage) {
|
|
1608
|
+
const normalized = {
|
|
1609
|
+
...span,
|
|
1610
|
+
stage,
|
|
1611
|
+
...typeof span.semantic_type !== "string" && typeof span.semanticType !== "string" ? { semantic_type: "llm_completion" } : {}
|
|
1612
|
+
};
|
|
1613
|
+
if (stage === "started") {
|
|
1614
|
+
delete normalized.duration_ns;
|
|
1615
|
+
delete normalized.durationNs;
|
|
1616
|
+
delete normalized.end_time;
|
|
1617
|
+
delete normalized.endTime;
|
|
1618
|
+
delete normalized.response_body;
|
|
1619
|
+
delete normalized.responseBody;
|
|
1620
|
+
delete normalized.response_headers;
|
|
1621
|
+
delete normalized.responseHeaders;
|
|
1622
|
+
delete normalized.status;
|
|
1623
|
+
}
|
|
1624
|
+
return normalized;
|
|
1625
|
+
}
|
|
1626
|
+
function ensureAgentSpanBuffer(options, runId, workflowId, workflowType) {
|
|
1627
|
+
const existing = options.spanProcessor.getBuffer(workflowId, runId);
|
|
1628
|
+
if (!existing || existing.runId !== runId) {
|
|
1629
|
+
options.spanProcessor.registerWorkflow(
|
|
1630
|
+
workflowId,
|
|
1631
|
+
new WorkflowSpanBuffer({
|
|
1632
|
+
runId,
|
|
1633
|
+
taskQueue: "mastra",
|
|
1634
|
+
workflowId,
|
|
1635
|
+
workflowType
|
|
1636
|
+
})
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
function getAgentStreamMeta(stream) {
|
|
1641
|
+
if (!stream || typeof stream !== "object") {
|
|
1642
|
+
return void 0;
|
|
1643
|
+
}
|
|
1644
|
+
return stream[OPENBOX_AGENT_STREAM_META];
|
|
1645
|
+
}
|
|
1646
|
+
function setAgentStreamMeta(stream, meta) {
|
|
1647
|
+
Object.defineProperty(stream, OPENBOX_AGENT_STREAM_META, {
|
|
1648
|
+
configurable: true,
|
|
1649
|
+
enumerable: false,
|
|
1650
|
+
value: meta
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
function attachStreamLifecycleHandlers(stream, handlers) {
|
|
1654
|
+
const streamLike = stream;
|
|
1655
|
+
const originalGetFullOutput = typeof streamLike.getFullOutput === "function" ? streamLike.getFullOutput.bind(streamLike) : void 0;
|
|
1656
|
+
const originalConsumeStream = typeof streamLike.consumeStream === "function" ? streamLike.consumeStream.bind(streamLike) : void 0;
|
|
1657
|
+
if (!originalGetFullOutput && !originalConsumeStream && !isReadableStream(streamLike.fullStream)) {
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
let settled = false;
|
|
1661
|
+
let settledPromise;
|
|
1662
|
+
const settleSuccess = (fullOutput) => {
|
|
1663
|
+
if (settledPromise) {
|
|
1664
|
+
return settledPromise;
|
|
1665
|
+
}
|
|
1666
|
+
settled = true;
|
|
1667
|
+
settledPromise = handlers.onSuccess(fullOutput);
|
|
1668
|
+
return settledPromise;
|
|
1669
|
+
};
|
|
1670
|
+
const settleFailure = (error) => {
|
|
1671
|
+
if (settledPromise) {
|
|
1672
|
+
return settledPromise;
|
|
1673
|
+
}
|
|
1674
|
+
settled = true;
|
|
1675
|
+
settledPromise = handlers.onFailure(error);
|
|
1676
|
+
return settledPromise;
|
|
1677
|
+
};
|
|
1678
|
+
if (originalGetFullOutput) {
|
|
1679
|
+
streamLike.getFullOutput = async (...args) => {
|
|
1680
|
+
try {
|
|
1681
|
+
const fullOutput = await originalGetFullOutput(...args);
|
|
1682
|
+
await settleSuccess(fullOutput);
|
|
1683
|
+
return fullOutput;
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
await settleFailure(error);
|
|
1686
|
+
throw error;
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
if (originalConsumeStream) {
|
|
1691
|
+
streamLike.consumeStream = async (...args) => {
|
|
1692
|
+
try {
|
|
1693
|
+
const consumed = await originalConsumeStream(...args);
|
|
1694
|
+
if (!settled) {
|
|
1695
|
+
const snapshot = buildStreamSnapshot(streamLike);
|
|
1696
|
+
await settleSuccess(snapshot).catch(() => {
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
return consumed;
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
await settleFailure(error);
|
|
1702
|
+
throw error;
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
if (isReadableStream(streamLike.fullStream)) {
|
|
1707
|
+
const observedStream = streamLike.fullStream.pipeThrough(
|
|
1708
|
+
new TransformStream({
|
|
1709
|
+
flush() {
|
|
1710
|
+
if (settled) {
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
const snapshot = buildStreamSnapshot(streamLike);
|
|
1714
|
+
return settleSuccess(snapshot).catch(() => {
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
})
|
|
1718
|
+
);
|
|
1719
|
+
Object.defineProperty(streamLike, "fullStream", {
|
|
1720
|
+
configurable: true,
|
|
1721
|
+
enumerable: false,
|
|
1722
|
+
value: observedStream,
|
|
1723
|
+
writable: true
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
function buildStreamSnapshot(stream) {
|
|
1728
|
+
return {
|
|
1729
|
+
finishReason: stream._getImmediateFinishReason?.(),
|
|
1730
|
+
status: stream.status,
|
|
1731
|
+
text: stream._getImmediateText?.(),
|
|
1732
|
+
toolCalls: stream._getImmediateToolCalls?.(),
|
|
1733
|
+
toolResults: stream._getImmediateToolResults?.(),
|
|
1734
|
+
usage: stream._getImmediateUsage?.(),
|
|
1735
|
+
warnings: stream._getImmediateWarnings?.()
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
function isReadableStream(value) {
|
|
1739
|
+
return typeof value === "object" && value !== null && "pipeThrough" in value && typeof value.pipeThrough === "function";
|
|
1740
|
+
}
|
|
1741
|
+
export {
|
|
1742
|
+
wrapAgent
|
|
1743
|
+
};
|
|
1744
|
+
//# sourceMappingURL=wrap-agent.js.map
|