@agent-native/core 0.109.0 → 0.109.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/docs/content/integrations.mdx +87 -253
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +24 -2
- package/corpus/core/src/a2a/correlation.ts +50 -0
- package/corpus/core/src/a2a/handlers.ts +140 -14
- package/corpus/core/src/a2a/index.ts +1 -0
- package/corpus/core/src/a2a/invoke.ts +10 -1
- package/corpus/core/src/a2a/task-store.ts +146 -6
- package/corpus/core/src/a2a/types.ts +16 -0
- package/corpus/core/src/action.ts +2 -0
- package/corpus/core/src/agent/production-agent.ts +23 -0
- package/corpus/core/src/client/blocks/library/diagram.config.ts +10 -1
- package/corpus/core/src/client/blocks/library/diagram.tsx +9 -4
- package/corpus/core/src/client/use-run-stuck-detection.ts +27 -1
- package/corpus/core/src/db/client.ts +55 -0
- package/corpus/core/src/deploy/build.ts +63 -3
- package/corpus/core/src/observability/traces.ts +172 -19
- package/corpus/core/src/scripts/call-agent.ts +53 -2
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +72 -25
- package/corpus/core/src/server/agent-chat-plugin.ts +107 -6
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-07-17-analytics-now-includes-a-default-alert-for-spikes-in-stuck-agent-chats.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +23 -14
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +5 -1
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +3 -3
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +209 -87
- package/corpus/templates/clips/.agents/skills/dictate/SKILL.md +28 -5
- package/corpus/templates/clips/.agents/skills/meetings/SKILL.md +7 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +23 -0
- package/corpus/templates/clips/AGENTS.md +10 -0
- package/corpus/templates/clips/actions/create-dictation.ts +29 -4
- package/corpus/templates/clips/actions/finalize-recording.ts +542 -65
- package/corpus/templates/clips/actions/get-recording-player-data.ts +16 -6
- package/corpus/templates/clips/actions/set-mobile-capture-state.ts +37 -0
- package/corpus/templates/clips/actions/update-dictation.ts +9 -1
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +17 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +14 -2
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +2 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -3
- package/corpus/templates/clips/app/routes/record.tsx +3 -3
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +19 -5
- package/corpus/templates/clips/changelog/2026-07-17-capture-dictation-background-meeting-audio-and-camera-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-clips-now-retries-storage-verification-in-the-background-ins.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-delayed-upload-verification-keeps-local-backups-retryable.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-fixed-broken-thumbnail-icons-appearing-over-playable-clip-em.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-17-playback-comments-stay-readable-at-faster-playback-speeds.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +3 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +3 -3
- package/corpus/templates/clips/desktop/src/app.tsx +35 -6
- package/corpus/templates/clips/desktop/src/lib/processing-backup-recovery.ts +19 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +141 -18
- package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +28 -7
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -4
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +32 -15
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +59 -18
- package/corpus/templates/clips/server/db/schema.ts +9 -1
- package/corpus/templates/clips/server/jobs/media-verification.ts +130 -0
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +74 -7
- package/corpus/templates/clips/server/lib/media-verification-state.ts +55 -0
- package/corpus/templates/clips/server/lib/player-thumbnail-url.ts +31 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +10 -3
- package/corpus/templates/clips/server/plugins/auth.ts +5 -5
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +12 -2
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +13 -1
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +340 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +24 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +91 -7
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +22 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +14 -0
- package/corpus/templates/clips/shared/finalize-recovery.ts +61 -2
- package/corpus/templates/clips/shared/share-meta.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-17-diagrams-can-opt-into-a-polished-clean-rendering-mode-withou.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +3 -0
- package/dist/a2a/client.d.ts +11 -2
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +16 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/correlation.d.ts +10 -0
- package/dist/a2a/correlation.d.ts.map +1 -0
- package/dist/a2a/correlation.js +40 -0
- package/dist/a2a/correlation.js.map +1 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +100 -15
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/invoke.d.ts +4 -1
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +3 -0
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/a2a/task-store.d.ts +12 -1
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +96 -6
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/a2a/types.d.ts +15 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +2 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +14 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/blocks/library/diagram.config.d.ts +2 -0
- package/dist/client/blocks/library/diagram.config.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.config.js +8 -1
- package/dist/client/blocks/library/diagram.config.js.map +1 -1
- package/dist/client/blocks/library/diagram.d.ts.map +1 -1
- package/dist/client/blocks/library/diagram.js +9 -4
- package/dist/client/blocks/library/diagram.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +25 -1
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +7 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +33 -0
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/build.d.ts +3 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +45 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/observability/traces.d.ts +9 -0
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +137 -19
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +35 -3
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +18 -2
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +35 -7
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +93 -6
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/docs/content/integrations.mdx +87 -253
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +21 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
- package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
|
@@ -26,6 +26,16 @@ function costUsdFromCenticents(value: number): number {
|
|
|
26
26
|
return Math.round((value / 10_000) * 1_000_000) / 1_000_000;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
const MAX_TRACKED_GENERATION_TOOL_CALLS = 50;
|
|
30
|
+
|
|
31
|
+
type GenerationToolCall = {
|
|
32
|
+
name: string;
|
|
33
|
+
started_offset_ms: number;
|
|
34
|
+
duration_ms: number;
|
|
35
|
+
status: "success" | "error";
|
|
36
|
+
error_class: "tool_error" | "legacy_inferred_error" | "interrupted" | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
29
39
|
function emitLlmGenerationTrackingEvent(args: {
|
|
30
40
|
runId: string;
|
|
31
41
|
threadId: string | null;
|
|
@@ -45,6 +55,15 @@ function emitLlmGenerationTrackingEvent(args: {
|
|
|
45
55
|
toolCalls: number;
|
|
46
56
|
successfulTools: number;
|
|
47
57
|
failedTools: number;
|
|
58
|
+
tools: GenerationToolCall[];
|
|
59
|
+
toolsTruncated: boolean;
|
|
60
|
+
delegation?: {
|
|
61
|
+
protocol: "a2a" | "mcp";
|
|
62
|
+
callerApp?: string;
|
|
63
|
+
taskId?: string;
|
|
64
|
+
parentRunId?: string;
|
|
65
|
+
parentTurnId?: string;
|
|
66
|
+
};
|
|
48
67
|
createdAt: number;
|
|
49
68
|
experimentAssignments?: Array<{
|
|
50
69
|
experimentId: string;
|
|
@@ -77,6 +96,14 @@ function emitLlmGenerationTrackingEvent(args: {
|
|
|
77
96
|
tool_calls: args.toolCalls,
|
|
78
97
|
successful_tools: args.successfulTools,
|
|
79
98
|
failed_tools: args.failedTools,
|
|
99
|
+
tools: args.tools,
|
|
100
|
+
tools_truncated: args.toolsTruncated,
|
|
101
|
+
delegated: args.delegation ? true : undefined,
|
|
102
|
+
delegation_protocol: args.delegation?.protocol,
|
|
103
|
+
caller_app: args.delegation?.callerApp,
|
|
104
|
+
a2a_task_id: args.delegation?.taskId,
|
|
105
|
+
parent_run_id: args.delegation?.parentRunId,
|
|
106
|
+
parent_turn_id: args.delegation?.parentTurnId,
|
|
80
107
|
model_selection_source: args.modelSelectionSource,
|
|
81
108
|
created_at: new Date(args.createdAt).toISOString(),
|
|
82
109
|
created_at_ms: args.createdAt,
|
|
@@ -191,6 +218,7 @@ export async function instrumentAgentLoop(opts: {
|
|
|
191
218
|
send: (event: AgentChatEvent) => void;
|
|
192
219
|
signal: AbortSignal;
|
|
193
220
|
providerOptions?: any;
|
|
221
|
+
runId?: string;
|
|
194
222
|
}) => Promise<AgentLoopUsage>;
|
|
195
223
|
loopOpts: {
|
|
196
224
|
engine: any;
|
|
@@ -202,6 +230,7 @@ export async function instrumentAgentLoop(opts: {
|
|
|
202
230
|
send: (event: AgentChatEvent) => void;
|
|
203
231
|
signal: AbortSignal;
|
|
204
232
|
providerOptions?: any;
|
|
233
|
+
runId?: string;
|
|
205
234
|
};
|
|
206
235
|
runId: string;
|
|
207
236
|
threadId: string | null;
|
|
@@ -217,6 +246,13 @@ export async function instrumentAgentLoop(opts: {
|
|
|
217
246
|
variantId: string;
|
|
218
247
|
}>;
|
|
219
248
|
modelSelectionSource?: string;
|
|
249
|
+
delegation?: {
|
|
250
|
+
protocol: "a2a" | "mcp";
|
|
251
|
+
callerApp?: string;
|
|
252
|
+
taskId?: string;
|
|
253
|
+
parentRunId?: string;
|
|
254
|
+
parentTurnId?: string;
|
|
255
|
+
};
|
|
220
256
|
/** Raw user-authored message before prompt/context enrichment. */
|
|
221
257
|
sentimentInput?: string;
|
|
222
258
|
classifyError?: (error: unknown) =>
|
|
@@ -271,6 +307,7 @@ export async function instrumentAgentLoop(opts: {
|
|
|
271
307
|
number,
|
|
272
308
|
{
|
|
273
309
|
spanId: string;
|
|
310
|
+
callId?: string;
|
|
274
311
|
startMs: number;
|
|
275
312
|
toolName: string;
|
|
276
313
|
input: AgentToolInput;
|
|
@@ -278,12 +315,11 @@ export async function instrumentAgentLoop(opts: {
|
|
|
278
315
|
endResult?: { status: "success" | "error"; errorMessage: string | null };
|
|
279
316
|
}
|
|
280
317
|
>();
|
|
281
|
-
// Secondary index
|
|
282
|
-
//
|
|
283
|
-
// pair starts and dones correctly when the agent runs concurrent calls to the
|
|
284
|
-
// same tool name (read-only / parallelSafe batches via Promise.all), we keep a
|
|
285
|
-
// queue per name and match each done to the OLDEST still-pending start.
|
|
318
|
+
// Secondary index for legacy emitters without call ids. Current tool events
|
|
319
|
+
// are paired by id first; same-name FIFO remains as a compatibility fallback.
|
|
286
320
|
const toolNameToCounters = new Map<string, number[]>();
|
|
321
|
+
const toolCallIdToCounter = new Map<string, number>();
|
|
322
|
+
const generationToolCalls = new Map<number, GenerationToolCall>();
|
|
287
323
|
|
|
288
324
|
let toolCallCount = 0;
|
|
289
325
|
let successfulTools = 0;
|
|
@@ -304,6 +340,7 @@ export async function instrumentAgentLoop(opts: {
|
|
|
304
340
|
// microtask gap by recording the span on the pending entry when ready.
|
|
305
341
|
const entry: {
|
|
306
342
|
spanId: string;
|
|
343
|
+
callId?: string;
|
|
307
344
|
startMs: number;
|
|
308
345
|
toolName: string;
|
|
309
346
|
input: AgentToolInput;
|
|
@@ -316,12 +353,14 @@ export async function instrumentAgentLoop(opts: {
|
|
|
316
353
|
};
|
|
317
354
|
} = {
|
|
318
355
|
spanId: sid,
|
|
356
|
+
...(event.id ? { callId: event.id } : {}),
|
|
319
357
|
startMs: Date.now(),
|
|
320
358
|
toolName: event.tool,
|
|
321
359
|
input: event.input,
|
|
322
360
|
otelSpan: null,
|
|
323
361
|
};
|
|
324
362
|
pendingTools.set(counter, entry);
|
|
363
|
+
if (event.id) toolCallIdToCounter.set(event.id, counter);
|
|
325
364
|
void startAgentSpan("tool.call", {
|
|
326
365
|
"tool.name": event.tool,
|
|
327
366
|
}).then((span) => {
|
|
@@ -343,16 +382,39 @@ export async function instrumentAgentLoop(opts: {
|
|
|
343
382
|
else toolNameToCounters.set(event.tool, [counter]);
|
|
344
383
|
} else if (event.type === "tool_done") {
|
|
345
384
|
const queue = toolNameToCounters.get(event.tool);
|
|
346
|
-
const
|
|
385
|
+
const counterFromId = event.id
|
|
386
|
+
? toolCallIdToCounter.get(event.id)
|
|
387
|
+
: undefined;
|
|
388
|
+
const legacyQueueIndex =
|
|
389
|
+
event.id && counterFromId === undefined && queue
|
|
390
|
+
? queue.findIndex(
|
|
391
|
+
(candidate) => !pendingTools.get(candidate)?.callId,
|
|
392
|
+
)
|
|
393
|
+
: -1;
|
|
394
|
+
const counter =
|
|
395
|
+
counterFromId ??
|
|
396
|
+
(event.id
|
|
397
|
+
? legacyQueueIndex >= 0
|
|
398
|
+
? queue?.[legacyQueueIndex]
|
|
399
|
+
: undefined
|
|
400
|
+
: queue?.shift());
|
|
347
401
|
const pending =
|
|
348
402
|
counter !== undefined ? pendingTools.get(counter) : undefined;
|
|
349
403
|
if (counter !== undefined) {
|
|
350
404
|
pendingTools.delete(counter);
|
|
405
|
+
if (pending?.callId) toolCallIdToCounter.delete(pending.callId);
|
|
406
|
+
if ((counterFromId !== undefined || legacyQueueIndex >= 0) && queue) {
|
|
407
|
+
const queueIndex = queue.indexOf(counter);
|
|
408
|
+
if (queueIndex >= 0) queue.splice(queueIndex, 1);
|
|
409
|
+
}
|
|
351
410
|
if (queue && queue.length === 0)
|
|
352
411
|
toolNameToCounters.delete(event.tool);
|
|
353
412
|
}
|
|
354
413
|
toolCallCount++;
|
|
355
414
|
|
|
415
|
+
const finishedAt = Date.now();
|
|
416
|
+
|
|
417
|
+
const explicitError = event.isError === true;
|
|
356
418
|
const isError =
|
|
357
419
|
typeof event.isError === "boolean"
|
|
358
420
|
? event.isError
|
|
@@ -362,6 +424,24 @@ export async function instrumentAgentLoop(opts: {
|
|
|
362
424
|
if (isError) failedTools++;
|
|
363
425
|
else successfulTools++;
|
|
364
426
|
|
|
427
|
+
if (
|
|
428
|
+
counter !== undefined &&
|
|
429
|
+
counter < MAX_TRACKED_GENERATION_TOOL_CALLS &&
|
|
430
|
+
pending
|
|
431
|
+
) {
|
|
432
|
+
generationToolCalls.set(counter, {
|
|
433
|
+
name: pending.toolName,
|
|
434
|
+
started_offset_ms: Math.max(0, pending.startMs - runStart),
|
|
435
|
+
duration_ms: Math.max(0, finishedAt - pending.startMs),
|
|
436
|
+
status: isError ? "error" : "success",
|
|
437
|
+
error_class: !isError
|
|
438
|
+
? null
|
|
439
|
+
: explicitError
|
|
440
|
+
? "tool_error"
|
|
441
|
+
: "legacy_inferred_error",
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
365
445
|
// Finalize the OTel tool span. If the span promise hasn't resolved yet
|
|
366
446
|
// we record the result on the entry so its `.then` handler ends it.
|
|
367
447
|
const otelEndResult = {
|
|
@@ -392,7 +472,7 @@ export async function instrumentAgentLoop(opts: {
|
|
|
392
472
|
cacheReadTokens: 0,
|
|
393
473
|
cacheWriteTokens: 0,
|
|
394
474
|
costCentsX100: 0,
|
|
395
|
-
durationMs: pending ?
|
|
475
|
+
durationMs: pending ? Math.max(0, finishedAt - pending.startMs) : 0,
|
|
396
476
|
status: isError ? "error" : "success",
|
|
397
477
|
errorMessage: isError ? event.result : null,
|
|
398
478
|
metadata:
|
|
@@ -422,7 +502,11 @@ export async function instrumentAgentLoop(opts: {
|
|
|
422
502
|
let errorMessage: string | null = null;
|
|
423
503
|
let runMetadata: Record<string, unknown> | null = opts.metadata ?? null;
|
|
424
504
|
try {
|
|
425
|
-
usage = await runAgentLoop({
|
|
505
|
+
usage = await runAgentLoop({
|
|
506
|
+
...loopOpts,
|
|
507
|
+
runId,
|
|
508
|
+
send: instrumentedSend,
|
|
509
|
+
});
|
|
426
510
|
} catch (err: any) {
|
|
427
511
|
const classification = opts.classifyError?.(err) ?? null;
|
|
428
512
|
runStatus = classification?.status ?? "error";
|
|
@@ -440,6 +524,62 @@ export async function instrumentAgentLoop(opts: {
|
|
|
440
524
|
const runEnd = Date.now();
|
|
441
525
|
const totalDurationMs = runEnd - runStart;
|
|
442
526
|
|
|
527
|
+
if (pendingTools.size > 0) {
|
|
528
|
+
if (runStatus === "success") {
|
|
529
|
+
runStatus = "error";
|
|
530
|
+
errorMessage ??= "Agent run ended with interrupted tool calls";
|
|
531
|
+
}
|
|
532
|
+
for (const [counter, pending] of pendingTools) {
|
|
533
|
+
toolCallCount += 1;
|
|
534
|
+
failedTools += 1;
|
|
535
|
+
if (counter < MAX_TRACKED_GENERATION_TOOL_CALLS) {
|
|
536
|
+
generationToolCalls.set(counter, {
|
|
537
|
+
name: pending.toolName,
|
|
538
|
+
started_offset_ms: Math.max(0, pending.startMs - runStart),
|
|
539
|
+
duration_ms: Math.max(0, runEnd - pending.startMs),
|
|
540
|
+
status: "error",
|
|
541
|
+
error_class: "interrupted",
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
const interruptedMessage = "Tool call interrupted before completion";
|
|
545
|
+
if (pending.otelSpan) {
|
|
546
|
+
openOtelToolSpans.delete(pending.otelSpan);
|
|
547
|
+
endAgentSpan(pending.otelSpan, {
|
|
548
|
+
status: "error",
|
|
549
|
+
errorMessage: interruptedMessage,
|
|
550
|
+
attributes: { "tool.name": pending.toolName },
|
|
551
|
+
});
|
|
552
|
+
} else {
|
|
553
|
+
pending.endResult = {
|
|
554
|
+
status: "error",
|
|
555
|
+
errorMessage: interruptedMessage,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
spans.push({
|
|
559
|
+
id: pending.spanId,
|
|
560
|
+
runId,
|
|
561
|
+
threadId,
|
|
562
|
+
userId,
|
|
563
|
+
parentSpanId,
|
|
564
|
+
spanType: "tool_call",
|
|
565
|
+
name: pending.toolName,
|
|
566
|
+
inputTokens: 0,
|
|
567
|
+
outputTokens: 0,
|
|
568
|
+
cacheReadTokens: 0,
|
|
569
|
+
cacheWriteTokens: 0,
|
|
570
|
+
costCentsX100: 0,
|
|
571
|
+
durationMs: Math.max(0, runEnd - pending.startMs),
|
|
572
|
+
status: "error",
|
|
573
|
+
errorMessage: interruptedMessage,
|
|
574
|
+
metadata: null,
|
|
575
|
+
createdAt: runEnd,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
pendingTools.clear();
|
|
579
|
+
toolNameToCounters.clear();
|
|
580
|
+
toolCallIdToCounter.clear();
|
|
581
|
+
}
|
|
582
|
+
|
|
443
583
|
let costCentsX100 = 0;
|
|
444
584
|
try {
|
|
445
585
|
const { calculateCost } = await import("../usage/store.js");
|
|
@@ -455,8 +595,15 @@ export async function instrumentAgentLoop(opts: {
|
|
|
455
595
|
} catch {}
|
|
456
596
|
|
|
457
597
|
let llmCallCount = 0;
|
|
458
|
-
if (usage) {
|
|
598
|
+
if (usage || runStatus === "error") {
|
|
459
599
|
llmCallCount = 1;
|
|
600
|
+
const generationUsage = usage ?? {
|
|
601
|
+
inputTokens: 0,
|
|
602
|
+
outputTokens: 0,
|
|
603
|
+
cacheReadTokens: 0,
|
|
604
|
+
cacheWriteTokens: 0,
|
|
605
|
+
model: loopOpts.model,
|
|
606
|
+
};
|
|
460
607
|
const llmSpanId = spanId();
|
|
461
608
|
const llmSpan: TraceSpan = {
|
|
462
609
|
id: llmSpanId,
|
|
@@ -465,11 +612,11 @@ export async function instrumentAgentLoop(opts: {
|
|
|
465
612
|
userId,
|
|
466
613
|
parentSpanId,
|
|
467
614
|
spanType: "llm_call",
|
|
468
|
-
name:
|
|
469
|
-
inputTokens:
|
|
470
|
-
outputTokens:
|
|
471
|
-
cacheReadTokens:
|
|
472
|
-
cacheWriteTokens:
|
|
615
|
+
name: generationUsage.model,
|
|
616
|
+
inputTokens: generationUsage.inputTokens,
|
|
617
|
+
outputTokens: generationUsage.outputTokens,
|
|
618
|
+
cacheReadTokens: generationUsage.cacheReadTokens,
|
|
619
|
+
cacheWriteTokens: generationUsage.cacheWriteTokens,
|
|
473
620
|
costCentsX100,
|
|
474
621
|
durationMs: totalDurationMs,
|
|
475
622
|
status: runStatus,
|
|
@@ -488,11 +635,11 @@ export async function instrumentAgentLoop(opts: {
|
|
|
488
635
|
typeof loopOpts.engine?.name === "string"
|
|
489
636
|
? loopOpts.engine.name
|
|
490
637
|
: undefined,
|
|
491
|
-
model:
|
|
492
|
-
inputTokens:
|
|
493
|
-
outputTokens:
|
|
494
|
-
cacheReadTokens:
|
|
495
|
-
cacheWriteTokens:
|
|
638
|
+
model: generationUsage.model,
|
|
639
|
+
inputTokens: generationUsage.inputTokens,
|
|
640
|
+
outputTokens: generationUsage.outputTokens,
|
|
641
|
+
cacheReadTokens: generationUsage.cacheReadTokens,
|
|
642
|
+
cacheWriteTokens: generationUsage.cacheWriteTokens,
|
|
496
643
|
costCentsX100,
|
|
497
644
|
durationMs: totalDurationMs,
|
|
498
645
|
status: runStatus,
|
|
@@ -500,6 +647,12 @@ export async function instrumentAgentLoop(opts: {
|
|
|
500
647
|
toolCalls: toolCallCount,
|
|
501
648
|
successfulTools,
|
|
502
649
|
failedTools,
|
|
650
|
+
tools: [...generationToolCalls.entries()]
|
|
651
|
+
.sort(([a], [b]) => a - b)
|
|
652
|
+
.map(([, detail]) => detail),
|
|
653
|
+
toolsTruncated:
|
|
654
|
+
toolInvocationCounter > MAX_TRACKED_GENERATION_TOOL_CALLS,
|
|
655
|
+
delegation: opts.delegation,
|
|
503
656
|
createdAt: runStart,
|
|
504
657
|
experimentAssignments: opts.experimentAssignments,
|
|
505
658
|
modelSelectionSource: opts.modelSelectionSource,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
A2ATaskTimeoutError,
|
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
signA2AToken,
|
|
8
8
|
} from "../a2a/client.js";
|
|
9
9
|
import { invokeAgentAction } from "../a2a/invoke.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
A2AApprovedAction,
|
|
12
|
+
A2ACorrelationMetadata,
|
|
13
|
+
Task,
|
|
14
|
+
} from "../a2a/types.js";
|
|
11
15
|
import {
|
|
12
16
|
formatLlmCredentialErrorMessage,
|
|
13
17
|
isLlmCredentialError,
|
|
@@ -28,6 +32,38 @@ const DEFAULT_SERVERLESS_INTEGRATION_A2A_TIMEOUT_MS = 18_000;
|
|
|
28
32
|
const NETLIFY_INTEGRATION_A2A_TIMEOUT_MS = 2_000;
|
|
29
33
|
const INTEGRATION_A2A_TOKEN_TTL = "30m";
|
|
30
34
|
|
|
35
|
+
function buildDelegationCorrelation(
|
|
36
|
+
context: ActionRunContext | undefined,
|
|
37
|
+
selfAppId: string | undefined,
|
|
38
|
+
invocationId?: string,
|
|
39
|
+
): A2ACorrelationMetadata {
|
|
40
|
+
return {
|
|
41
|
+
...(selfAppId?.trim() ? { callerApp: selfAppId.trim() } : {}),
|
|
42
|
+
...(context?.threadId ? { callerThreadId: context.threadId } : {}),
|
|
43
|
+
...(context?.runId ? { parentRunId: context.runId } : {}),
|
|
44
|
+
...(context?.turnId ? { parentTurnId: context.turnId } : {}),
|
|
45
|
+
...(invocationId ? { invocationId } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildMessageIdempotencyKey(
|
|
50
|
+
originatingTurnId: string | undefined,
|
|
51
|
+
target: string,
|
|
52
|
+
exactMessage: string,
|
|
53
|
+
): string | undefined {
|
|
54
|
+
if (!originatingTurnId) return undefined;
|
|
55
|
+
const digest = createHash("sha256")
|
|
56
|
+
.update(
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
originatingTurnId,
|
|
59
|
+
target,
|
|
60
|
+
message: exactMessage,
|
|
61
|
+
}),
|
|
62
|
+
)
|
|
63
|
+
.digest("hex");
|
|
64
|
+
return `v1:${digest}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
31
67
|
function parseTimeoutMs(value: string | undefined): number | undefined {
|
|
32
68
|
if (!value) return undefined;
|
|
33
69
|
const parsed = Number(value);
|
|
@@ -187,6 +223,12 @@ export async function run(
|
|
|
187
223
|
return `Error: Agent "${agentIdOrName}" not found. Available agents: ${available || "(none)"}`;
|
|
188
224
|
}
|
|
189
225
|
|
|
226
|
+
const correlation = buildDelegationCorrelation(context, selfAppId);
|
|
227
|
+
const idempotencyKey =
|
|
228
|
+
message && !taskId
|
|
229
|
+
? buildMessageIdempotencyKey(context?.turnId, agent.url, message)
|
|
230
|
+
: undefined;
|
|
231
|
+
|
|
190
232
|
if (action) {
|
|
191
233
|
if (context?.send) {
|
|
192
234
|
context.send({ type: "agent_call", agent: agent.name, status: "start" });
|
|
@@ -196,6 +238,7 @@ export async function run(
|
|
|
196
238
|
agent,
|
|
197
239
|
action,
|
|
198
240
|
input as Record<string, unknown>,
|
|
241
|
+
buildDelegationCorrelation(context, selfAppId, randomUUID()),
|
|
199
242
|
);
|
|
200
243
|
return output;
|
|
201
244
|
} finally {
|
|
@@ -389,6 +432,9 @@ export async function run(
|
|
|
389
432
|
orgDomain: callerOrgDomain,
|
|
390
433
|
orgSecret: callerOrgSecret,
|
|
391
434
|
approvedActions,
|
|
435
|
+
contextId: context.threadId,
|
|
436
|
+
correlation,
|
|
437
|
+
idempotencyKey,
|
|
392
438
|
...(taskId ? { taskId } : {}),
|
|
393
439
|
onUpdate: onRemotePollUpdate,
|
|
394
440
|
returnRecoverableArtifactsOnTimeout: false,
|
|
@@ -480,6 +526,9 @@ export async function run(
|
|
|
480
526
|
orgDomain: domain,
|
|
481
527
|
orgSecret,
|
|
482
528
|
approvedActions,
|
|
529
|
+
contextId: context?.threadId,
|
|
530
|
+
correlation,
|
|
531
|
+
idempotencyKey,
|
|
483
532
|
...(taskId ? { taskId } : {}),
|
|
484
533
|
returnRecoverableArtifactsOnTimeout: false,
|
|
485
534
|
});
|
|
@@ -514,6 +563,7 @@ async function invokeReadOnlyAppAction(
|
|
|
514
563
|
agent: { name: string; url: string },
|
|
515
564
|
action: string,
|
|
516
565
|
input: Record<string, unknown>,
|
|
566
|
+
correlation: A2ACorrelationMetadata,
|
|
517
567
|
): Promise<string> {
|
|
518
568
|
const callerEmail = getRequestUserEmail();
|
|
519
569
|
if (!callerEmail) {
|
|
@@ -544,6 +594,7 @@ async function invokeReadOnlyAppAction(
|
|
|
544
594
|
userEmail: callerEmail,
|
|
545
595
|
orgDomain: callerOrgDomain,
|
|
546
596
|
orgSecret: callerOrgSecret,
|
|
597
|
+
correlation,
|
|
547
598
|
});
|
|
548
599
|
return invocation.result.status === "completed"
|
|
549
600
|
? invocation.result.output
|
|
@@ -191,34 +191,81 @@ function formatA2ATerminalError(
|
|
|
191
191
|
|
|
192
192
|
type A2AAgentLoopRunner = typeof runAgentLoopDirectWithSoftTimeout;
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
export interface DelegatedAgentLoopTelemetry {
|
|
195
|
+
runId: string;
|
|
196
|
+
threadId: string | null;
|
|
197
|
+
userId: string | null;
|
|
198
|
+
delegation: {
|
|
199
|
+
protocol: "a2a" | "mcp";
|
|
200
|
+
callerApp?: string;
|
|
201
|
+
taskId?: string;
|
|
202
|
+
parentRunId?: string;
|
|
203
|
+
parentTurnId?: string;
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
interface DelegatedAgentLoopOptions {
|
|
208
|
+
runner?: A2AAgentLoopRunner;
|
|
209
|
+
telemetry?: DelegatedAgentLoopTelemetry;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function runDelegatedAgentLoop(
|
|
195
213
|
runOptions: Parameters<A2AAgentLoopRunner>[0],
|
|
196
214
|
pluginOptions: Pick<
|
|
197
215
|
AgentChatPluginOptions,
|
|
198
216
|
"finalResponseGuard" | "runSoftTimeoutMs"
|
|
199
217
|
>,
|
|
200
218
|
timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
|
|
201
|
-
|
|
219
|
+
options: DelegatedAgentLoopOptions,
|
|
202
220
|
) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
timeoutOptions
|
|
221
|
-
|
|
221
|
+
const runner = options.runner ?? runAgentLoopDirectWithSoftTimeout;
|
|
222
|
+
const resolvedRunOptions = {
|
|
223
|
+
...runOptions,
|
|
224
|
+
// Delegated runs resolve their own model and do not pass through the
|
|
225
|
+
// interactive request handler's output-token setup. Use the same
|
|
226
|
+
// model-aware headroom here so reasoning models (notably GPT-5.x) do
|
|
227
|
+
// not spend the small internal default entirely on reasoning before
|
|
228
|
+
// emitting a tool call or answer. Preserve explicit test/caller values.
|
|
229
|
+
maxOutputTokens:
|
|
230
|
+
runOptions.maxOutputTokens ??
|
|
231
|
+
resolveMainChatMaxOutputTokens(runOptions.model),
|
|
232
|
+
reasoningEffort:
|
|
233
|
+
runOptions.reasoningEffort ??
|
|
234
|
+
resolveAgentRequestReasoningEffort({ model: runOptions.model }),
|
|
235
|
+
finalResponseGuard: pluginOptions.finalResponseGuard,
|
|
236
|
+
};
|
|
237
|
+
const execute = (loopOptions = resolvedRunOptions) =>
|
|
238
|
+
runner(loopOptions, pluginOptions.runSoftTimeoutMs, timeoutOptions);
|
|
239
|
+
|
|
240
|
+
if (!options.telemetry) return execute();
|
|
241
|
+
|
|
242
|
+
let instrumented = false;
|
|
243
|
+
try {
|
|
244
|
+
const { getObservabilityConfig, instrumentAgentLoop } =
|
|
245
|
+
await import("../../observability/traces.js");
|
|
246
|
+
const config = await getObservabilityConfig();
|
|
247
|
+
if (config.enabled) {
|
|
248
|
+
instrumented = true;
|
|
249
|
+
return await instrumentAgentLoop({
|
|
250
|
+
runAgentLoop: (loopOptions) =>
|
|
251
|
+
execute(
|
|
252
|
+
loopOptions as Parameters<A2AAgentLoopRunner>[0] &
|
|
253
|
+
typeof resolvedRunOptions,
|
|
254
|
+
),
|
|
255
|
+
loopOpts: resolvedRunOptions,
|
|
256
|
+
runId: options.telemetry.runId,
|
|
257
|
+
threadId: options.telemetry.threadId,
|
|
258
|
+
userId: options.telemetry.userId,
|
|
259
|
+
config,
|
|
260
|
+
delegation: options.telemetry.delegation,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
// Match interactive chat: setup failures fall through, but a failure from
|
|
265
|
+
// inside an instrumented agent loop is the real run failure and rethrows.
|
|
266
|
+
if (instrumented) throw error;
|
|
267
|
+
}
|
|
268
|
+
return execute();
|
|
222
269
|
}
|
|
223
270
|
|
|
224
271
|
/**
|
|
@@ -236,13 +283,13 @@ export function runA2AAgentLoop(
|
|
|
236
283
|
"finalResponseGuard" | "runSoftTimeoutMs"
|
|
237
284
|
>,
|
|
238
285
|
timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
|
|
239
|
-
|
|
286
|
+
options: DelegatedAgentLoopOptions = {},
|
|
240
287
|
) {
|
|
241
288
|
return runDelegatedAgentLoop(
|
|
242
289
|
runOptions,
|
|
243
290
|
pluginOptions,
|
|
244
291
|
timeoutOptions,
|
|
245
|
-
|
|
292
|
+
options,
|
|
246
293
|
);
|
|
247
294
|
}
|
|
248
295
|
|
|
@@ -258,13 +305,13 @@ export function runMCPAgentLoop(
|
|
|
258
305
|
"finalResponseGuard" | "runSoftTimeoutMs"
|
|
259
306
|
>,
|
|
260
307
|
timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
|
|
261
|
-
|
|
308
|
+
options: DelegatedAgentLoopOptions = {},
|
|
262
309
|
) {
|
|
263
310
|
return runDelegatedAgentLoop(
|
|
264
311
|
runOptions,
|
|
265
312
|
pluginOptions,
|
|
266
313
|
timeoutOptions,
|
|
267
|
-
|
|
314
|
+
options,
|
|
268
315
|
);
|
|
269
316
|
}
|
|
270
317
|
|