@agent-native/core 0.118.1 → 0.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +11 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
  4. package/corpus/core/docs/content/template-chat.mdx +1 -1
  5. package/corpus/core/docs/content/template-plan.mdx +3 -3
  6. package/corpus/core/package.json +1 -1
  7. package/corpus/core/src/a2a/activity.ts +371 -0
  8. package/corpus/core/src/a2a/client.ts +3 -1
  9. package/corpus/core/src/a2a/index.ts +21 -0
  10. package/corpus/core/src/a2a/types.ts +8 -0
  11. package/corpus/core/src/agent/model-config.ts +19 -23
  12. package/corpus/core/src/agent/production-agent.ts +167 -100
  13. package/corpus/core/src/agent/types.ts +16 -1
  14. package/corpus/core/src/client/chat/tool-call-display.tsx +190 -35
  15. package/corpus/core/src/client/chat-model-groups.ts +67 -27
  16. package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
  17. package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
  18. package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
  19. package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
  20. package/corpus/core/src/client/sse-event-processor.ts +85 -1
  21. package/corpus/core/src/localization/default-messages.ts +6 -0
  22. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  23. package/corpus/core/src/scripts/call-agent.ts +49 -6
  24. package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
  25. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  26. package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
  27. package/corpus/templates/clips/server/plugins/db.ts +9 -1
  28. package/dist/a2a/activity.d.ts +50 -0
  29. package/dist/a2a/activity.d.ts.map +1 -0
  30. package/dist/a2a/activity.js +236 -0
  31. package/dist/a2a/activity.js.map +1 -0
  32. package/dist/a2a/client.d.ts +2 -0
  33. package/dist/a2a/client.d.ts.map +1 -1
  34. package/dist/a2a/client.js +1 -1
  35. package/dist/a2a/client.js.map +1 -1
  36. package/dist/a2a/index.d.ts +2 -1
  37. package/dist/a2a/index.d.ts.map +1 -1
  38. package/dist/a2a/index.js +1 -0
  39. package/dist/a2a/index.js.map +1 -1
  40. package/dist/a2a/types.d.ts +1 -0
  41. package/dist/a2a/types.d.ts.map +1 -1
  42. package/dist/a2a/types.js.map +1 -1
  43. package/dist/agent/engine/anthropic-engine.d.ts +1 -1
  44. package/dist/agent/engine/builder-engine.d.ts +2 -2
  45. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  46. package/dist/agent/model-config.d.ts +16 -16
  47. package/dist/agent/model-config.d.ts.map +1 -1
  48. package/dist/agent/model-config.js +19 -23
  49. package/dist/agent/model-config.js.map +1 -1
  50. package/dist/agent/production-agent.d.ts +26 -0
  51. package/dist/agent/production-agent.d.ts.map +1 -1
  52. package/dist/agent/production-agent.js +137 -80
  53. package/dist/agent/production-agent.js.map +1 -1
  54. package/dist/agent/types.d.ts +10 -0
  55. package/dist/agent/types.d.ts.map +1 -1
  56. package/dist/agent/types.js.map +1 -1
  57. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  58. package/dist/client/chat/tool-call-display.js +63 -14
  59. package/dist/client/chat/tool-call-display.js.map +1 -1
  60. package/dist/client/chat-model-groups.d.ts.map +1 -1
  61. package/dist/client/chat-model-groups.js +44 -21
  62. package/dist/client/chat-model-groups.js.map +1 -1
  63. package/dist/client/sharing/ShareButton.d.ts +1 -1
  64. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  65. package/dist/client/sharing/ShareButton.js +3 -4
  66. package/dist/client/sharing/ShareButton.js.map +1 -1
  67. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  68. package/dist/client/sharing/ShareDialog.js +1 -2
  69. package/dist/client/sharing/ShareDialog.js.map +1 -1
  70. package/dist/client/sharing/useShareButtonController.d.ts +0 -1
  71. package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
  72. package/dist/client/sharing/useShareButtonController.js +0 -6
  73. package/dist/client/sharing/useShareButtonController.js.map +1 -1
  74. package/dist/client/sharing/useShareDialogController.d.ts +0 -1
  75. package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
  76. package/dist/client/sharing/useShareDialogController.js +0 -1
  77. package/dist/client/sharing/useShareDialogController.js.map +1 -1
  78. package/dist/client/sse-event-processor.d.ts +12 -0
  79. package/dist/client/sse-event-processor.d.ts.map +1 -1
  80. package/dist/client/sse-event-processor.js +63 -1
  81. package/dist/client/sse-event-processor.js.map +1 -1
  82. package/dist/collab/awareness.d.ts +2 -2
  83. package/dist/collab/awareness.d.ts.map +1 -1
  84. package/dist/collab/routes.d.ts +1 -1
  85. package/dist/collab/struct-routes.d.ts +1 -1
  86. package/dist/localization/default-messages.d.ts +6 -0
  87. package/dist/localization/default-messages.d.ts.map +1 -1
  88. package/dist/localization/default-messages.js +6 -0
  89. package/dist/localization/default-messages.js.map +1 -1
  90. package/dist/notifications/routes.d.ts +2 -2
  91. package/dist/observability/routes.d.ts +1 -1
  92. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  93. package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
  94. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  95. package/dist/scripts/call-agent.d.ts.map +1 -1
  96. package/dist/scripts/call-agent.js +49 -6
  97. package/dist/scripts/call-agent.js.map +1 -1
  98. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  99. package/dist/server/agent-chat-plugin.js +38 -10
  100. package/dist/server/agent-chat-plugin.js.map +1 -1
  101. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  102. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  103. package/docs/content/a2a-protocol.mdx +15 -0
  104. package/docs/content/template-chat.mdx +1 -1
  105. package/docs/content/template-plan.mdx +3 -3
  106. package/package.json +1 -1
  107. package/src/a2a/activity.ts +371 -0
  108. package/src/a2a/client.ts +3 -1
  109. package/src/a2a/index.ts +21 -0
  110. package/src/a2a/types.ts +8 -0
  111. package/src/agent/model-config.ts +19 -23
  112. package/src/agent/production-agent.ts +167 -100
  113. package/src/agent/types.ts +16 -1
  114. package/src/client/chat/tool-call-display.tsx +190 -35
  115. package/src/client/chat-model-groups.ts +67 -27
  116. package/src/client/sharing/ShareButton.tsx +1 -27
  117. package/src/client/sharing/ShareDialog.tsx +0 -16
  118. package/src/client/sharing/useShareButtonController.ts +0 -7
  119. package/src/client/sharing/useShareDialogController.ts +0 -2
  120. package/src/client/sse-event-processor.ts +85 -1
  121. package/src/localization/default-messages.ts +6 -0
  122. package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  123. package/src/scripts/call-agent.ts +49 -6
  124. package/src/server/agent-chat-plugin.ts +50 -10
  125. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  126. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  127. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  128. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  129. package/dist/observability/hosted-model-experiment.js +0 -90
  130. package/dist/observability/hosted-model-experiment.js.map +0 -1
  131. package/src/observability/hosted-model-experiment.ts +0 -118
@@ -40,34 +40,64 @@ function addCurrentModel(
40
40
  return next;
41
41
  }
42
42
 
43
+ const MODEL_COST_ORDER = [
44
+ "luna",
45
+ "terra",
46
+ "sol",
47
+ "haiku",
48
+ "sonnet",
49
+ "opus",
50
+ "fable",
51
+ "flash",
52
+ "pro",
53
+ ] as const;
54
+
55
+ function modelCostRank(model: string): number {
56
+ const normalized = model.toLowerCase();
57
+ const rank = MODEL_COST_ORDER.findIndex((tier) => normalized.includes(tier));
58
+ return rank === -1 ? MODEL_COST_ORDER.length : rank;
59
+ }
60
+
61
+ function sortModelsByCost(models: readonly string[]): string[] {
62
+ return [...models].sort((a, b) => modelCostRank(a) - modelCostRank(b));
63
+ }
64
+
43
65
  function groupBuilderModels(models: readonly string[]): EngineModelGroup[] {
44
- const claude = models.filter((model) => model.startsWith("claude-"));
45
- const openai = models.filter((model) => model.startsWith("gpt-"));
46
- const gemini = models.filter((model) => model.startsWith("gemini-"));
47
- const other = models.filter(
48
- (model) =>
49
- !model.startsWith("claude-") &&
50
- !model.startsWith("gpt-") &&
51
- !model.startsWith("gemini-"),
66
+ const claude = sortModelsByCost(
67
+ models.filter((model) => model.startsWith("claude-")),
68
+ );
69
+ const openai = sortModelsByCost(
70
+ models.filter((model) => model.startsWith("gpt-")),
71
+ );
72
+ const gemini = sortModelsByCost(
73
+ models.filter((model) => model.startsWith("gemini-")),
74
+ );
75
+ const other = sortModelsByCost(
76
+ models.filter(
77
+ (model) =>
78
+ !model.startsWith("claude-") &&
79
+ !model.startsWith("gpt-") &&
80
+ !model.startsWith("gemini-"),
81
+ ),
52
82
  );
53
83
 
54
84
  return [
55
- ...(claude.length
85
+ ...(openai.length
56
86
  ? [
57
87
  {
58
88
  engine: "builder",
59
- label: "Claude",
60
- models: claude,
89
+ label: "OpenAI",
90
+ models: openai,
61
91
  configured: true,
62
92
  },
63
93
  ]
64
94
  : []),
65
- ...(openai.length
95
+ ...(claude.length
66
96
  ? [
67
97
  {
68
98
  engine: "builder",
69
- label: "OpenAI",
70
- models: openai,
99
+ label: "Claude",
100
+ models: claude,
71
101
  configured: true,
72
102
  },
73
103
  ]
@@ -115,16 +145,24 @@ function shouldShowDirectEngine(
115
145
  return true;
116
146
  }
117
147
 
118
- function putOpenRouterLast(
148
+ function modelPickerEngineRank(engine: ChatModelEngineEntry): number {
149
+ if (engine.name === "ai-sdk:openai" || engine.label === "OpenAI") return 0;
150
+ if (
151
+ engine.name === "anthropic" ||
152
+ engine.name === "ai-sdk:anthropic" ||
153
+ engine.label === "Claude"
154
+ ) {
155
+ return 1;
156
+ }
157
+ if (engine.name === "ai-sdk:openrouter") return 100;
158
+ return 2;
159
+ }
160
+
161
+ function sortModelPickerEngines(
119
162
  a: ChatModelEngineEntry,
120
163
  b: ChatModelEngineEntry,
121
164
  ): number {
122
- const aIsOpenRouter = a.name === "ai-sdk:openrouter";
123
- const bIsOpenRouter = b.name === "ai-sdk:openrouter";
124
- if (aIsOpenRouter === bIsOpenRouter) return 0;
125
- if (aIsOpenRouter) return 1;
126
- if (bIsOpenRouter) return -1;
127
- return 0;
165
+ return modelPickerEngineRank(a) - modelPickerEngineRank(b);
128
166
  }
129
167
 
130
168
  export function buildChatModelGroups({
@@ -150,17 +188,19 @@ export function buildChatModelGroups({
150
188
  return engines
151
189
  .filter((engine) => engine.packageInstalled !== false)
152
190
  .filter((engine) => shouldShowDirectEngine(engine, currentEngineName))
153
- .sort(putOpenRouterLast)
191
+ .sort(sortModelPickerEngines)
154
192
  .map((engine) => {
155
193
  const requiredEnvVars = engine.requiredEnvVars ?? [];
156
194
  return {
157
195
  engine: engine.name,
158
196
  label: engine.label,
159
- models: addCurrentModel(
160
- engine.supportedModels ?? [],
161
- engine.name,
162
- currentEngineName,
163
- currentModel,
197
+ models: sortModelsByCost(
198
+ addCurrentModel(
199
+ engine.supportedModels ?? [],
200
+ engine.name,
201
+ currentEngineName,
202
+ currentModel,
203
+ ),
164
204
  ),
165
205
  configured:
166
206
  requiredEnvVars.length === 0 ||
@@ -77,7 +77,7 @@ export interface ShareButtonProps {
77
77
  shareUrlPlacement?: "top" | "bottom";
78
78
  /** Whether to render copyable share URL fields. Defaults to true. */
79
79
  showShareLinks?: boolean;
80
- /** Whether to render the bottom Done button. Defaults to true. */
80
+ /** @deprecated The Done action was removed; share popovers dismiss directly. */
81
81
  showDoneButton?: boolean;
82
82
  /** Optional placeholder shown in the share-URL slot when `shareUrl` is
83
83
  * undefined. Use this to explain *why* there's no link yet (e.g. "Publish
@@ -301,7 +301,6 @@ function SharePanel(
301
301
  const {
302
302
  inviteEmail,
303
303
  setInviteEmail: onInviteEmailChange,
304
- handleOpenChange,
305
304
  activeShareTab,
306
305
  handleShareTabChange,
307
306
  data,
@@ -326,7 +325,6 @@ function SharePanel(
326
325
  handleAdd,
327
326
  handleChangeRole,
328
327
  handleRemove,
329
- handleDone,
330
328
  } = controller;
331
329
  const hasInviteEmail = inviteEmail.trim().length > 0;
332
330
 
@@ -367,7 +365,6 @@ function SharePanel(
367
365
  (Boolean(props.shareUrl) ||
368
366
  Boolean(props.shareUrlPlaceholder) ||
369
367
  Boolean(props.secondaryShareUrl));
370
- const showDoneButton = props.showDoneButton ?? true;
371
368
  const shareUrlPlacement = props.shareUrlPlacement ?? "bottom";
372
369
  const extraTabs = props.shareTabs?.tabs ?? [];
373
370
  const hasTabs = extraTabs.length > 0;
@@ -392,17 +389,6 @@ function SharePanel(
392
389
  <div className="mb-4 h-7 rounded-md bg-muted animate-pulse" />
393
390
  <div className="mb-2 text-sm font-semibold">{generalAccessLabel}</div>
394
391
  <div className="mb-4 h-9 rounded-md bg-muted animate-pulse" />
395
- {showDoneButton ? (
396
- <div className="mt-2 flex justify-end">
397
- <button
398
- type="button"
399
- onClick={() => handleOpenChange(false)}
400
- className={BUTTON_PRIMARY_SM}
401
- >
402
- Done
403
- </button>
404
- </div>
405
- ) : null}
406
392
  </div>
407
393
  ) : (
408
394
  <div>
@@ -579,18 +565,6 @@ function SharePanel(
579
565
  {showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null}
580
566
 
581
567
  {props.shareFooterContent}
582
-
583
- {showDoneButton ? (
584
- <div className="mt-2 flex justify-end">
585
- <button
586
- type="button"
587
- onClick={handleDone}
588
- className={BUTTON_PRIMARY_SM}
589
- >
590
- Done
591
- </button>
592
- </div>
593
- ) : null}
594
568
  </div>
595
569
  );
596
570
 
@@ -61,10 +61,6 @@ const BUTTON_OUTLINE_SM = cn(
61
61
  BUTTON_BASE,
62
62
  "!h-9 !px-3 border border-input bg-background hover:bg-accent hover:text-accent-foreground",
63
63
  );
64
- const BUTTON_PRIMARY_SM = cn(
65
- BUTTON_BASE,
66
- "!h-9 !px-4 bg-primary text-primary-foreground hover:bg-primary/90",
67
- );
68
64
  const BUTTON_GHOST_ICON = cn(
69
65
  BUTTON_BASE,
70
66
  "!h-8 !w-8 !p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground",
@@ -171,18 +167,6 @@ export function ShareDialog({
171
167
  ? (embedTabContent ?? <DefaultEmbedBody controller={controller} />)
172
168
  : null}
173
169
  </div>
174
-
175
- <div className="flex justify-end border-t border-border px-5 py-3">
176
- <ActionButton
177
- type="button"
178
- intent="primary"
179
- emphasis="solid"
180
- onPress={controller.close}
181
- className={BUTTON_PRIMARY_SM}
182
- >
183
- {controller.labels.done}
184
- </ActionButton>
185
- </div>
186
170
  </DesignSystemDialog>
187
171
  );
188
172
  }
@@ -105,7 +105,6 @@ export interface ShareButtonController {
105
105
  handleAdd: () => void;
106
106
  handleChangeRole: (share: ShareButtonShare, role: ShareButtonRole) => void;
107
107
  handleRemove: (share: ShareButtonShare) => void;
108
- handleDone: () => void;
109
108
  }
110
109
 
111
110
  export function useShareButtonController(
@@ -547,11 +546,6 @@ export function useShareButtonController(
547
546
  ],
548
547
  );
549
548
 
550
- const handleDone = useCallback(() => {
551
- if (canManage && inviteEmail.trim()) handleAdd();
552
- handleOpenChange(false);
553
- }, [canManage, handleAdd, handleOpenChange, inviteEmail]);
554
-
555
549
  return {
556
550
  open,
557
551
  handleOpenChange,
@@ -585,7 +579,6 @@ export function useShareButtonController(
585
579
  handleAdd,
586
580
  handleChangeRole,
587
581
  handleRemove,
588
- handleDone,
589
582
  };
590
583
  }
591
584
 
@@ -71,7 +71,6 @@ export interface ShareDialogController {
71
71
  tabs: Array<{ value: ShareDialogTab; label: string }>;
72
72
  labels: {
73
73
  close: string;
74
- done: string;
75
74
  shareOptions: string;
76
75
  generalAccess: string;
77
76
  shareLink: string;
@@ -407,7 +406,6 @@ export function useShareDialogController({
407
406
  ],
408
407
  labels: {
409
408
  close: t("share.close"),
410
- done: t("share.done"),
411
409
  shareOptions: t("share.shareOptions"),
412
410
  generalAccess: t("share.generalAccess"),
413
411
  shareLink: t("share.shareLink"),
@@ -1,5 +1,6 @@
1
1
  import type { ChatModelRunResult } from "@assistant-ui/react";
2
2
 
3
+ import type { A2AAgentActivitySnapshot } from "../a2a/activity.js";
3
4
  import type { ActionChatUIConfig } from "../action-ui.js";
4
5
  import {
5
6
  LLM_MISSING_CREDENTIALS_ERROR_CODE,
@@ -74,6 +75,12 @@ export interface SSEEvent {
74
75
  seq?: number;
75
76
  agent?: string;
76
77
  status?: string;
78
+ state?: string;
79
+ elapsedSeconds?: number;
80
+ detail?: string;
81
+ agentCallId?: string;
82
+ durationMs?: number;
83
+ snapshot?: A2AAgentActivitySnapshot;
77
84
  reason?: string;
78
85
  // Agent task fields
79
86
  taskId?: string;
@@ -100,6 +107,12 @@ export type AgentAutoContinueReason =
100
107
 
101
108
  export type AgentActivityTrailEntry = { label: string; tool?: string };
102
109
 
110
+ export interface AgentCallProgress {
111
+ state: string;
112
+ elapsedSeconds: number;
113
+ detail?: string;
114
+ }
115
+
103
116
  export interface AgentAutoContinueErrorInfo {
104
117
  message: string;
105
118
  details?: string;
@@ -1365,7 +1378,7 @@ export function processEvent(
1365
1378
  if (ev.type === "agent_call") {
1366
1379
  const agentName = ev.agent ?? "agent";
1367
1380
  if (ev.status === "start") {
1368
- const toolCallId = `tc_${++toolCallCounter.value}`;
1381
+ const toolCallId = ev.agentCallId ?? `tc_${++toolCallCounter.value}`;
1369
1382
  content.push({
1370
1383
  type: "tool-call",
1371
1384
  toolCallId,
@@ -1380,9 +1393,16 @@ export function processEvent(
1380
1393
  if (
1381
1394
  part.type === "tool-call" &&
1382
1395
  part.toolName === `agent:${agentName}` &&
1396
+ (!ev.agentCallId || part.toolCallId === ev.agentCallId) &&
1383
1397
  part.result === undefined
1384
1398
  ) {
1385
1399
  part.result = ev.status === "error" ? "Error calling agent" : "Done";
1400
+ part.structuredMeta = {
1401
+ ...part.structuredMeta,
1402
+ ...(ev.durationMs != null
1403
+ ? { agentDurationMs: ev.durationMs }
1404
+ : {}),
1405
+ };
1386
1406
  break;
1387
1407
  }
1388
1408
  }
@@ -1401,6 +1421,7 @@ export function processEvent(
1401
1421
  if (
1402
1422
  part.type === "tool-call" &&
1403
1423
  part.toolName === `agent:${agentName}` &&
1424
+ (!ev.agentCallId || part.toolCallId === ev.agentCallId) &&
1404
1425
  part.result === undefined
1405
1426
  ) {
1406
1427
  part.argsText += ev.text ?? "";
@@ -1413,6 +1434,69 @@ export function processEvent(
1413
1434
  };
1414
1435
  }
1415
1436
 
1437
+ if (ev.type === "agent_call_progress") {
1438
+ const agentName = ev.agent ?? "agent";
1439
+ if (typeof ev.state !== "string") {
1440
+ return { action: "continue" };
1441
+ }
1442
+ const elapsedSeconds =
1443
+ typeof ev.elapsedSeconds === "number" &&
1444
+ Number.isFinite(ev.elapsedSeconds) &&
1445
+ ev.elapsedSeconds >= 0
1446
+ ? ev.elapsedSeconds
1447
+ : 0;
1448
+ for (let i = content.length - 1; i >= 0; i--) {
1449
+ const part = content[i];
1450
+ if (
1451
+ part.type === "tool-call" &&
1452
+ part.toolName === `agent:${agentName}` &&
1453
+ (!ev.agentCallId || part.toolCallId === ev.agentCallId) &&
1454
+ part.result === undefined
1455
+ ) {
1456
+ part.structuredMeta = {
1457
+ ...part.structuredMeta,
1458
+ agentProgress: {
1459
+ state: ev.state,
1460
+ elapsedSeconds,
1461
+ ...(ev.detail ? { detail: ev.detail } : {}),
1462
+ } satisfies AgentCallProgress,
1463
+ };
1464
+ break;
1465
+ }
1466
+ }
1467
+ return {
1468
+ action: "yield",
1469
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
1470
+ };
1471
+ }
1472
+
1473
+ if (ev.type === "agent_call_activity" && ev.snapshot) {
1474
+ const agentName = ev.agent ?? "agent";
1475
+ for (let i = content.length - 1; i >= 0; i--) {
1476
+ const part = content[i];
1477
+ if (
1478
+ part.type === "tool-call" &&
1479
+ part.toolName === `agent:${agentName}` &&
1480
+ (!ev.agentCallId || part.toolCallId === ev.agentCallId)
1481
+ ) {
1482
+ const previous = part.structuredMeta?.agentActivity as
1483
+ | A2AAgentActivitySnapshot
1484
+ | undefined;
1485
+ if (!previous || ev.snapshot.sequence >= previous.sequence) {
1486
+ part.structuredMeta = {
1487
+ ...part.structuredMeta,
1488
+ agentActivity: ev.snapshot,
1489
+ };
1490
+ }
1491
+ break;
1492
+ }
1493
+ }
1494
+ return {
1495
+ action: "yield",
1496
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
1497
+ };
1498
+ }
1499
+
1416
1500
  // ─── Agent task events (sub-agent chips) ─────────────────────────
1417
1501
  // These events are dispatched as CustomEvents so AgentTaskCard components
1418
1502
  // can listen for updates to their specific taskId.
@@ -337,6 +337,12 @@ const messages = {
337
337
  configureProviderKeys: "Configure Anthropic, OpenAI, or another provider",
338
338
  checkingAiConnection: "Checking AI connection...",
339
339
  connectionUnavailable: "Unable to check AI connection. Click to retry.",
340
+ delegatedAgent: {
341
+ asking: "Asking {{name}}...",
342
+ asked: "Asked {{name}}",
343
+ error: "Error asking {{name}}",
344
+ elapsed: "{{duration}} elapsed",
345
+ },
340
346
  voiceMode: {
341
347
  entryButtonLabel: "Use microphone",
342
348
  promptTitle: "Use your voice",
@@ -15,8 +15,6 @@ import {
15
15
  normalizeModelForEngine,
16
16
  } from "../../agent/engine/index.js";
17
17
  import type { ActionTool } from "../../agent/types.js";
18
- import { resolveHostedDefaultModelExperiment } from "../../observability/hosted-model-experiment.js";
19
- import { getRequestUserEmail } from "../../server/request-context.js";
20
18
  import { getSetting } from "../../settings/index.js";
21
19
 
22
20
  export const tool: ActionTool = {
@@ -91,15 +89,6 @@ export async function run(args: Record<string, string> = {}): Promise<string> {
91
89
  currentModelCandidate ?? currentEntry.defaultModel,
92
90
  )
93
91
  : (currentModelCandidate ?? DEFAULT_MODEL);
94
- const hostedExperiment = currentEntry
95
- ? resolveHostedDefaultModelExperiment({
96
- userId: getRequestUserEmail(),
97
- engineName: currentEntry.name,
98
- isDefaultModelSelection: currentModelCandidate == null,
99
- supportedModels: currentEntry.supportedModels,
100
- })
101
- : null;
102
- const effectiveCurrentModel = hostedExperiment?.model ?? currentModel;
103
92
  const result = {
104
93
  engines: engines.map((e) => ({
105
94
  name: e.name,
@@ -116,13 +105,7 @@ export async function run(args: Record<string, string> = {}): Promise<string> {
116
105
  ? null
117
106
  : {
118
107
  engine: currentEngineName,
119
- model: effectiveCurrentModel,
120
- ...(hostedExperiment
121
- ? {
122
- modelSelectionSource: "experiment",
123
- experimentAssignments: [hostedExperiment.assignment],
124
- }
125
- : {}),
108
+ model: currentModel,
126
109
  },
127
110
  };
128
111
 
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
 
3
+ import { parseA2AAgentActivityPart } from "../a2a/activity.js";
3
4
  import {
4
5
  A2ATaskTimeoutError,
5
6
  callAgent,
@@ -271,8 +272,15 @@ export async function run(
271
272
  : undefined;
272
273
 
273
274
  if (action) {
275
+ const agentCallId = randomUUID();
276
+ const startedAt = Date.now();
274
277
  if (context?.send) {
275
- context.send({ type: "agent_call", agent: agent.name, status: "start" });
278
+ context.send({
279
+ type: "agent_call",
280
+ agent: agent.name,
281
+ status: "start",
282
+ agentCallId,
283
+ });
276
284
  }
277
285
  try {
278
286
  const output = await invokeReadOnlyAppAction(
@@ -281,10 +289,24 @@ export async function run(
281
289
  input as Record<string, unknown>,
282
290
  buildDelegationCorrelation(context, selfAppId, randomUUID()),
283
291
  );
292
+ if (context?.send && output) {
293
+ context.send({
294
+ type: "agent_call_text",
295
+ agent: agent.name,
296
+ text: output,
297
+ agentCallId,
298
+ });
299
+ }
284
300
  return output;
285
301
  } finally {
286
302
  if (context?.send) {
287
- context.send({ type: "agent_call", agent: agent.name, status: "done" });
303
+ context.send({
304
+ type: "agent_call",
305
+ agent: agent.name,
306
+ status: "done",
307
+ agentCallId,
308
+ durationMs: Date.now() - startedAt,
309
+ });
288
310
  }
289
311
  }
290
312
  }
@@ -363,6 +385,7 @@ export async function run(
363
385
 
364
386
  let responseText = "";
365
387
  let lastSentLength = 0;
388
+ const agentCallId = randomUUID();
366
389
  const existingContinuationText = taskId
367
390
  ? null
368
391
  : await formatExistingIntegrationContinuationIfRetry(agent, message);
@@ -372,6 +395,7 @@ export async function run(
372
395
  type: "agent_call",
373
396
  agent: agent.name,
374
397
  status: "start",
398
+ agentCallId,
375
399
  });
376
400
 
377
401
  const emitNewText = (newText: string) => {
@@ -380,6 +404,7 @@ export async function run(
380
404
  type: "agent_call_text",
381
405
  agent: agent.name,
382
406
  text: newText.slice(lastSentLength),
407
+ agentCallId,
383
408
  });
384
409
  lastSentLength = newText.length;
385
410
  }
@@ -395,9 +420,10 @@ export async function run(
395
420
  // time we get to the sync fallback, Lambda is dead and the second fetch
396
421
  // errors out as "fetch failed". Async+poll has its own short fetches
397
422
  // with their own budgets, so it works reliably across hosts. The
398
- // trade-off is we lose progressive in-UI text streaming for cross-app
399
- // A2A calls, but the receiving agent's full response still surfaces via
400
- // the tool_result event below.
423
+ // trade-off is that cross-app activity arrives at the poll cadence rather
424
+ // than token-by-token. Agent Native peers attach their current reasoning,
425
+ // tool status, and response preview to each task checkpoint, and the
426
+ // receiver's full response still surfaces below.
401
427
  //
402
428
  // That trade-off has a second-order cost: callAgent()'s poll (see
403
429
  // A2AClient.sendAndWait in a2a/client.ts) can legitimately run for
@@ -450,8 +476,22 @@ export async function run(
450
476
  ]);
451
477
  const callStartedAt = Date.now();
452
478
  let lastProgressEmitAt = callStartedAt;
479
+ let lastActivitySequence = -1;
453
480
  const onRemotePollUpdate = (task: Task) => {
454
481
  const state = task?.status?.state;
482
+ const parts = task.status?.message?.parts;
483
+ const snapshot = Array.isArray(parts)
484
+ ? parts.map(parseA2AAgentActivityPart).find((value) => value !== null)
485
+ : undefined;
486
+ if (snapshot && snapshot.sequence > lastActivitySequence) {
487
+ lastActivitySequence = snapshot.sequence;
488
+ context.send!({
489
+ type: "agent_call_activity",
490
+ agent: agent.name,
491
+ agentCallId,
492
+ snapshot,
493
+ });
494
+ }
455
495
  if (!state || !ACTIVELY_WORKING_STATES.has(state)) return;
456
496
  const now = Date.now();
457
497
  if (now - lastProgressEmitAt < PROGRESS_MIN_INTERVAL_MS) return;
@@ -462,6 +502,7 @@ export async function run(
462
502
  agent: agent.name,
463
503
  state,
464
504
  elapsedSeconds: Math.round((now - callStartedAt) / 1000),
505
+ agentCallId,
465
506
  ...(detail ? { detail } : {}),
466
507
  });
467
508
  };
@@ -549,6 +590,8 @@ export async function run(
549
590
  type: "agent_call",
550
591
  agent: agent.name,
551
592
  status: "done",
593
+ agentCallId,
594
+ durationMs: Date.now() - callStartedAt,
552
595
  });
553
596
 
554
597
  return responseText || "(empty response)";
@@ -718,7 +761,7 @@ async function enqueueIntegrationContinuationIfPossible(
718
761
  const MAX_PROGRESS_DETAIL_CHARS = 200;
719
762
  function extractRemoteProgressDetail(task: Task): string | undefined {
720
763
  const parts = task.status?.message?.parts;
721
- if (!parts) return undefined;
764
+ if (!Array.isArray(parts)) return undefined;
722
765
  const text = parts
723
766
  .filter((p): p is { type: "text"; text: string } => p.type === "text")
724
767
  .map((p) => p.text)