@agent-native/core 0.84.20 → 0.84.22

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 (193) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/caller-auth.ts +37 -8
  5. package/corpus/core/src/a2a/client.ts +178 -67
  6. package/corpus/core/src/agent/production-agent.ts +19 -3
  7. package/corpus/core/src/agent/types.ts +1 -1
  8. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  9. package/corpus/core/src/cli/recap.ts +43 -34
  10. package/corpus/core/src/client/AssistantChat.tsx +27 -4
  11. package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
  12. package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
  13. package/corpus/core/src/client/guided-questions.tsx +11 -6
  14. package/corpus/core/src/client/i18n.tsx +10 -5
  15. package/corpus/core/src/client/sse-event-processor.ts +147 -5
  16. package/corpus/core/src/client/tool-display.ts +8 -0
  17. package/corpus/core/src/file-upload/builder.ts +40 -14
  18. package/corpus/core/src/file-upload/types.ts +3 -0
  19. package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
  20. package/corpus/core/src/mcp/builtin-tools.ts +3 -0
  21. package/corpus/core/src/mcp/org-directory.ts +66 -20
  22. package/corpus/core/src/notifications/channels.ts +235 -38
  23. package/corpus/core/src/scripts/call-agent.ts +7 -2
  24. package/corpus/core/src/server/analytics.ts +32 -6
  25. package/corpus/core/src/server/ssr-handler.ts +31 -12
  26. package/corpus/templates/analytics/AGENTS.md +21 -0
  27. package/corpus/templates/analytics/README.md +35 -0
  28. package/corpus/templates/analytics/actions/delete-analytics-alert-rule.ts +23 -0
  29. package/corpus/templates/analytics/actions/list-analytics-alert-rules.ts +20 -0
  30. package/corpus/templates/analytics/actions/run-analytics-alerts.ts +23 -0
  31. package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +69 -0
  32. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
  33. package/corpus/templates/analytics/changelog/2026-07-01-analytics-can-alert-when-first-party-events-spike.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-07-01-session-replays-play-back-in-production-instead-of-showing.md +6 -0
  36. package/corpus/templates/analytics/netlify.toml +3 -0
  37. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +109 -0
  38. package/corpus/templates/analytics/server/db/schema.ts +55 -0
  39. package/corpus/templates/analytics/server/handlers/session-replay.ts +5 -2
  40. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +71 -0
  41. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +734 -0
  42. package/corpus/templates/analytics/server/lib/session-replay.ts +10 -3
  43. package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +54 -0
  44. package/corpus/templates/analytics/server/plugins/db.ts +100 -0
  45. package/corpus/templates/analytics/server/routes/api/analytics-alerts/run.post.ts +60 -0
  46. package/corpus/templates/assets/README.md +30 -8
  47. package/corpus/templates/brain/README.md +20 -552
  48. package/corpus/templates/calendar/README.md +22 -28
  49. package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
  50. package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
  51. package/corpus/templates/chat/README.md +32 -0
  52. package/corpus/templates/clips/README.md +37 -0
  53. package/corpus/templates/clips/actions/create-recording.ts +7 -2
  54. package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
  55. package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
  56. package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
  57. package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
  58. package/corpus/templates/clips/app/components/library/library-grid.tsx +18 -7
  59. package/corpus/templates/clips/app/components/library/recording-card.tsx +42 -5
  60. package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
  61. package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
  62. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
  63. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +106 -2
  64. package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
  65. package/corpus/templates/clips/app/routes/record.tsx +48 -8
  66. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
  68. package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
  69. package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
  70. package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
  71. package/corpus/templates/clips/changelog/2026-07-01-move-to-folder-now-opens-folder-choices-directly-instead-of.md +6 -0
  72. package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
  73. package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
  74. package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
  75. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  76. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
  77. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +64 -6
  78. package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
  79. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
  80. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +51 -1
  81. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
  82. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
  83. package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
  84. package/corpus/templates/content/AGENTS.md +7 -3
  85. package/corpus/templates/content/README.md +22 -34
  86. package/corpus/templates/content/actions/list-trashed-content-databases.ts +4 -0
  87. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +58 -6
  88. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +257 -24
  89. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +41 -15
  90. package/corpus/templates/content/app/i18n/zh-TW.ts +25 -7
  91. package/corpus/templates/content/app/i18n-data.ts +278 -40
  92. package/corpus/templates/content/changelog/2026-06-30-database-once-again-creates-an-inline-database-in-the-curren.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +1 -0
  94. package/corpus/templates/design/AGENTS.md +7 -6
  95. package/corpus/templates/design/README.md +33 -0
  96. package/corpus/templates/design/actions/delete-file.ts +86 -10
  97. package/corpus/templates/design/actions/get-design-snapshot.ts +13 -0
  98. package/corpus/templates/design/actions/present-design-variants.ts +11 -13
  99. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  100. package/corpus/templates/design/app/pages/DesignEditor.tsx +2 -2
  101. package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
  102. package/corpus/templates/dispatch/README.md +34 -0
  103. package/corpus/templates/forms/README.md +34 -0
  104. package/corpus/templates/macros/README.md +28 -0
  105. package/corpus/templates/mail/README.md +35 -0
  106. package/corpus/templates/plan/README.md +30 -118
  107. package/corpus/templates/plan/app/global.css +1 -1
  108. package/corpus/templates/slides/README.md +34 -0
  109. package/corpus/templates/videos/README.md +22 -201
  110. package/dist/a2a/caller-auth.d.ts +1 -0
  111. package/dist/a2a/caller-auth.d.ts.map +1 -1
  112. package/dist/a2a/caller-auth.js +34 -8
  113. package/dist/a2a/caller-auth.js.map +1 -1
  114. package/dist/a2a/client.d.ts +4 -0
  115. package/dist/a2a/client.d.ts.map +1 -1
  116. package/dist/a2a/client.js +148 -58
  117. package/dist/a2a/client.js.map +1 -1
  118. package/dist/agent/production-agent.d.ts.map +1 -1
  119. package/dist/agent/production-agent.js +19 -4
  120. package/dist/agent/production-agent.js.map +1 -1
  121. package/dist/agent/types.d.ts +1 -0
  122. package/dist/agent/types.d.ts.map +1 -1
  123. package/dist/agent/types.js.map +1 -1
  124. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  125. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  126. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  127. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  128. package/dist/cli/recap.d.ts +3 -3
  129. package/dist/cli/recap.d.ts.map +1 -1
  130. package/dist/cli/recap.js +41 -34
  131. package/dist/cli/recap.js.map +1 -1
  132. package/dist/client/AssistantChat.d.ts.map +1 -1
  133. package/dist/client/AssistantChat.js +28 -4
  134. package/dist/client/AssistantChat.js.map +1 -1
  135. package/dist/client/PoweredByBadge.d.ts +9 -3
  136. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  137. package/dist/client/PoweredByBadge.js +47 -15
  138. package/dist/client/PoweredByBadge.js.map +1 -1
  139. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  140. package/dist/client/agent-chat-adapter.js +5 -1
  141. package/dist/client/agent-chat-adapter.js.map +1 -1
  142. package/dist/client/guided-questions.d.ts.map +1 -1
  143. package/dist/client/guided-questions.js +10 -6
  144. package/dist/client/guided-questions.js.map +1 -1
  145. package/dist/client/i18n.d.ts +1 -1
  146. package/dist/client/i18n.d.ts.map +1 -1
  147. package/dist/client/i18n.js +6 -2
  148. package/dist/client/i18n.js.map +1 -1
  149. package/dist/client/sse-event-processor.d.ts +2 -0
  150. package/dist/client/sse-event-processor.d.ts.map +1 -1
  151. package/dist/client/sse-event-processor.js +121 -5
  152. package/dist/client/sse-event-processor.js.map +1 -1
  153. package/dist/client/tool-display.d.ts.map +1 -1
  154. package/dist/client/tool-display.js +8 -0
  155. package/dist/client/tool-display.js.map +1 -1
  156. package/dist/collab/awareness.d.ts +2 -2
  157. package/dist/collab/awareness.d.ts.map +1 -1
  158. package/dist/collab/routes.d.ts +1 -1
  159. package/dist/file-upload/builder.d.ts.map +1 -1
  160. package/dist/file-upload/builder.js +26 -8
  161. package/dist/file-upload/builder.js.map +1 -1
  162. package/dist/file-upload/types.d.ts +5 -1
  163. package/dist/file-upload/types.d.ts.map +1 -1
  164. package/dist/file-upload/types.js.map +1 -1
  165. package/dist/integrations/a2a-continuation-processor.js +43 -18
  166. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  167. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  168. package/dist/mcp/builtin-tools.js +3 -0
  169. package/dist/mcp/builtin-tools.js.map +1 -1
  170. package/dist/mcp/org-directory.d.ts.map +1 -1
  171. package/dist/mcp/org-directory.js +58 -20
  172. package/dist/mcp/org-directory.js.map +1 -1
  173. package/dist/notifications/channels.d.ts +10 -0
  174. package/dist/notifications/channels.d.ts.map +1 -1
  175. package/dist/notifications/channels.js +172 -20
  176. package/dist/notifications/channels.js.map +1 -1
  177. package/dist/notifications/routes.d.ts +3 -3
  178. package/dist/observability/routes.d.ts +5 -5
  179. package/dist/progress/routes.d.ts +1 -1
  180. package/dist/resources/handlers.d.ts +1 -1
  181. package/dist/scripts/call-agent.d.ts.map +1 -1
  182. package/dist/scripts/call-agent.js +2 -2
  183. package/dist/scripts/call-agent.js.map +1 -1
  184. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  185. package/dist/server/analytics.d.ts +16 -0
  186. package/dist/server/analytics.d.ts.map +1 -1
  187. package/dist/server/analytics.js +30 -7
  188. package/dist/server/analytics.js.map +1 -1
  189. package/dist/server/ssr-handler.d.ts.map +1 -1
  190. package/dist/server/ssr-handler.js +28 -12
  191. package/dist/server/ssr-handler.js.map +1 -1
  192. package/dist/server/transcribe-voice.d.ts +1 -1
  193. package/package.json +1 -1
@@ -1,212 +1,33 @@
1
- # Video Studio
1
+ # Video
2
2
 
3
- A powerful Remotion-based video composition studio with AI-powered generation, interactive cursor animations, and advanced timeline controls.
3
+ An agent-native, Remotion-based video composition studio. Describe a video in
4
+ plain English and the agent generates animated React compositions with camera
5
+ moves, interactive cursor animations, and a full timeline editor.
4
6
 
5
- ## 🚀 Quick Start
7
+ **Live app: [videos.agent-native.com](https://videos.agent-native.com)**
6
8
 
7
- ```bash
8
- pnpm install
9
- pnpm run dev
10
- ```
11
-
12
- Open http://localhost:8080 to access the studio.
13
-
14
- ## ✨ Features
15
-
16
- ### 🎬 AI-Powered Generation
17
-
18
- - Describe videos in natural language
19
- - Attach reference images, logos, and assets
20
- - Generates React components and animation tracks automatically
21
-
22
- ### 🎥 Camera System
23
-
24
- - 6 animatable properties: translate X/Y, scale, rotate X/Y, perspective
25
- - Multi-keyframe selection with box-select and shift-click
26
- - Visual camera controls with real-time preview
27
-
28
- ### 🖱️ Interactive Cursor
29
-
30
- - Position tracking with smooth animations
31
- - Hover and click detection
32
- - Component-type based interactions
33
- - Visual cursor with multiple states
34
-
35
- ### 🎭 Cursor Interactions
36
-
37
- - Add hover animations to any component type
38
- - Click effects with timing control
39
- - Scale, translate, and rotate on interaction
40
- - Duration and easing customization
41
-
42
- ### ⚡ Advanced Timeline
43
-
44
- - Multi-keyframe selection and editing
45
- - Visual easing selector (20+ curves)
46
- - View range control for focused editing
47
- - Track properties panel with live preview
48
- - Expression-controlled animations (programmatic)
49
-
50
- ### 🎨 Built-in Components
51
-
52
- - Kinetic Text (typing reveal, drift, explode)
53
- - Logo Reveal (particle burst)
54
- - Logo Explode (SVG scatter)
55
- - Interactive Demo (hover buttons)
56
- - Interactive Card Grid (cursor-reactive cards)
57
- - Slideshow (multi-slide transitions)
58
-
59
- ## 🛠️ Helper Functions
60
-
61
- ```typescript
62
- import { createBlankComposition, addComposition } from "@/remotion/registry";
63
-
64
- import {
65
- createCameraTrack,
66
- createCursorTrack,
67
- createAnimationTrack,
68
- createFadeInTrack,
69
- createSlideInTrack,
70
- createCursorPath,
71
- createClickEvents,
72
- validateComposition,
73
- } from "@/utils/compositionHelpers";
74
-
75
- // Create a new composition
76
- const comp = createBlankComposition("My Video");
77
- addComposition(comp);
78
-
79
- // Or build manually with full control
80
- const tracks = [
81
- createCameraTrack(240),
82
- createCursorTrack(240),
83
- createFadeInTrack("title", "Title Entrance", 0, 30),
84
- ];
85
-
86
- validateComposition(tracks); // Ensure it has required tracks
87
- ```
88
-
89
- ## 📖 Key Concepts
90
-
91
- ### Compositions
92
-
93
- - **ID**: URL-friendly slug (auto-generated from title)
94
- - **Tracks**: Animation timelines (camera, cursor, custom)
95
- - **Props**: Component configuration (colors, text, etc.)
96
- - **Dimensions**: Default 1920×1080 @ 30fps
97
-
98
- ### Tracks
99
-
100
- - **Camera**: Controls viewport (required)
101
- - **Cursor**: Tracks pointer position (required for interactions)
102
- - **Custom**: Element-specific animations (optional)
9
+ Compositions are code-driven and yours to own. Generate with AI or build manually
10
+ with the same track system, then refine keyframes, easing, and cursor
11
+ interactions on the timeline.
103
12
 
104
- ### Keyframes
13
+ ## Features
105
14
 
106
- - **Simple**: from/to values with easing
107
- - **Complex**: Multiple keyframes with individual easing
108
- - **Programmatic**: Code-driven animations with parameters
15
+ - Describe videos in natural language and get React components + animation tracks.
16
+ - Camera system with animatable translate, scale, rotate, and perspective.
17
+ - Interactive cursor with hover/click detection and component-type interactions.
18
+ - Advanced timeline: multi-keyframe editing, easing curves, and view ranges.
19
+ - Built-in components: kinetic text, logo reveal/explode, slideshows, and more.
20
+ - Standardized 1920×1080 @ 30fps compositions.
109
21
 
110
- ### Cursor Interactions
22
+ ## Develop locally
111
23
 
112
- - **Hover**: Trigger when cursor enters component bounds
113
- - **Click**: Trigger on cursor click events
114
- - **Component-Type Based**: Apply to all instances globally
115
-
116
- ## 🎯 Common Workflows
117
-
118
- ### Create with AI
119
-
120
- 1. Click "+ New Composition"
121
- 2. Describe your video
122
- 3. Attach references (optional)
123
- 4. Press Enter
124
- 5. Edit and customize
125
-
126
- ### Create Manually
127
-
128
- 1. Copy `BlankComposition.tsx` as template
129
- 2. Modify component code
130
- 3. Register in `registry.ts`
131
- 4. Export in `compositions/index.ts`
132
- 5. Navigate to `/c/your-comp-id`
133
-
134
- ### Add Cursor Interactions
135
-
136
- 1. Ensure cursor track exists
137
- 2. Add `<Cursor>` to component
138
- 3. Use `useHoverAnimation()` hook
139
- 4. Configure in Properties → Cursor Interactions
140
-
141
- ### Multi-Keyframe Editing
142
-
143
- 1. Box-select: Click+drag in timeline
144
- 2. Shift-click: Add individual keyframes
145
- 3. Drag selection: Move all together
146
- 4. Maintains relative timing
147
-
148
- ## 📁 Project Structure
149
-
150
- ```
151
- app/
152
- ├── remotion/
153
- │ ├── compositions/ # Video components
154
- │ ├── ui-components/ # Reusable elements (Cursor, etc.)
155
- │ ├── hooks/ # Animation hooks
156
- │ ├── CameraHost.tsx # Camera wrapper
157
- │ ├── registry.ts # Composition registry
158
- │ └── trackAnimation.ts # Track utilities
159
- ├── components/ # Studio UI components
160
- ├── pages/ # Routes (Index, CompositionView)
161
- ├── utils/ # Helpers and utilities
162
- └── contexts/ # React context providers
163
-
164
- server/
165
- ├── routes/ # API endpoints
166
- └── index.ts # Express server
167
-
168
- docs/examples/
169
- └── create-composition.example.ts # Example creation script
170
- ```
171
-
172
- ## 🔧 Development
173
-
174
- ### Commands
24
+ Scaffold your own copy and run it:
175
25
 
176
26
  ```bash
177
- # Development
178
- pnpm run dev # Start dev server (http://localhost:8080)
179
-
180
- # Build
181
- pnpm run build # Build client and server
182
-
183
- # Production
184
- pnpm start # Run production server
185
-
186
- # Testing
187
- pnpm test # Run tests
188
- pnpm typecheck # Type checking
189
- pnpm format.fix # Format code
27
+ npx @agent-native/core@latest create my-videos --standalone --template videos
28
+ cd my-videos
29
+ pnpm install
30
+ pnpm dev
190
31
  ```
191
32
 
192
- ### Environment
193
-
194
- All compositions run at:
195
-
196
- - **30fps** (standardized)
197
- - **1920×1080** (Wide format default)
198
- - **240 frames** (8 seconds default duration)
199
-
200
- ## 🎓 Learn More
201
-
202
- - **Remotion Docs**: https://remotion.dev/docs
203
- - **Builder.io Docs**: https://www.builder.io/c/docs/projects
204
- - **Example Compositions**: Check `app/remotion/compositions/` folder
205
-
206
- ## 📝 License
207
-
208
- Private project.
209
-
210
- ---
211
-
212
- **Ready to create?** Click "+ New Composition" and describe your first video! 🎬
33
+ Full docs: [agent-native.com/docs/template-videos](https://agent-native.com/docs/template-videos).
@@ -1,5 +1,6 @@
1
1
  export interface A2ACallerAuth {
2
2
  apiKey?: string;
3
+ apiKeyFallbacks?: string[];
3
4
  userEmail?: string;
4
5
  orgId?: string;
5
6
  orgDomain?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"caller-auth.d.ts","sourceRoot":"","sources":["../../src/a2a/caller-auth.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IACnD,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAAC,aAAa,CAAC,CAmCzB"}
1
+ {"version":3,"file":"caller-auth.d.ts","sourceRoot":"","sources":["../../src/a2a/caller-auth.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IACnD,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAAC,aAAa,CAAC,CA+DzB"}
@@ -23,20 +23,46 @@ export async function resolveA2ACallerAuth(options) {
23
23
  }
24
24
  catch { }
25
25
  }
26
- let apiKey;
26
+ const apiKeyAttempts = [];
27
+ const addApiKeyAttempt = (token) => {
28
+ if (!token || apiKeyAttempts.includes(token))
29
+ return;
30
+ apiKeyAttempts.push(token);
31
+ };
27
32
  if (userEmail && (orgSecret || process.env.A2A_SECRET)) {
28
- try {
29
- apiKey = await signA2AToken(userEmail, orgDomain, orgSecret, {
30
- expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
31
- preferGlobalSecret: !orgSecret,
32
- });
33
+ if (process.env.A2A_SECRET?.trim()) {
34
+ try {
35
+ addApiKeyAttempt(await signA2AToken(userEmail, orgDomain, orgSecret, {
36
+ expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
37
+ preferGlobalSecret: true,
38
+ }));
39
+ }
40
+ catch { }
41
+ }
42
+ if (orgSecret) {
43
+ try {
44
+ addApiKeyAttempt(await signA2AToken(userEmail, orgDomain, orgSecret, {
45
+ expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
46
+ preferGlobalSecret: false,
47
+ }));
48
+ }
49
+ catch { }
33
50
  }
34
- catch { }
35
51
  }
36
52
  if (options?.includeGoogleToken) {
37
53
  await attachGoogleTokenMetadata(metadata, userEmail);
38
54
  }
39
- return { apiKey, userEmail, orgId, orgDomain, orgSecret, metadata };
55
+ return {
56
+ apiKey: apiKeyAttempts[0],
57
+ ...(apiKeyAttempts.length > 1
58
+ ? { apiKeyFallbacks: apiKeyAttempts.slice(1) }
59
+ : {}),
60
+ userEmail,
61
+ orgId,
62
+ orgDomain,
63
+ orgSecret,
64
+ metadata,
65
+ };
40
66
  }
41
67
  async function attachGoogleTokenMetadata(metadata, userEmail) {
42
68
  if (process.env.NODE_ENV !== "production" || !userEmail)
@@ -1 +1 @@
1
- {"version":3,"file":"caller-auth.js","sourceRoot":"","sources":["../../src/a2a/caller-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAW3C,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAG1C;IACC,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC;IACxC,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,IAAI,SAAS;QAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,CAAC;IAE9C,IAAI,SAA6B,CAAC;IAClC,IAAI,SAA6B,CAAC;IAClC,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;IAChC,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,CAAC;YACH,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAC3D,SAAS,GAAG,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC;YACrD,IAAI,SAAS;gBAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,IAAI,CAAC;YACH,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAC9D,SAAS,GAAG,CAAC,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,IAAI,MAA0B,CAAC;IAC/B,IAAI,SAAS,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;gBAC3D,SAAS,EAAE,OAAO,EAAE,SAAS,IAAI,4BAA4B;gBAC7D,kBAAkB,EAAE,CAAC,SAAS;aAC/B,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,IAAI,OAAO,EAAE,kBAAkB,EAAE,CAAC;QAChC,MAAM,yBAAyB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AACtE,CAAC;AAED,KAAK,UAAU,yBAAyB,CACtC,QAAiC,EACjC,SAA6B;IAE7B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,IAAI,CAAC,SAAS;QAAE,OAAO;IAEhE,IAAI,CAAC;QACH,MAAM,EAAE,wBAAwB,EAAE,GAChC,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QACnC,IAAI,MAAM,EAAE,YAAY,EAAE,CAAC;YACzB,QAAQ,CAAC,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC;QAC7C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;AACZ,CAAC","sourcesContent":["import {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\nimport { signA2AToken } from \"./client.js\";\n\nconst DEFAULT_A2A_CALLER_TOKEN_TTL = \"30m\";\n\nexport interface A2ACallerAuth {\n apiKey?: string;\n userEmail?: string;\n orgId?: string;\n orgDomain?: string;\n orgSecret?: string;\n metadata: Record<string, unknown>;\n}\n\nexport async function resolveA2ACallerAuth(options?: {\n expiresIn?: string | number;\n includeGoogleToken?: boolean;\n}): Promise<A2ACallerAuth> {\n const userEmail = getRequestUserEmail();\n const metadata: Record<string, unknown> = {};\n if (userEmail) metadata.userEmail = userEmail;\n\n let orgDomain: string | undefined;\n let orgSecret: string | undefined;\n const orgId = getRequestOrgId();\n if (orgId) {\n try {\n const { getOrgDomain } = await import(\"../org/context.js\");\n orgDomain = (await getOrgDomain(orgId)) ?? undefined;\n if (orgDomain) metadata.orgDomain = orgDomain;\n } catch {}\n try {\n const { getOrgA2ASecret } = await import(\"../org/context.js\");\n orgSecret = (await getOrgA2ASecret(orgId)) ?? undefined;\n } catch {}\n }\n\n let apiKey: string | undefined;\n if (userEmail && (orgSecret || process.env.A2A_SECRET)) {\n try {\n apiKey = await signA2AToken(userEmail, orgDomain, orgSecret, {\n expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,\n preferGlobalSecret: !orgSecret,\n });\n } catch {}\n }\n\n if (options?.includeGoogleToken) {\n await attachGoogleTokenMetadata(metadata, userEmail);\n }\n\n return { apiKey, userEmail, orgId, orgDomain, orgSecret, metadata };\n}\n\nasync function attachGoogleTokenMetadata(\n metadata: Record<string, unknown>,\n userEmail: string | undefined,\n): Promise<void> {\n if (process.env.NODE_ENV !== \"production\" || !userEmail) return;\n\n try {\n const { listOAuthAccountsByOwner } =\n await import(\"../oauth-tokens/store.js\");\n const accounts = await listOAuthAccountsByOwner(\"google\", userEmail);\n const tokens = accounts[0]?.tokens;\n if (tokens?.access_token) {\n metadata.googleToken = tokens.access_token;\n }\n } catch {}\n}\n"]}
1
+ {"version":3,"file":"caller-auth.js","sourceRoot":"","sources":["../../src/a2a/caller-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"../server/request-context.js\";\nimport { signA2AToken } from \"./client.js\";\n\nconst DEFAULT_A2A_CALLER_TOKEN_TTL = \"30m\";\n\nexport interface A2ACallerAuth {\n apiKey?: string;\n apiKeyFallbacks?: string[];\n userEmail?: string;\n orgId?: string;\n orgDomain?: string;\n orgSecret?: string;\n metadata: Record<string, unknown>;\n}\n\nexport async function resolveA2ACallerAuth(options?: {\n expiresIn?: string | number;\n includeGoogleToken?: boolean;\n}): Promise<A2ACallerAuth> {\n const userEmail = getRequestUserEmail();\n const metadata: Record<string, unknown> = {};\n if (userEmail) metadata.userEmail = userEmail;\n\n let orgDomain: string | undefined;\n let orgSecret: string | undefined;\n const orgId = getRequestOrgId();\n if (orgId) {\n try {\n const { getOrgDomain } = await import(\"../org/context.js\");\n orgDomain = (await getOrgDomain(orgId)) ?? undefined;\n if (orgDomain) metadata.orgDomain = orgDomain;\n } catch {}\n try {\n const { getOrgA2ASecret } = await import(\"../org/context.js\");\n orgSecret = (await getOrgA2ASecret(orgId)) ?? undefined;\n } catch {}\n }\n\n const apiKeyAttempts: string[] = [];\n const addApiKeyAttempt = (token: string | undefined) => {\n if (!token || apiKeyAttempts.includes(token)) return;\n apiKeyAttempts.push(token);\n };\n if (userEmail && (orgSecret || process.env.A2A_SECRET)) {\n if (process.env.A2A_SECRET?.trim()) {\n try {\n addApiKeyAttempt(\n await signA2AToken(userEmail, orgDomain, orgSecret, {\n expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,\n preferGlobalSecret: true,\n }),\n );\n } catch {}\n }\n if (orgSecret) {\n try {\n addApiKeyAttempt(\n await signA2AToken(userEmail, orgDomain, orgSecret, {\n expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,\n preferGlobalSecret: false,\n }),\n );\n } catch {}\n }\n }\n\n if (options?.includeGoogleToken) {\n await attachGoogleTokenMetadata(metadata, userEmail);\n }\n\n return {\n apiKey: apiKeyAttempts[0],\n ...(apiKeyAttempts.length > 1\n ? { apiKeyFallbacks: apiKeyAttempts.slice(1) }\n : {}),\n userEmail,\n orgId,\n orgDomain,\n orgSecret,\n metadata,\n };\n}\n\nasync function attachGoogleTokenMetadata(\n metadata: Record<string, unknown>,\n userEmail: string | undefined,\n): Promise<void> {\n if (process.env.NODE_ENV !== \"production\" || !userEmail) return;\n\n try {\n const { listOAuthAccountsByOwner } =\n await import(\"../oauth-tokens/store.js\");\n const accounts = await listOAuthAccountsByOwner(\"google\", userEmail);\n const tokens = accounts[0]?.tokens;\n if (tokens?.access_token) {\n metadata.googleToken = tokens.access_token;\n }\n } catch {}\n}\n"]}
@@ -27,14 +27,17 @@ export declare function signA2AToken(email: string, orgDomain?: string, orgSecre
27
27
  */
28
28
  extraClaims?: Record<string, unknown>;
29
29
  }): Promise<string>;
30
+ export declare function shouldPreferGlobalA2ASecret(orgSecret?: string): boolean;
30
31
  export declare class A2AClient {
31
32
  private baseUrl;
32
33
  private apiKey?;
34
+ private apiKeyAttempts;
33
35
  private endpointCandidates;
34
36
  private endpointResolved;
35
37
  private requestTimeoutMs?;
36
38
  constructor(baseUrl: string, apiKey?: string, options?: {
37
39
  requestTimeoutMs?: number;
40
+ fallbackApiKeys?: string[];
38
41
  });
39
42
  /**
40
43
  * Detect which A2A path the target agent uses.
@@ -42,6 +45,7 @@ export declare class A2AClient {
42
45
  */
43
46
  resolveEndpoint(): Promise<void>;
44
47
  private headers;
48
+ private markApiKeySucceeded;
45
49
  private rpc;
46
50
  getAgentCard(): Promise<AgentCard>;
47
51
  send(message: Message, opts?: {
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/a2a/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/a2a/client.ts"],"names":[],"mappings":"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"}
@@ -50,9 +50,13 @@ export async function signA2AToken(email, orgDomain, orgSecret, options) {
50
50
  jwt.setAudience(options.audience);
51
51
  return jwt.sign(new TextEncoder().encode(secret));
52
52
  }
53
+ export function shouldPreferGlobalA2ASecret(orgSecret) {
54
+ return !!process.env.A2A_SECRET?.trim() || !orgSecret;
55
+ }
53
56
  export class A2AClient {
54
57
  baseUrl;
55
58
  apiKey;
59
+ apiKeyAttempts;
56
60
  endpointCandidates = [];
57
61
  endpointResolved = false;
58
62
  requestTimeoutMs;
@@ -65,6 +69,10 @@ export class A2AClient {
65
69
  this.endpointResolved = true;
66
70
  }
67
71
  this.apiKey = apiKey;
72
+ this.apiKeyAttempts = uniqueAuthTokens([
73
+ apiKey,
74
+ ...(options?.fallbackApiKeys ?? []),
75
+ ]);
68
76
  this.requestTimeoutMs = options?.requestTimeoutMs;
69
77
  }
70
78
  /**
@@ -92,13 +100,20 @@ export class A2AClient {
92
100
  }
93
101
  }
94
102
  }
95
- headers() {
103
+ headers(apiKey = this.apiKey) {
96
104
  const h = { "Content-Type": "application/json" };
97
- if (this.apiKey) {
98
- h["Authorization"] = `Bearer ${this.apiKey}`;
105
+ if (apiKey) {
106
+ h["Authorization"] = `Bearer ${apiKey}`;
99
107
  }
100
108
  return h;
101
109
  }
110
+ markApiKeySucceeded(apiKey) {
111
+ this.apiKey = apiKey;
112
+ this.apiKeyAttempts = uniqueAuthTokens([
113
+ apiKey,
114
+ ...this.apiKeyAttempts.filter((token) => token !== apiKey),
115
+ ]);
116
+ }
102
117
  async rpc(method, params) {
103
118
  const body = {
104
119
  jsonrpc: "2.0",
@@ -109,18 +124,26 @@ export class A2AClient {
109
124
  await this.ensureEndpointCandidates();
110
125
  let lastError = null;
111
126
  for (const url of this.endpointCandidates) {
112
- console.log(`[A2A Client] POST ${url} method=${method}`);
113
- const startTime = Date.now();
114
- const res = await this.postJson(url, body);
115
- console.log(`[A2A Client] Response: ${res.status} in ${Date.now() - startTime}ms`);
116
- if (res.ok) {
117
- this.endpointCandidates = [url];
118
- return res.json();
119
- }
120
- const text = await res.text();
121
- lastError = new Error(`A2A request failed (${res.status}): ${text}`);
122
- if (!shouldTryNextEndpoint(res.status)) {
123
- throw lastError;
127
+ for (let i = 0; i < this.apiKeyAttempts.length; i++) {
128
+ console.log(`[A2A Client] POST ${url} method=${method}`);
129
+ const startTime = Date.now();
130
+ const res = await this.postJson(url, body, this.apiKeyAttempts[i]);
131
+ console.log(`[A2A Client] Response: ${res.status} in ${Date.now() - startTime}ms`);
132
+ if (res.ok) {
133
+ this.endpointCandidates = [url];
134
+ this.markApiKeySucceeded(this.apiKeyAttempts[i]);
135
+ return res.json();
136
+ }
137
+ const text = await res.text();
138
+ lastError = new Error(`A2A request failed (${res.status}): ${text}`);
139
+ if (i < this.apiKeyAttempts.length - 1 &&
140
+ isA2AAuthRejectionResponse(res.status, text)) {
141
+ continue;
142
+ }
143
+ if (!shouldTryNextEndpoint(res.status)) {
144
+ throw lastError;
145
+ }
146
+ break;
124
147
  }
125
148
  }
126
149
  throw lastError ?? new Error("No A2A endpoint candidates available");
@@ -206,15 +229,25 @@ export class A2AClient {
206
229
  let res = null;
207
230
  let lastError = null;
208
231
  for (const candidate of this.endpointCandidates) {
209
- res = await this.postJson(candidate, body);
210
- if (res.ok) {
211
- this.endpointCandidates = [candidate];
232
+ for (let i = 0; i < this.apiKeyAttempts.length; i++) {
233
+ res = await this.postJson(candidate, body, this.apiKeyAttempts[i]);
234
+ if (res.ok) {
235
+ this.endpointCandidates = [candidate];
236
+ this.markApiKeySucceeded(this.apiKeyAttempts[i]);
237
+ break;
238
+ }
239
+ const text = await res.text();
240
+ lastError = new Error(`A2A stream failed (${res.status}): ${text}`);
241
+ if (i < this.apiKeyAttempts.length - 1 &&
242
+ isA2AAuthRejectionResponse(res.status, text)) {
243
+ continue;
244
+ }
245
+ if (!shouldTryNextEndpoint(res.status))
246
+ throw lastError;
212
247
  break;
213
248
  }
214
- const text = await res.text();
215
- lastError = new Error(`A2A stream failed (${res.status}): ${text}`);
216
- if (!shouldTryNextEndpoint(res.status))
217
- throw lastError;
249
+ if (res?.ok)
250
+ break;
218
251
  }
219
252
  if (!res?.ok) {
220
253
  throw lastError ?? new Error("No A2A endpoint candidates available");
@@ -271,7 +304,7 @@ export class A2AClient {
271
304
  }
272
305
  this.endpointCandidates = unique(candidates);
273
306
  }
274
- async postJson(url, body) {
307
+ async postJson(url, body, apiKey = this.apiKey) {
275
308
  const controller = this.requestTimeoutMs
276
309
  ? new AbortController()
277
310
  : undefined;
@@ -281,7 +314,7 @@ export class A2AClient {
281
314
  try {
282
315
  return await ssrfSafeFetch(url, {
283
316
  method: "POST",
284
- headers: this.headers(),
317
+ headers: this.headers(apiKey),
285
318
  body: JSON.stringify(body),
286
319
  signal: controller?.signal,
287
320
  }, { maxRedirects: 3 });
@@ -342,6 +375,21 @@ function shouldTryNextEndpoint(status) {
342
375
  function unique(values) {
343
376
  return Array.from(new Set(values));
344
377
  }
378
+ function uniqueAuthTokens(values) {
379
+ const result = [];
380
+ for (const value of values) {
381
+ if (result.includes(value))
382
+ continue;
383
+ result.push(value);
384
+ }
385
+ if (result.length === 0)
386
+ result.push(undefined);
387
+ return result;
388
+ }
389
+ function isA2AAuthRejectionResponse(status, text) {
390
+ return (status === 401 ||
391
+ /A2A error \(-32001\): (?:Invalid or expired A2A token|Invalid API key|Authentication required)|Invalid or expired A2A token|Invalid API key|Authentication required/i.test(text));
392
+ }
345
393
  /**
346
394
  * One-shot convenience function: send a text message and get a text response.
347
395
  *
@@ -350,19 +398,6 @@ function unique(values) {
350
398
  * identity (instead of blindly trusting metadata).
351
399
  */
352
400
  export async function callAgent(url, text, opts) {
353
- let apiKey = opts?.apiKey;
354
- // Auto-sign with JWT when an A2A secret (org or global) is available and we have a user email
355
- if (!apiKey &&
356
- opts?.userEmail &&
357
- (opts?.orgSecret || process.env.A2A_SECRET)) {
358
- try {
359
- apiKey = await signA2AToken(opts.userEmail, opts.orgDomain, opts.orgSecret, { preferGlobalSecret: !opts.orgSecret });
360
- }
361
- catch {
362
- // Fall back to unsigned call
363
- }
364
- }
365
- const client = new A2AClient(url, apiKey);
366
401
  const metadata = {};
367
402
  if (opts?.userEmail)
368
403
  metadata.userEmail = opts.userEmail;
@@ -378,15 +413,40 @@ export async function callAgent(url, text, opts) {
378
413
  role: "user",
379
414
  parts: [{ type: "text", text }],
380
415
  };
381
- let task;
382
- if (useAsync) {
416
+ const apiKeyAttempts = await buildA2AApiKeyAttempts(opts);
417
+ let lastAuthError;
418
+ for (let i = 0; i < apiKeyAttempts.length; i++) {
383
419
  try {
384
- task = await client.sendAndWait(message, {
385
- contextId: opts?.contextId,
386
- metadata,
387
- timeoutMs: opts?.timeoutMs,
388
- pollIntervalMs: opts?.pollIntervalMs,
420
+ const fallbackApiKeys = apiKeyAttempts
421
+ .slice(i + 1)
422
+ .filter((token) => token !== undefined);
423
+ const client = new A2AClient(url, apiKeyAttempts[i], {
424
+ fallbackApiKeys,
389
425
  });
426
+ let task;
427
+ if (useAsync) {
428
+ task = await client.sendAndWait(message, {
429
+ contextId: opts?.contextId,
430
+ metadata,
431
+ timeoutMs: opts?.timeoutMs,
432
+ pollIntervalMs: opts?.pollIntervalMs,
433
+ });
434
+ }
435
+ else {
436
+ task = await client.send(message, {
437
+ contextId: opts?.contextId,
438
+ metadata,
439
+ });
440
+ }
441
+ // Extract text from the response
442
+ const responseMessage = task.status.message;
443
+ if (responseMessage) {
444
+ const textParts = responseMessage.parts
445
+ .filter((p) => p.type === "text")
446
+ .map((p) => p.text);
447
+ return textParts.join("\n");
448
+ }
449
+ return "";
390
450
  }
391
451
  catch (err) {
392
452
  if (err instanceof A2ATaskTimeoutError) {
@@ -394,25 +454,55 @@ export async function callAgent(url, text, opts) {
394
454
  if (recoverableText)
395
455
  return recoverableText;
396
456
  }
457
+ if (i < apiKeyAttempts.length - 1 && isA2AAuthRejection(err)) {
458
+ lastAuthError = err;
459
+ continue;
460
+ }
397
461
  throw err;
398
462
  }
399
463
  }
400
- else {
401
- task = await client.send(message, {
402
- contextId: opts?.contextId,
403
- metadata,
404
- });
405
- }
406
- // Extract text from the response
407
- const responseMessage = task.status.message;
408
- if (responseMessage) {
409
- const textParts = responseMessage.parts
410
- .filter((p) => p.type === "text")
411
- .map((p) => p.text);
412
- return textParts.join("\n");
413
- }
464
+ if (lastAuthError)
465
+ throw lastAuthError;
414
466
  return "";
415
467
  }
468
+ async function buildA2AApiKeyAttempts(opts) {
469
+ const attempts = [];
470
+ const add = (token) => {
471
+ if (token === undefined || attempts.includes(token))
472
+ return;
473
+ attempts.push(token);
474
+ };
475
+ add(opts?.apiKey);
476
+ if (opts?.userEmail && (opts.orgSecret || process.env.A2A_SECRET)) {
477
+ if (process.env.A2A_SECRET?.trim()) {
478
+ try {
479
+ add(await signA2AToken(opts.userEmail, opts.orgDomain, opts.orgSecret, {
480
+ preferGlobalSecret: true,
481
+ }));
482
+ }
483
+ catch {
484
+ // Keep any explicit token attempt, then fall back below.
485
+ }
486
+ }
487
+ if (opts.orgSecret) {
488
+ try {
489
+ add(await signA2AToken(opts.userEmail, opts.orgDomain, opts.orgSecret, {
490
+ preferGlobalSecret: false,
491
+ }));
492
+ }
493
+ catch {
494
+ // Fall through to the attempts we already have.
495
+ }
496
+ }
497
+ }
498
+ if (attempts.length === 0)
499
+ attempts.push(undefined);
500
+ return attempts;
501
+ }
502
+ function isA2AAuthRejection(err) {
503
+ const message = err instanceof Error ? err.message : String(err ?? "");
504
+ return /A2A request failed \(401\)|A2A error \(-32001\): (?:Invalid or expired A2A token|Invalid API key|Authentication required)|Invalid or expired A2A token|Invalid API key|Authentication required/i.test(message);
505
+ }
416
506
  function extractRecoverableArtifactText(task) {
417
507
  if (!task.status.message?.metadata?.agentNativeRecoverableArtifacts) {
418
508
  return "";