@agent-native/core 0.118.1 → 0.119.1

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 (184) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -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/ingestion/figma-node-to-html.ts +20 -99
  22. package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
  23. package/corpus/core/src/ingestion/index.ts +20 -0
  24. package/corpus/core/src/localization/default-messages.ts +6 -0
  25. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  26. package/corpus/core/src/scripts/call-agent.ts +49 -6
  27. package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  29. package/corpus/templates/assets/app/routes/library.tsx +17 -9
  30. package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
  31. package/corpus/templates/clips/server/plugins/db.ts +9 -1
  32. package/corpus/templates/design/AGENTS.md +18 -5
  33. package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
  34. package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
  35. package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
  36. package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
  37. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
  38. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
  39. package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
  40. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
  41. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
  42. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
  43. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
  44. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  45. package/corpus/templates/design/app/i18n-data.ts +150 -2
  46. package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
  47. package/corpus/templates/design/app/lib/design-import.ts +19 -1
  48. package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
  49. package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
  50. package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
  51. package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
  52. package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
  53. package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
  54. package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
  55. package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
  56. package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
  57. package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
  58. package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
  59. package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
  60. package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
  61. package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
  62. package/corpus/templates/design/server/register-secrets.ts +14 -2
  63. package/dist/a2a/activity.d.ts +50 -0
  64. package/dist/a2a/activity.d.ts.map +1 -0
  65. package/dist/a2a/activity.js +236 -0
  66. package/dist/a2a/activity.js.map +1 -0
  67. package/dist/a2a/client.d.ts +2 -0
  68. package/dist/a2a/client.d.ts.map +1 -1
  69. package/dist/a2a/client.js +1 -1
  70. package/dist/a2a/client.js.map +1 -1
  71. package/dist/a2a/index.d.ts +2 -1
  72. package/dist/a2a/index.d.ts.map +1 -1
  73. package/dist/a2a/index.js +1 -0
  74. package/dist/a2a/index.js.map +1 -1
  75. package/dist/a2a/types.d.ts +1 -0
  76. package/dist/a2a/types.d.ts.map +1 -1
  77. package/dist/a2a/types.js.map +1 -1
  78. package/dist/agent/engine/anthropic-engine.d.ts +1 -1
  79. package/dist/agent/engine/builder-engine.d.ts +2 -2
  80. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  81. package/dist/agent/model-config.d.ts +16 -16
  82. package/dist/agent/model-config.d.ts.map +1 -1
  83. package/dist/agent/model-config.js +19 -23
  84. package/dist/agent/model-config.js.map +1 -1
  85. package/dist/agent/production-agent.d.ts +26 -0
  86. package/dist/agent/production-agent.d.ts.map +1 -1
  87. package/dist/agent/production-agent.js +137 -80
  88. package/dist/agent/production-agent.js.map +1 -1
  89. package/dist/agent/types.d.ts +10 -0
  90. package/dist/agent/types.d.ts.map +1 -1
  91. package/dist/agent/types.js.map +1 -1
  92. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  93. package/dist/client/chat/tool-call-display.js +63 -14
  94. package/dist/client/chat/tool-call-display.js.map +1 -1
  95. package/dist/client/chat-model-groups.d.ts.map +1 -1
  96. package/dist/client/chat-model-groups.js +44 -21
  97. package/dist/client/chat-model-groups.js.map +1 -1
  98. package/dist/client/sharing/ShareButton.d.ts +1 -1
  99. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  100. package/dist/client/sharing/ShareButton.js +3 -4
  101. package/dist/client/sharing/ShareButton.js.map +1 -1
  102. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  103. package/dist/client/sharing/ShareDialog.js +1 -2
  104. package/dist/client/sharing/ShareDialog.js.map +1 -1
  105. package/dist/client/sharing/useShareButtonController.d.ts +0 -1
  106. package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
  107. package/dist/client/sharing/useShareButtonController.js +0 -6
  108. package/dist/client/sharing/useShareButtonController.js.map +1 -1
  109. package/dist/client/sharing/useShareDialogController.d.ts +0 -1
  110. package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
  111. package/dist/client/sharing/useShareDialogController.js +0 -1
  112. package/dist/client/sharing/useShareDialogController.js.map +1 -1
  113. package/dist/client/sse-event-processor.d.ts +12 -0
  114. package/dist/client/sse-event-processor.d.ts.map +1 -1
  115. package/dist/client/sse-event-processor.js +63 -1
  116. package/dist/client/sse-event-processor.js.map +1 -1
  117. package/dist/collab/routes.d.ts +1 -1
  118. package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
  119. package/dist/ingestion/figma-node-to-html.js +14 -88
  120. package/dist/ingestion/figma-node-to-html.js.map +1 -1
  121. package/dist/ingestion/figma-paint-math.d.ts +157 -0
  122. package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
  123. package/dist/ingestion/figma-paint-math.js +274 -0
  124. package/dist/ingestion/figma-paint-math.js.map +1 -0
  125. package/dist/ingestion/index.d.ts +1 -0
  126. package/dist/ingestion/index.d.ts.map +1 -1
  127. package/dist/ingestion/index.js +1 -0
  128. package/dist/ingestion/index.js.map +1 -1
  129. package/dist/localization/default-messages.d.ts +6 -0
  130. package/dist/localization/default-messages.d.ts.map +1 -1
  131. package/dist/localization/default-messages.js +6 -0
  132. package/dist/localization/default-messages.js.map +1 -1
  133. package/dist/notifications/routes.d.ts +2 -2
  134. package/dist/observability/routes.d.ts +5 -5
  135. package/dist/progress/routes.d.ts +1 -1
  136. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  137. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  138. package/dist/resources/handlers.d.ts +1 -1
  139. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  140. package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
  141. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  142. package/dist/scripts/call-agent.d.ts.map +1 -1
  143. package/dist/scripts/call-agent.js +49 -6
  144. package/dist/scripts/call-agent.js.map +1 -1
  145. package/dist/secrets/routes.d.ts +3 -3
  146. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  147. package/dist/server/agent-chat-plugin.js +38 -10
  148. package/dist/server/agent-chat-plugin.js.map +1 -1
  149. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  150. package/dist/server/realtime-token.d.ts +1 -1
  151. package/dist/server/transcribe-voice.d.ts +1 -1
  152. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  153. package/docs/content/a2a-protocol.mdx +15 -0
  154. package/docs/content/template-chat.mdx +1 -1
  155. package/docs/content/template-plan.mdx +3 -3
  156. package/package.json +1 -1
  157. package/src/a2a/activity.ts +371 -0
  158. package/src/a2a/client.ts +3 -1
  159. package/src/a2a/index.ts +21 -0
  160. package/src/a2a/types.ts +8 -0
  161. package/src/agent/model-config.ts +19 -23
  162. package/src/agent/production-agent.ts +167 -100
  163. package/src/agent/types.ts +16 -1
  164. package/src/client/chat/tool-call-display.tsx +190 -35
  165. package/src/client/chat-model-groups.ts +67 -27
  166. package/src/client/sharing/ShareButton.tsx +1 -27
  167. package/src/client/sharing/ShareDialog.tsx +0 -16
  168. package/src/client/sharing/useShareButtonController.ts +0 -7
  169. package/src/client/sharing/useShareDialogController.ts +0 -2
  170. package/src/client/sse-event-processor.ts +85 -1
  171. package/src/ingestion/figma-node-to-html.ts +20 -99
  172. package/src/ingestion/figma-paint-math.ts +371 -0
  173. package/src/ingestion/index.ts +20 -0
  174. package/src/localization/default-messages.ts +6 -0
  175. package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
  176. package/src/scripts/call-agent.ts +49 -6
  177. package/src/server/agent-chat-plugin.ts +50 -10
  178. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
  179. package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
  180. package/dist/observability/hosted-model-experiment.d.ts +0 -39
  181. package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
  182. package/dist/observability/hosted-model-experiment.js +0 -90
  183. package/dist/observability/hosted-model-experiment.js.map +0 -1
  184. 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.
@@ -62,6 +62,15 @@
62
62
  * mismatch.
63
63
  */
64
64
 
65
+ import {
66
+ cssBlendMode,
67
+ gradientAngleDegrees as gradientAngleDegreesMath,
68
+ remapLinearStopPosition as remapLinearStopPositionMath,
69
+ resolveGradientHandles,
70
+ vectorLength,
71
+ type GradientHandles,
72
+ } from "./figma-paint-math.js";
73
+
65
74
  export interface FigmaColor {
66
75
  r: number;
67
76
  g: number;
@@ -422,16 +431,8 @@ class FidelityTracker {
422
431
  // Gradient angle / position derivation
423
432
  // ---------------------------------------------------------------------------
424
433
 
425
- interface GradientGeometry {
426
- start: { x: number; y: number };
427
- end: { x: number; y: number };
428
- width: { x: number; y: number };
429
- }
430
-
431
- function resolveGradientGeometry(paint: FigmaPaint): GradientGeometry | null {
432
- const handles = paint.gradientHandlePositions;
433
- if (!handles || handles.length < 3) return null;
434
- return { start: handles[0]!, end: handles[1]!, width: handles[2]! };
434
+ function resolveGradientGeometry(paint: FigmaPaint): GradientHandles | null {
435
+ return resolveGradientHandles(paint.gradientHandlePositions);
435
436
  }
436
437
 
437
438
  /**
@@ -453,13 +454,7 @@ export function gradientAngleDegrees(
453
454
  paint: FigmaPaint,
454
455
  box: { width: number; height: number },
455
456
  ): number | null {
456
- const geometry = resolveGradientGeometry(paint);
457
- if (!geometry) return null;
458
- const dx = (geometry.end.x - geometry.start.x) * box.width;
459
- const dy = (geometry.end.y - geometry.start.y) * box.height;
460
- const angleRad = Math.atan2(dy, dx);
461
- const angleDeg = (angleRad * 180) / Math.PI + 90;
462
- return ((angleDeg % 360) + 360) % 360;
457
+ return gradientAngleDegreesMath(paint, box);
463
458
  }
464
459
 
465
460
  function gradientStopsCss(
@@ -478,59 +473,12 @@ function gradientStopsCss(
478
473
  .join(", ");
479
474
  }
480
475
 
481
- /**
482
- * CSS `linear-gradient(angle, ...)` always stretches its 0%/100% stops
483
- * across the box's FULL diagonal extent at that angle (the CSS spec's
484
- * "gradient line" always spans corner-to-corner) -- it has no way to say
485
- * "start partway in, end partway in" the way Figma's actual gradient handles
486
- * can (a designer can drag the start/end handles anywhere, including short
487
- * of the shape's edges, or past them). Figma's own stop positions are
488
- * fractions of the literal start-handle-to-end-handle distance, which only
489
- * happens to coincide with the CSS full-box span when the handles are
490
- * dragged exactly corner-to-corner -- a common case, but far from the only
491
- * one, and the divergence gets worse the more the box's aspect ratio departs
492
- * from square (rotated/skewed handles included, e.g. gradientTransform-authored
493
- * paints). This projects each Figma stop's real pixel position onto the same
494
- * angle CSS will use and re-expresses it as a percentage of the CSS line's
495
- * length, so a partial/offset gradient renders at the same actual pixel
496
- * positions Figma draws it at instead of silently stretching to fill the box.
497
- */
498
476
  function remapLinearStopPosition(
499
- geometry: GradientGeometry,
477
+ geometry: GradientHandles,
500
478
  box: { width: number; height: number },
501
479
  angleDeg: number,
502
480
  ): (position: number) => number {
503
- const angleRad = (angleDeg * Math.PI) / 180;
504
- const ux = Math.sin(angleRad);
505
- const uy = -Math.cos(angleRad);
506
- const lineLength = box.width * Math.abs(ux) + box.height * Math.abs(uy);
507
- if (lineLength < 1e-6) return (position) => position;
508
- const startPx = {
509
- x: geometry.start.x * box.width,
510
- y: geometry.start.y * box.height,
511
- };
512
- const endPx = {
513
- x: geometry.end.x * box.width,
514
- y: geometry.end.y * box.height,
515
- };
516
- const centerX = box.width / 2;
517
- const centerY = box.height / 2;
518
- return (position: number) => {
519
- const pointX = startPx.x + position * (endPx.x - startPx.x);
520
- const pointY = startPx.y + position * (endPx.y - startPx.y);
521
- const projected = (pointX - centerX) * ux + (pointY - centerY) * uy;
522
- return (projected + lineLength / 2) / lineLength;
523
- };
524
- }
525
-
526
- function vectorLength(
527
- from: { x: number; y: number },
528
- to: { x: number; y: number },
529
- box: { width: number; height: number },
530
- ): number {
531
- const dx = (to.x - from.x) * box.width;
532
- const dy = (to.y - from.y) * box.height;
533
- return Math.sqrt(dx * dx + dy * dy);
481
+ return remapLinearStopPositionMath(geometry, box, angleDeg);
534
482
  }
535
483
 
536
484
  /**
@@ -960,49 +908,22 @@ function buildEffects(
960
908
  // Blend modes
961
909
  // ---------------------------------------------------------------------------
962
910
 
963
- const CSS_BLEND_MODES = new Set([
964
- "multiply",
965
- "screen",
966
- "overlay",
967
- "darken",
968
- "lighten",
969
- "color-dodge",
970
- "color-burn",
971
- "hard-light",
972
- "soft-light",
973
- "difference",
974
- "exclusion",
975
- "hue",
976
- "saturation",
977
- "color",
978
- "luminosity",
979
- ]);
980
-
981
- const FIGMA_ONLY_BLEND_MODE_FALLBACK: Record<string, string> = {
982
- LINEAR_BURN: "multiply",
983
- LINEAR_DODGE: "plus-lighter",
984
- LIGHTER: "plus-lighter",
985
- DARKER: "darken",
986
- };
987
-
988
911
  function buildBlendMode(
989
912
  node: FigmaNode,
990
913
  tracker: FidelityTracker,
991
914
  ): string | undefined {
992
915
  const mode = node.blendMode;
993
- if (!mode || mode === "PASS_THROUGH" || mode === "NORMAL") return undefined;
994
- const cssMode = mode.toLowerCase().replace(/_/g, "-");
995
- if (CSS_BLEND_MODES.has(cssMode)) return cssMode;
996
- const fallback = FIGMA_ONLY_BLEND_MODE_FALLBACK[mode];
997
- if (fallback) {
916
+ if (!mode) return undefined;
917
+ const result = cssBlendMode(mode);
918
+ if (!result) return undefined;
919
+ if (result.verdict === "approximated") {
998
920
  tracker.record(
999
921
  node,
1000
922
  "approximated",
1001
- `Figma blend mode "${mode}" has no CSS equivalent; approximated as mix-blend-mode: ${fallback}.`,
923
+ `Figma blend mode "${mode}" has no CSS equivalent; approximated as mix-blend-mode: ${result.cssMode}.`,
1002
924
  );
1003
- return fallback;
1004
925
  }
1005
- return undefined;
926
+ return result.cssMode;
1006
927
  }
1007
928
 
1008
929
  // ---------------------------------------------------------------------------