@agent-native/core 0.84.20 → 0.84.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/caller-auth.ts +37 -8
- package/corpus/core/src/a2a/client.ts +178 -67
- package/corpus/core/src/agent/production-agent.ts +19 -3
- package/corpus/core/src/agent/types.ts +1 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/cli/recap.ts +43 -34
- package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
- package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
- package/corpus/core/src/client/i18n.tsx +10 -5
- package/corpus/core/src/client/sse-event-processor.ts +147 -5
- package/corpus/core/src/file-upload/builder.ts +35 -14
- package/corpus/core/src/file-upload/types.ts +3 -0
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
- package/corpus/core/src/mcp/builtin-tools.ts +3 -0
- package/corpus/core/src/mcp/org-directory.ts +66 -20
- package/corpus/core/src/scripts/call-agent.ts +7 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
- package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
- package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
- package/corpus/templates/clips/actions/create-recording.ts +7 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
- package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
- package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +65 -1
- package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
- package/corpus/templates/clips/app/routes/record.tsx +48 -8
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +64 -6
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +14 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
- package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
- package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -1
- package/dist/a2a/caller-auth.d.ts +1 -0
- package/dist/a2a/caller-auth.d.ts.map +1 -1
- package/dist/a2a/caller-auth.js +34 -8
- package/dist/a2a/caller-auth.js.map +1 -1
- package/dist/a2a/client.d.ts +4 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +148 -58
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -4
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +1 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +3 -3
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +41 -34
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/PoweredByBadge.d.ts +9 -3
- package/dist/client/PoweredByBadge.d.ts.map +1 -1
- package/dist/client/PoweredByBadge.js +47 -15
- package/dist/client/PoweredByBadge.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +5 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/i18n.d.ts +1 -1
- package/dist/client/i18n.d.ts.map +1 -1
- package/dist/client/i18n.js +6 -2
- package/dist/client/i18n.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +2 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +121 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +22 -8
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/types.d.ts +5 -1
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +43 -18
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +3 -0
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/org-directory.d.ts.map +1 -1
- package/dist/mcp/org-directory.js +58 -20
- package/dist/mcp/org-directory.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +2 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.21
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f883173: Refresh public booking branding with a plain Agent Native logo, updated open-source copy, and a ghost icon language picker for public pages.
|
|
8
|
+
- f883173: Fix PR visual recap gating and comments so nested AGENTS.md files no longer count as recap-control files, trusted write actors can recap control-file edits, stale success bodies are replaced by skipped comments, and screenshot capture no longer waits for network-idle.
|
|
9
|
+
- f883173: Offer Claude Opus 4.8 in the managed Builder gateway model picker, keep action-preparation recovery progress-aware for large streamed tool inputs, retry A2A auth with org-scoped bearer fallbacks in mixed deployments, and allow Builder uploads to skip waiting on server-side compression when callers can process asynchronously.
|
|
10
|
+
|
|
3
11
|
## 0.84.20
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.21",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -8,6 +8,7 @@ const DEFAULT_A2A_CALLER_TOKEN_TTL = "30m";
|
|
|
8
8
|
|
|
9
9
|
export interface A2ACallerAuth {
|
|
10
10
|
apiKey?: string;
|
|
11
|
+
apiKeyFallbacks?: string[];
|
|
11
12
|
userEmail?: string;
|
|
12
13
|
orgId?: string;
|
|
13
14
|
orgDomain?: string;
|
|
@@ -38,21 +39,49 @@ export async function resolveA2ACallerAuth(options?: {
|
|
|
38
39
|
} catch {}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
const apiKeyAttempts: string[] = [];
|
|
43
|
+
const addApiKeyAttempt = (token: string | undefined) => {
|
|
44
|
+
if (!token || apiKeyAttempts.includes(token)) return;
|
|
45
|
+
apiKeyAttempts.push(token);
|
|
46
|
+
};
|
|
42
47
|
if (userEmail && (orgSecret || process.env.A2A_SECRET)) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
if (process.env.A2A_SECRET?.trim()) {
|
|
49
|
+
try {
|
|
50
|
+
addApiKeyAttempt(
|
|
51
|
+
await signA2AToken(userEmail, orgDomain, orgSecret, {
|
|
52
|
+
expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
|
|
53
|
+
preferGlobalSecret: true,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
if (orgSecret) {
|
|
59
|
+
try {
|
|
60
|
+
addApiKeyAttempt(
|
|
61
|
+
await signA2AToken(userEmail, orgDomain, orgSecret, {
|
|
62
|
+
expiresIn: options?.expiresIn ?? DEFAULT_A2A_CALLER_TOKEN_TTL,
|
|
63
|
+
preferGlobalSecret: false,
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
} catch {}
|
|
67
|
+
}
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
if (options?.includeGoogleToken) {
|
|
52
71
|
await attachGoogleTokenMetadata(metadata, userEmail);
|
|
53
72
|
}
|
|
54
73
|
|
|
55
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
apiKey: apiKeyAttempts[0],
|
|
76
|
+
...(apiKeyAttempts.length > 1
|
|
77
|
+
? { apiKeyFallbacks: apiKeyAttempts.slice(1) }
|
|
78
|
+
: {}),
|
|
79
|
+
userEmail,
|
|
80
|
+
orgId,
|
|
81
|
+
orgDomain,
|
|
82
|
+
orgSecret,
|
|
83
|
+
metadata,
|
|
84
|
+
};
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
async function attachGoogleTokenMetadata(
|
|
@@ -86,9 +86,14 @@ export async function signA2AToken(
|
|
|
86
86
|
return jwt.sign(new TextEncoder().encode(secret));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
export function shouldPreferGlobalA2ASecret(orgSecret?: string): boolean {
|
|
90
|
+
return !!process.env.A2A_SECRET?.trim() || !orgSecret;
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
export class A2AClient {
|
|
90
94
|
private baseUrl: string;
|
|
91
95
|
private apiKey?: string;
|
|
96
|
+
private apiKeyAttempts: Array<string | undefined>;
|
|
92
97
|
private endpointCandidates: string[] = [];
|
|
93
98
|
private endpointResolved = false;
|
|
94
99
|
private requestTimeoutMs?: number;
|
|
@@ -96,7 +101,7 @@ export class A2AClient {
|
|
|
96
101
|
constructor(
|
|
97
102
|
baseUrl: string,
|
|
98
103
|
apiKey?: string,
|
|
99
|
-
options?: { requestTimeoutMs?: number },
|
|
104
|
+
options?: { requestTimeoutMs?: number; fallbackApiKeys?: string[] },
|
|
100
105
|
) {
|
|
101
106
|
const normalized = baseUrl.replace(/\/$/, "");
|
|
102
107
|
const explicitEndpoint = splitExplicitA2AEndpoint(normalized);
|
|
@@ -106,6 +111,10 @@ export class A2AClient {
|
|
|
106
111
|
this.endpointResolved = true;
|
|
107
112
|
}
|
|
108
113
|
this.apiKey = apiKey;
|
|
114
|
+
this.apiKeyAttempts = uniqueAuthTokens([
|
|
115
|
+
apiKey,
|
|
116
|
+
...(options?.fallbackApiKeys ?? []),
|
|
117
|
+
]);
|
|
109
118
|
this.requestTimeoutMs = options?.requestTimeoutMs;
|
|
110
119
|
}
|
|
111
120
|
|
|
@@ -138,14 +147,22 @@ export class A2AClient {
|
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
|
|
141
|
-
private headers(): Record<string, string> {
|
|
150
|
+
private headers(apiKey = this.apiKey): Record<string, string> {
|
|
142
151
|
const h: Record<string, string> = { "Content-Type": "application/json" };
|
|
143
|
-
if (
|
|
144
|
-
h["Authorization"] = `Bearer ${
|
|
152
|
+
if (apiKey) {
|
|
153
|
+
h["Authorization"] = `Bearer ${apiKey}`;
|
|
145
154
|
}
|
|
146
155
|
return h;
|
|
147
156
|
}
|
|
148
157
|
|
|
158
|
+
private markApiKeySucceeded(apiKey: string | undefined) {
|
|
159
|
+
this.apiKey = apiKey;
|
|
160
|
+
this.apiKeyAttempts = uniqueAuthTokens([
|
|
161
|
+
apiKey,
|
|
162
|
+
...this.apiKeyAttempts.filter((token) => token !== apiKey),
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
private async rpc(
|
|
150
167
|
method: string,
|
|
151
168
|
params: Record<string, unknown>,
|
|
@@ -161,22 +178,32 @@ export class A2AClient {
|
|
|
161
178
|
let lastError: Error | null = null;
|
|
162
179
|
|
|
163
180
|
for (const url of this.endpointCandidates) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
for (let i = 0; i < this.apiKeyAttempts.length; i++) {
|
|
182
|
+
console.log(`[A2A Client] POST ${url} method=${method}`);
|
|
183
|
+
const startTime = Date.now();
|
|
184
|
+
const res = await this.postJson(url, body, this.apiKeyAttempts[i]);
|
|
185
|
+
console.log(
|
|
186
|
+
`[A2A Client] Response: ${res.status} in ${Date.now() - startTime}ms`,
|
|
187
|
+
);
|
|
170
188
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
if (res.ok) {
|
|
190
|
+
this.endpointCandidates = [url];
|
|
191
|
+
this.markApiKeySucceeded(this.apiKeyAttempts[i]);
|
|
192
|
+
return res.json() as Promise<JsonRpcResponse>;
|
|
193
|
+
}
|
|
175
194
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
195
|
+
const text = await res.text();
|
|
196
|
+
lastError = new Error(`A2A request failed (${res.status}): ${text}`);
|
|
197
|
+
if (
|
|
198
|
+
i < this.apiKeyAttempts.length - 1 &&
|
|
199
|
+
isA2AAuthRejectionResponse(res.status, text)
|
|
200
|
+
) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!shouldTryNextEndpoint(res.status)) {
|
|
204
|
+
throw lastError;
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
180
207
|
}
|
|
181
208
|
}
|
|
182
209
|
|
|
@@ -309,14 +336,25 @@ export class A2AClient {
|
|
|
309
336
|
let res: Response | null = null;
|
|
310
337
|
let lastError: Error | null = null;
|
|
311
338
|
for (const candidate of this.endpointCandidates) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
339
|
+
for (let i = 0; i < this.apiKeyAttempts.length; i++) {
|
|
340
|
+
res = await this.postJson(candidate, body, this.apiKeyAttempts[i]);
|
|
341
|
+
if (res.ok) {
|
|
342
|
+
this.endpointCandidates = [candidate];
|
|
343
|
+
this.markApiKeySucceeded(this.apiKeyAttempts[i]);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
const text = await res.text();
|
|
347
|
+
lastError = new Error(`A2A stream failed (${res.status}): ${text}`);
|
|
348
|
+
if (
|
|
349
|
+
i < this.apiKeyAttempts.length - 1 &&
|
|
350
|
+
isA2AAuthRejectionResponse(res.status, text)
|
|
351
|
+
) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (!shouldTryNextEndpoint(res.status)) throw lastError;
|
|
315
355
|
break;
|
|
316
356
|
}
|
|
317
|
-
|
|
318
|
-
lastError = new Error(`A2A stream failed (${res.status}): ${text}`);
|
|
319
|
-
if (!shouldTryNextEndpoint(res.status)) throw lastError;
|
|
357
|
+
if (res?.ok) break;
|
|
320
358
|
}
|
|
321
359
|
if (!res?.ok) {
|
|
322
360
|
throw lastError ?? new Error("No A2A endpoint candidates available");
|
|
@@ -379,7 +417,11 @@ export class A2AClient {
|
|
|
379
417
|
this.endpointCandidates = unique(candidates);
|
|
380
418
|
}
|
|
381
419
|
|
|
382
|
-
private async postJson(
|
|
420
|
+
private async postJson(
|
|
421
|
+
url: string,
|
|
422
|
+
body: JsonRpcRequest,
|
|
423
|
+
apiKey = this.apiKey,
|
|
424
|
+
): Promise<Response> {
|
|
383
425
|
const controller = this.requestTimeoutMs
|
|
384
426
|
? new AbortController()
|
|
385
427
|
: undefined;
|
|
@@ -392,7 +434,7 @@ export class A2AClient {
|
|
|
392
434
|
url,
|
|
393
435
|
{
|
|
394
436
|
method: "POST",
|
|
395
|
-
headers: this.headers(),
|
|
437
|
+
headers: this.headers(apiKey),
|
|
396
438
|
body: JSON.stringify(body),
|
|
397
439
|
signal: controller?.signal,
|
|
398
440
|
},
|
|
@@ -461,6 +503,27 @@ function unique(values: string[]): string[] {
|
|
|
461
503
|
return Array.from(new Set(values));
|
|
462
504
|
}
|
|
463
505
|
|
|
506
|
+
function uniqueAuthTokens(
|
|
507
|
+
values: Array<string | undefined>,
|
|
508
|
+
): Array<string | undefined> {
|
|
509
|
+
const result: Array<string | undefined> = [];
|
|
510
|
+
for (const value of values) {
|
|
511
|
+
if (result.includes(value)) continue;
|
|
512
|
+
result.push(value);
|
|
513
|
+
}
|
|
514
|
+
if (result.length === 0) result.push(undefined);
|
|
515
|
+
return result;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function isA2AAuthRejectionResponse(status: number, text: string): boolean {
|
|
519
|
+
return (
|
|
520
|
+
status === 401 ||
|
|
521
|
+
/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(
|
|
522
|
+
text,
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
464
527
|
/**
|
|
465
528
|
* One-shot convenience function: send a text message and get a text response.
|
|
466
529
|
*
|
|
@@ -489,27 +552,6 @@ export async function callAgent(
|
|
|
489
552
|
pollIntervalMs?: number;
|
|
490
553
|
},
|
|
491
554
|
): Promise<string> {
|
|
492
|
-
let apiKey = opts?.apiKey;
|
|
493
|
-
|
|
494
|
-
// Auto-sign with JWT when an A2A secret (org or global) is available and we have a user email
|
|
495
|
-
if (
|
|
496
|
-
!apiKey &&
|
|
497
|
-
opts?.userEmail &&
|
|
498
|
-
(opts?.orgSecret || process.env.A2A_SECRET)
|
|
499
|
-
) {
|
|
500
|
-
try {
|
|
501
|
-
apiKey = await signA2AToken(
|
|
502
|
-
opts.userEmail,
|
|
503
|
-
opts.orgDomain,
|
|
504
|
-
opts.orgSecret,
|
|
505
|
-
{ preferGlobalSecret: !opts.orgSecret },
|
|
506
|
-
);
|
|
507
|
-
} catch {
|
|
508
|
-
// Fall back to unsigned call
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const client = new A2AClient(url, apiKey);
|
|
513
555
|
const metadata: Record<string, unknown> = {};
|
|
514
556
|
if (opts?.userEmail) metadata.userEmail = opts.userEmail;
|
|
515
557
|
if (opts?.orgDomain) metadata.orgDomain = opts.orgDomain;
|
|
@@ -525,39 +567,108 @@ export async function callAgent(
|
|
|
525
567
|
parts: [{ type: "text", text }],
|
|
526
568
|
};
|
|
527
569
|
|
|
528
|
-
|
|
529
|
-
|
|
570
|
+
const apiKeyAttempts = await buildA2AApiKeyAttempts(opts);
|
|
571
|
+
let lastAuthError: unknown;
|
|
572
|
+
|
|
573
|
+
for (let i = 0; i < apiKeyAttempts.length; i++) {
|
|
530
574
|
try {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
575
|
+
const fallbackApiKeys = apiKeyAttempts
|
|
576
|
+
.slice(i + 1)
|
|
577
|
+
.filter((token): token is string => token !== undefined);
|
|
578
|
+
const client = new A2AClient(url, apiKeyAttempts[i], {
|
|
579
|
+
fallbackApiKeys,
|
|
536
580
|
});
|
|
581
|
+
let task: Task;
|
|
582
|
+
if (useAsync) {
|
|
583
|
+
task = await client.sendAndWait(message, {
|
|
584
|
+
contextId: opts?.contextId,
|
|
585
|
+
metadata,
|
|
586
|
+
timeoutMs: opts?.timeoutMs,
|
|
587
|
+
pollIntervalMs: opts?.pollIntervalMs,
|
|
588
|
+
});
|
|
589
|
+
} else {
|
|
590
|
+
task = await client.send(message, {
|
|
591
|
+
contextId: opts?.contextId,
|
|
592
|
+
metadata,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Extract text from the response
|
|
597
|
+
const responseMessage = task.status.message;
|
|
598
|
+
if (responseMessage) {
|
|
599
|
+
const textParts = responseMessage.parts
|
|
600
|
+
.filter((p): p is { type: "text"; text: string } => p.type === "text")
|
|
601
|
+
.map((p) => p.text);
|
|
602
|
+
return textParts.join("\n");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return "";
|
|
537
606
|
} catch (err) {
|
|
538
607
|
if (err instanceof A2ATaskTimeoutError) {
|
|
539
608
|
const recoverableText = extractRecoverableArtifactText(err.lastTask);
|
|
540
609
|
if (recoverableText) return recoverableText;
|
|
541
610
|
}
|
|
611
|
+
if (i < apiKeyAttempts.length - 1 && isA2AAuthRejection(err)) {
|
|
612
|
+
lastAuthError = err;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
542
615
|
throw err;
|
|
543
616
|
}
|
|
544
|
-
} else {
|
|
545
|
-
task = await client.send(message, {
|
|
546
|
-
contextId: opts?.contextId,
|
|
547
|
-
metadata,
|
|
548
|
-
});
|
|
549
617
|
}
|
|
550
618
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
619
|
+
if (lastAuthError) throw lastAuthError;
|
|
620
|
+
return "";
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async function buildA2AApiKeyAttempts(opts?: {
|
|
624
|
+
apiKey?: string;
|
|
625
|
+
userEmail?: string;
|
|
626
|
+
orgDomain?: string;
|
|
627
|
+
orgSecret?: string;
|
|
628
|
+
}): Promise<Array<string | undefined>> {
|
|
629
|
+
const attempts: Array<string | undefined> = [];
|
|
630
|
+
const add = (token: string | undefined) => {
|
|
631
|
+
if (token === undefined || attempts.includes(token)) return;
|
|
632
|
+
attempts.push(token);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
add(opts?.apiKey);
|
|
636
|
+
|
|
637
|
+
if (opts?.userEmail && (opts.orgSecret || process.env.A2A_SECRET)) {
|
|
638
|
+
if (process.env.A2A_SECRET?.trim()) {
|
|
639
|
+
try {
|
|
640
|
+
add(
|
|
641
|
+
await signA2AToken(opts.userEmail, opts.orgDomain, opts.orgSecret, {
|
|
642
|
+
preferGlobalSecret: true,
|
|
643
|
+
}),
|
|
644
|
+
);
|
|
645
|
+
} catch {
|
|
646
|
+
// Keep any explicit token attempt, then fall back below.
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (opts.orgSecret) {
|
|
651
|
+
try {
|
|
652
|
+
add(
|
|
653
|
+
await signA2AToken(opts.userEmail, opts.orgDomain, opts.orgSecret, {
|
|
654
|
+
preferGlobalSecret: false,
|
|
655
|
+
}),
|
|
656
|
+
);
|
|
657
|
+
} catch {
|
|
658
|
+
// Fall through to the attempts we already have.
|
|
659
|
+
}
|
|
660
|
+
}
|
|
558
661
|
}
|
|
559
662
|
|
|
560
|
-
|
|
663
|
+
if (attempts.length === 0) attempts.push(undefined);
|
|
664
|
+
return attempts;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function isA2AAuthRejection(err: unknown): boolean {
|
|
668
|
+
const message = err instanceof Error ? err.message : String(err ?? "");
|
|
669
|
+
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(
|
|
670
|
+
message,
|
|
671
|
+
);
|
|
561
672
|
}
|
|
562
673
|
|
|
563
674
|
function extractRecoverableArtifactText(task: Task): string {
|
|
@@ -2791,9 +2791,11 @@ export async function runAgentLoop(opts: {
|
|
|
2791
2791
|
const eventStream = engine.stream(streamOpts);
|
|
2792
2792
|
let thinkingBuffer = "";
|
|
2793
2793
|
const toolInputNames = new Map<string, string>();
|
|
2794
|
+
const toolInputBytes = new Map<string, number>();
|
|
2794
2795
|
let lastToolInputActivityAt = 0;
|
|
2795
2796
|
const sendToolInputActivity = (
|
|
2796
2797
|
toolName: string | undefined,
|
|
2798
|
+
progressBytes?: number,
|
|
2797
2799
|
force = false,
|
|
2798
2800
|
) => {
|
|
2799
2801
|
const now = Date.now();
|
|
@@ -2808,6 +2810,7 @@ export async function runAgentLoop(opts: {
|
|
|
2808
2810
|
type: "activity",
|
|
2809
2811
|
label: toolInputActivityLabel(toolName),
|
|
2810
2812
|
...(toolName ? { tool: toolName } : {}),
|
|
2813
|
+
...(typeof progressBytes === "number" ? { progressBytes } : {}),
|
|
2811
2814
|
});
|
|
2812
2815
|
};
|
|
2813
2816
|
|
|
@@ -2842,13 +2845,22 @@ export async function runAgentLoop(opts: {
|
|
|
2842
2845
|
} else if (event.type === "tool-input-start") {
|
|
2843
2846
|
if (event.id && event.name) {
|
|
2844
2847
|
toolInputNames.set(event.id, event.name);
|
|
2848
|
+
toolInputBytes.set(event.id, 0);
|
|
2845
2849
|
}
|
|
2846
|
-
sendToolInputActivity(event.name, true);
|
|
2850
|
+
sendToolInputActivity(event.name, undefined, true);
|
|
2847
2851
|
} else if (event.type === "tool-input-delta") {
|
|
2848
2852
|
const toolName =
|
|
2849
2853
|
event.name ??
|
|
2850
2854
|
(event.id ? toolInputNames.get(event.id) : undefined);
|
|
2851
|
-
|
|
2855
|
+
let progressBytes: number | undefined;
|
|
2856
|
+
if (event.id) {
|
|
2857
|
+
const previous = toolInputBytes.get(event.id) ?? 0;
|
|
2858
|
+
progressBytes =
|
|
2859
|
+
previous +
|
|
2860
|
+
new TextEncoder().encode(event.text ?? "").byteLength;
|
|
2861
|
+
toolInputBytes.set(event.id, progressBytes);
|
|
2862
|
+
}
|
|
2863
|
+
sendToolInputActivity(toolName, progressBytes);
|
|
2852
2864
|
} else if (event.type === "gateway-heartbeat") {
|
|
2853
2865
|
send({ type: "stream_keepalive" });
|
|
2854
2866
|
} else if (event.type === "tool-call") {
|
|
@@ -5607,7 +5619,11 @@ export function createProductionAgentHandler(
|
|
|
5607
5619
|
const callerAuth = await resolveA2ACallerAuth({
|
|
5608
5620
|
includeGoogleToken: true,
|
|
5609
5621
|
});
|
|
5610
|
-
const a2aClient = new A2AClient(ref.path, callerAuth.apiKey
|
|
5622
|
+
const a2aClient = new A2AClient(ref.path, callerAuth.apiKey, {
|
|
5623
|
+
...(callerAuth.apiKeyFallbacks
|
|
5624
|
+
? { fallbackApiKeys: callerAuth.apiKeyFallbacks }
|
|
5625
|
+
: {}),
|
|
5626
|
+
});
|
|
5611
5627
|
const a2aMetadata = callerAuth.metadata;
|
|
5612
5628
|
|
|
5613
5629
|
let responseText = "";
|
|
@@ -212,7 +212,7 @@ export type AgentToolInput = Record<string, unknown>;
|
|
|
212
212
|
export type AgentChatEvent =
|
|
213
213
|
| { type: "text"; text: string }
|
|
214
214
|
| { type: "thinking"; text: string }
|
|
215
|
-
| { type: "activity"; label: string; tool?: string }
|
|
215
|
+
| { type: "activity"; label: string; tool?: string; progressBytes?: number }
|
|
216
216
|
| { type: "stream_keepalive" }
|
|
217
217
|
| { type: "tool_start"; tool: string; input: AgentToolInput }
|
|
218
218
|
| {
|