@agent-native/core 0.159.5 → 0.160.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +10 -6
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +18 -4
- package/corpus/templates/calendar/actions/create-event.ts +4 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +56 -9
- package/corpus/templates/calendar/actions/update-event.ts +1 -0
- package/corpus/templates/calendar/agent-native.config.ts +1 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +8 -2
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +102 -68
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +150 -49
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +57 -23
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +4 -1
- package/corpus/templates/calendar/app/components/calendar/TimezoneSwitchDialog.tsx +70 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +127 -88
- package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +59 -25
- package/corpus/templates/calendar/app/hooks/use-event-drag.ts +55 -18
- package/corpus/templates/calendar/app/i18n-data.ts +60 -0
- package/corpus/templates/calendar/app/lib/all-day-layout.ts +16 -8
- package/corpus/templates/calendar/app/lib/calendar-drafts.ts +56 -0
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +351 -0
- package/corpus/templates/calendar/app/lib/event-layout.ts +14 -8
- package/corpus/templates/calendar/app/lib/out-of-office.ts +29 -61
- package/corpus/templates/calendar/app/lib/working-location.ts +34 -2
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +317 -139
- package/corpus/templates/calendar/changelog/2026-08-13-calendar-stays-pinned-to-the-saved-timezone-and-asks-before-.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-13-working-locations-can-be-added-from-calendar-days-with-a-ful.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-choosing-office-or-other-when-adding-a-working-location-creates.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-creating-an-other-working-location-now-keeps-the-custom-name.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-keep-home-office-or-custom-titles.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-now-keep-a-home-office-or-custom-title.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-turning-a-midnight-ending-working-location-back-to-all-day.md +6 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +22 -1
- package/dist/agent/engine/builder-engine.d.ts +16 -2
- package/dist/agent/engine/builder-engine.js +178 -115
- package/dist/agent/engine/builtin.js +13 -4
- package/dist/agent/engine/credential-errors.d.ts +23 -0
- package/dist/agent/engine/credential-errors.js +19 -0
- package/dist/agent/engine/error-detail.d.ts +15 -0
- package/dist/agent/engine/error-detail.js +29 -0
- package/dist/agent/engine/registry.d.ts +14 -3
- package/dist/agent/engine/registry.js +106 -61
- package/dist/agent/engine/types.d.ts +16 -0
- package/dist/agent/engine/types.js +9 -0
- package/dist/agent/production-agent.d.ts +7 -0
- package/dist/agent/production-agent.js +68 -31
- package/dist/agent/run-manager.js +30 -0
- package/dist/agent/thread-data-builder.js +4 -0
- package/dist/agent/tool-approval-migrations.d.ts +1 -0
- package/dist/agent/tool-approval-migrations.js +10 -0
- package/dist/agent/tool-approval-store.d.ts +5 -3
- package/dist/agent/tool-approval-store.js +11 -6
- package/dist/agent/types.d.ts +9 -0
- package/dist/cli/code-agent-connector.js +79 -7
- package/dist/cli/portal-transfer.d.ts +43 -0
- package/dist/cli/portal-transfer.js +201 -0
- package/dist/client/agent-chat-adapter.js +46 -15
- package/dist/client/error-format.js +25 -2
- package/dist/client/sse-event-processor.d.ts +2 -0
- package/dist/client/sse-event-processor.js +12 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/jobs/background-automation-runner.js +9 -15
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/register-framework-secrets.js +25 -0
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat-plugin.js +9 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +80 -0
- package/dist/server/credential-provider.js +168 -1
- package/dist/server/google-realtime-session.d.ts +1 -1
- package/dist/server/google-realtime-session.js +6 -6
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +6 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/realtime-voice.js +12 -6
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/transcribe-voice.js +18 -16
- package/dist/transcription/builder-transcription.js +8 -4
- package/package.json +3 -3
|
@@ -1209,6 +1209,21 @@ export async function getEvent(
|
|
|
1209
1209
|
};
|
|
1210
1210
|
}
|
|
1211
1211
|
|
|
1212
|
+
function timedWorkingLocationSummary(event: CalendarEvent): string | undefined {
|
|
1213
|
+
if (event.eventType !== "workingLocation" || event.allDay) return undefined;
|
|
1214
|
+
const properties = event.workingLocationProperties;
|
|
1215
|
+
if (properties?.type === "officeLocation") {
|
|
1216
|
+
return properties.officeLocation?.label || "Office";
|
|
1217
|
+
}
|
|
1218
|
+
if (properties?.type === "customLocation") {
|
|
1219
|
+
return properties.customLocation?.label || "Working location";
|
|
1220
|
+
}
|
|
1221
|
+
if (properties?.type === "homeOffice") return "Home";
|
|
1222
|
+
const trimmed = event.title?.trim();
|
|
1223
|
+
if (trimmed && !event.titleIsGenerated) return trimmed;
|
|
1224
|
+
return "Home";
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1212
1227
|
export async function createEvent(
|
|
1213
1228
|
event: CalendarEvent,
|
|
1214
1229
|
opts: {
|
|
@@ -1230,9 +1245,15 @@ export async function createEvent(
|
|
|
1230
1245
|
throw new Error("Out of office and focus time events must be timed.");
|
|
1231
1246
|
}
|
|
1232
1247
|
|
|
1248
|
+
const workingLocationSummary = timedWorkingLocationSummary(event);
|
|
1233
1249
|
const body: any =
|
|
1234
1250
|
event.eventType === "workingLocation"
|
|
1235
|
-
?
|
|
1251
|
+
? {
|
|
1252
|
+
...buildDateRange(event),
|
|
1253
|
+
...(workingLocationSummary
|
|
1254
|
+
? { summary: workingLocationSummary }
|
|
1255
|
+
: {}),
|
|
1256
|
+
}
|
|
1236
1257
|
: {
|
|
1237
1258
|
summary: event.title,
|
|
1238
1259
|
description: event.description,
|
|
@@ -8,14 +8,26 @@
|
|
|
8
8
|
* concurrency) into structured stop events that carry an upgrade URL when
|
|
9
9
|
* the chat UI needs to prompt the user to upgrade.
|
|
10
10
|
*
|
|
11
|
-
* Credentials come from
|
|
12
|
-
* Builder
|
|
11
|
+
* Credentials come from the gateway lane (`resolveBuilderGatewayCredentials`):
|
|
12
|
+
* the user's own Builder connection when they have one, otherwise the
|
|
13
|
+
* deployment's Builder-credits pair. Base URL is overridable via
|
|
13
14
|
* BUILDER_GATEWAY_BASE_URL.
|
|
14
15
|
*/
|
|
16
|
+
import { type BuilderGatewayLane } from "../../server/credential-provider.js";
|
|
15
17
|
import type { AgentEngine, EngineCapabilities } from "./types.js";
|
|
16
18
|
export declare const BUILDER_CAPABILITIES: EngineCapabilities;
|
|
17
19
|
export declare const BUILDER_SUPPORTED_MODELS: readonly ["auto", string, "gpt-5-6-terra", "gpt-5-6-sol", "claude-haiku-4-5", "claude-sonnet-4-6" | "claude-sonnet-5", "claude-opus-4-8", "gemini-3-5-flash", "gemini-3-1-pro"];
|
|
18
20
|
export declare const BUILDER_MODEL_UNAUTHORIZED_ERROR_CODE = "builder_model_unauthorized";
|
|
21
|
+
/**
|
|
22
|
+
* A truncated stream, not a rejected request: the client continues the partial
|
|
23
|
+
* turn, so this is absent from `isRetryableError` on purpose.
|
|
24
|
+
*
|
|
25
|
+
* Every predicate that recognises `builder_gateway_network_error` must list this
|
|
26
|
+
* too, or a truncated stream silently stops recovering: `isResumableEngineError`,
|
|
27
|
+
* `isRecoverableContinuationError`, `shouldCaptureRunError`,
|
|
28
|
+
* `isInternalContinuationError` and the client's `sse-event-processor`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const BUILDER_GATEWAY_STREAM_ENDED_ERROR_CODE = "builder_gateway_stream_ended";
|
|
19
31
|
export declare const BUILDER_DEFAULT_MODEL: string;
|
|
20
32
|
/**
|
|
21
33
|
* Credentials captured by a durable caller that cannot rely on ambient
|
|
@@ -26,5 +38,7 @@ export interface BuilderEngineCredentials {
|
|
|
26
38
|
publicKey: string | null;
|
|
27
39
|
userId?: string | null;
|
|
28
40
|
orgName?: string | null;
|
|
41
|
+
/** Which lane these came from, when the capturing caller knew. */
|
|
42
|
+
lane?: BuilderGatewayLane | null;
|
|
29
43
|
}
|
|
30
44
|
export declare function createBuilderEngine(config?: Record<string, unknown>): AgentEngine;
|
|
@@ -8,19 +8,20 @@
|
|
|
8
8
|
* concurrency) into structured stop events that carry an upgrade URL when
|
|
9
9
|
* the chat UI needs to prompt the user to upgrade.
|
|
10
10
|
*
|
|
11
|
-
* Credentials come from
|
|
12
|
-
* Builder
|
|
11
|
+
* Credentials come from the gateway lane (`resolveBuilderGatewayCredentials`):
|
|
12
|
+
* the user's own Builder connection when they have one, otherwise the
|
|
13
|
+
* deployment's Builder-credits pair. Base URL is overridable via
|
|
13
14
|
* BUILDER_GATEWAY_BASE_URL.
|
|
14
15
|
*/
|
|
15
16
|
import { captureError } from "../../server/capture-error.js";
|
|
16
|
-
import {
|
|
17
|
+
import { clearBuilderGatewayAuthFailure, isBuilderGatewayDeployConfigured, resolveBuilderGatewayCredentialsDetailed, getBuilderGatewayBaseUrl, recordBuilderGatewayAuthFailure, } from "../../server/credential-provider.js";
|
|
17
18
|
import { applyBuilderUtmTrackingParams } from "../../shared/builder-link-tracking.js";
|
|
18
19
|
import { isGPTReasoningModel, normalizeReasoningEffortForModel, } from "../../shared/reasoning-effort.js";
|
|
19
20
|
import { isInBackgroundFunctionRuntime } from "../durable-background.js";
|
|
20
21
|
import { BUILDER_MODEL_CONFIG } from "../model-config.js";
|
|
21
22
|
import { getBuilderGatewayRequestHeaders } from "./builder-gateway-headers.js";
|
|
22
|
-
import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "./credential-errors.js";
|
|
23
|
-
import { describeErrorWithCauses, isProviderConnectionErrorMessage, } from "./error-detail.js";
|
|
23
|
+
import { gatewayVisitorFacingError, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "./credential-errors.js";
|
|
24
|
+
import { classifyTerminalErrorCode, describeErrorWithCauses, isContextOverflowCode, isContextOverflowMessage, isProviderConnectionErrorMessage, } from "./error-detail.js";
|
|
24
25
|
import { FIRST_STREAM_EVENT_TIMEOUT_MS } from "./first-event-timeout.js";
|
|
25
26
|
import { resolveMaxOutputTokensForEngine } from "./output-tokens.js";
|
|
26
27
|
import { splitSystemPromptForCache, stablePrefixCacheControl, } from "./prompt-cache.js";
|
|
@@ -48,6 +49,16 @@ const MAX_BACKGROUND_BUILDER_GATEWAY_TIMEOUT_MS = 14 * 60_000;
|
|
|
48
49
|
const MAX_LOCAL_BUILDER_GATEWAY_TIMEOUT_MS = MAX_BACKGROUND_BUILDER_GATEWAY_TIMEOUT_MS;
|
|
49
50
|
const BUILDER_GATEWAY_NETWORK_ERROR_CODE = "builder_gateway_network_error";
|
|
50
51
|
export const BUILDER_MODEL_UNAUTHORIZED_ERROR_CODE = "builder_model_unauthorized";
|
|
52
|
+
/**
|
|
53
|
+
* A truncated stream, not a rejected request: the client continues the partial
|
|
54
|
+
* turn, so this is absent from `isRetryableError` on purpose.
|
|
55
|
+
*
|
|
56
|
+
* Every predicate that recognises `builder_gateway_network_error` must list this
|
|
57
|
+
* too, or a truncated stream silently stops recovering: `isResumableEngineError`,
|
|
58
|
+
* `isRecoverableContinuationError`, `shouldCaptureRunError`,
|
|
59
|
+
* `isInternalContinuationError` and the client's `sse-event-processor`.
|
|
60
|
+
*/
|
|
61
|
+
export const BUILDER_GATEWAY_STREAM_ENDED_ERROR_CODE = "builder_gateway_stream_ended";
|
|
51
62
|
export const BUILDER_DEFAULT_MODEL = BUILDER_MODEL_CONFIG.defaultModel;
|
|
52
63
|
/**
|
|
53
64
|
* Bucket an Anthropic `thinking.budgetTokens` value into the gateway's
|
|
@@ -94,6 +105,15 @@ async function buildUpgradeUrl() {
|
|
|
94
105
|
});
|
|
95
106
|
return url.toString();
|
|
96
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* `isBuilderGatewayDeployConfigured()` must gate both answers: it owns the
|
|
110
|
+
* dev-preview exclusion, and a captured lane cannot substitute for it.
|
|
111
|
+
*/
|
|
112
|
+
function isBuilderCreditsLane(creds) {
|
|
113
|
+
if (!isBuilderGatewayDeployConfigured())
|
|
114
|
+
return false;
|
|
115
|
+
return creds.lane ? creds.lane === "gateway-deploy" : true;
|
|
116
|
+
}
|
|
97
117
|
class BuilderEngine {
|
|
98
118
|
configuredCredentials;
|
|
99
119
|
name = "builder";
|
|
@@ -105,17 +125,17 @@ class BuilderEngine {
|
|
|
105
125
|
this.configuredCredentials = configuredCredentials;
|
|
106
126
|
}
|
|
107
127
|
async *stream(opts) {
|
|
108
|
-
const creds = this.configuredCredentials ??
|
|
128
|
+
const creds = this.configuredCredentials ??
|
|
129
|
+
(await resolveBuilderGatewayCredentialsDetailed());
|
|
130
|
+
const creditsLane = isBuilderCreditsLane(creds);
|
|
109
131
|
const authHeader = creds.privateKey ? `Bearer ${creds.privateKey}` : null;
|
|
110
132
|
const spaceId = creds.publicKey;
|
|
111
133
|
const builderUserId = creds.userId;
|
|
112
134
|
if (!authHeader || !spaceId) {
|
|
113
|
-
yield {
|
|
114
|
-
type: "stop",
|
|
115
|
-
reason: "error",
|
|
135
|
+
yield gatewayErrorStop({
|
|
116
136
|
error: LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
117
137
|
errorCode: LLM_MISSING_CREDENTIALS_ERROR_CODE,
|
|
118
|
-
};
|
|
138
|
+
}, creditsLane);
|
|
119
139
|
return;
|
|
120
140
|
}
|
|
121
141
|
const messages = engineMessagesToBuilderGatewayAnthropic(opts.messages);
|
|
@@ -245,12 +265,12 @@ class BuilderEngine {
|
|
|
245
265
|
elapsedMs: Date.now() - tStart,
|
|
246
266
|
});
|
|
247
267
|
}
|
|
248
|
-
yield createBuilderGatewayTimeoutStop(err, timedOut, gatewayAbort.effectiveTimeoutMs());
|
|
268
|
+
yield createBuilderGatewayTimeoutStop(err, timedOut, gatewayAbort.effectiveTimeoutMs(), creditsLane);
|
|
249
269
|
return;
|
|
250
270
|
}
|
|
251
271
|
console.log(`[builder-engine] ← ${response.status} ${response.statusText} in ${Date.now() - tStart}ms`);
|
|
252
272
|
if (!response.ok) {
|
|
253
|
-
yield* emitHttpError(response);
|
|
273
|
+
yield* emitHttpError(response, { creditsLane });
|
|
254
274
|
return;
|
|
255
275
|
}
|
|
256
276
|
// A successful gateway call proves the connected credentials are valid
|
|
@@ -259,8 +279,9 @@ class BuilderEngine {
|
|
|
259
279
|
// self-healing path for workspace/env-managed credentials, which never
|
|
260
280
|
// flow through writeBuilderCredentials.
|
|
261
281
|
try {
|
|
262
|
-
const creds = this.configuredCredentials ??
|
|
263
|
-
|
|
282
|
+
const creds = this.configuredCredentials ??
|
|
283
|
+
(await resolveBuilderGatewayCredentialsDetailed());
|
|
284
|
+
await clearBuilderGatewayAuthFailure({
|
|
264
285
|
privateKey: creds.privateKey,
|
|
265
286
|
publicKey: creds.publicKey,
|
|
266
287
|
});
|
|
@@ -272,24 +293,29 @@ class BuilderEngine {
|
|
|
272
293
|
const contentType = response.headers.get("content-type") ?? "";
|
|
273
294
|
if (contentType.includes("text/html")) {
|
|
274
295
|
const rawText = await response.text().catch(() => "");
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
error
|
|
279
|
-
errorCode: `http_${
|
|
280
|
-
|
|
296
|
+
const status = response.status || 502;
|
|
297
|
+
const error = normalizeGatewayErrorText(rawText, status);
|
|
298
|
+
yield gatewayErrorStop({
|
|
299
|
+
error,
|
|
300
|
+
errorCode: `http_${status}`,
|
|
301
|
+
statusCode: status,
|
|
302
|
+
...(isTransientGatewayFailure(error, status)
|
|
303
|
+
? { providerRetryable: true }
|
|
304
|
+
: {}),
|
|
305
|
+
}, creditsLane);
|
|
281
306
|
return;
|
|
282
307
|
}
|
|
283
308
|
const reader = response.body?.getReader();
|
|
284
309
|
if (!reader) {
|
|
285
|
-
yield {
|
|
286
|
-
type: "stop",
|
|
287
|
-
reason: "error",
|
|
310
|
+
yield gatewayErrorStop({
|
|
288
311
|
error: "Builder gateway response has no body",
|
|
289
|
-
|
|
312
|
+
errorCode: "builder_gateway_error",
|
|
313
|
+
statusCode: response.status,
|
|
314
|
+
}, creditsLane);
|
|
290
315
|
return;
|
|
291
316
|
}
|
|
292
317
|
yield* parseJsonlStream(reader, opts.model, {
|
|
318
|
+
creditsLane,
|
|
293
319
|
abortSignal: gatewayAbort.signal,
|
|
294
320
|
didGatewayTimeout: gatewayAbort.didTimeout,
|
|
295
321
|
getGatewayTimeoutMs: gatewayAbort.effectiveTimeoutMs,
|
|
@@ -303,7 +329,49 @@ class BuilderEngine {
|
|
|
303
329
|
}
|
|
304
330
|
}
|
|
305
331
|
}
|
|
306
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Gateway statuses another attempt can clear. 402/401/403 quota and auth
|
|
334
|
+
* rejections are absent on purpose — they are terminal until someone acts.
|
|
335
|
+
*/
|
|
336
|
+
const RETRYABLE_GATEWAY_STATUSES = new Set([408, 429, 500, 502, 503, 504, 529]);
|
|
337
|
+
/** Read against the RAW reply: on the credits lane the message is replaced. */
|
|
338
|
+
const TRANSIENT_UPSTREAM_PATTERN = /overloaded|rate_limit|rate limit reached|too many requests|\b429\b|\b529\b|\b502\b|\b503\b|\b504\b|resource_exhausted|quota exceeded|socket hang up|connection reset|temporarily unavailable|timeout/i;
|
|
339
|
+
function isTransientGatewayFailure(rawMessage, status) {
|
|
340
|
+
if (status !== undefined && RETRYABLE_GATEWAY_STATUSES.has(status)) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
return TRANSIENT_UPSTREAM_PATTERN.test(rawMessage);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* EVERY terminal `reason: "error"` this module emits must go through here,
|
|
347
|
+
* including those with no HTTP response behind them. A branch building its own
|
|
348
|
+
* stop literal ships owner copy to a visitor; `gateway-error-retryability.spec.ts`
|
|
349
|
+
* fails on a second literal in this file.
|
|
350
|
+
*
|
|
351
|
+
* On the credits lane the message collapses to one visitor line, so
|
|
352
|
+
* `statusCode` / `providerRetryable` / `contextOverflow` are the only retry
|
|
353
|
+
* signals downstream may read: keyword coupling to the message turns a retryable
|
|
354
|
+
* throttle into a dead turn on credits sites alone.
|
|
355
|
+
*/
|
|
356
|
+
function gatewayErrorStop(details, creditsLane) {
|
|
357
|
+
const { error, errorCode, upgradeUrl, ...retry } = details;
|
|
358
|
+
return {
|
|
359
|
+
type: "stop",
|
|
360
|
+
reason: "error",
|
|
361
|
+
...(creditsLane
|
|
362
|
+
? gatewayVisitorFacingError(errorCode)
|
|
363
|
+
: {
|
|
364
|
+
error,
|
|
365
|
+
...(errorCode ? { errorCode } : {}),
|
|
366
|
+
...(upgradeUrl ? { upgradeUrl } : {}),
|
|
367
|
+
}),
|
|
368
|
+
...(isContextOverflowMessage(error) || isContextOverflowCode(errorCode)
|
|
369
|
+
? { contextOverflow: true }
|
|
370
|
+
: {}),
|
|
371
|
+
...retry,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
async function* emitHttpError(response, opts) {
|
|
307
375
|
const status = response.status;
|
|
308
376
|
// Read the body once as text and then try to parse — calling `.json()`
|
|
309
377
|
// and then `.text()` as a fallback fails because the body stream is
|
|
@@ -321,84 +389,67 @@ async function* emitHttpError(response) {
|
|
|
321
389
|
}
|
|
322
390
|
const code = errBody.code ?? `http_${status}`;
|
|
323
391
|
const message = errBody.message ?? `Builder gateway returned ${status}`;
|
|
392
|
+
const stop = (details) => gatewayErrorStop(details, opts.creditsLane);
|
|
324
393
|
// Belt-and-suspenders: 402 without a structured `credits-limit` code
|
|
325
394
|
// (e.g. bare proxy response) still means quota → show upgrade CTA.
|
|
326
395
|
if (code.startsWith("credits-limit") || status === 402) {
|
|
327
|
-
yield {
|
|
328
|
-
type: "stop",
|
|
329
|
-
reason: "error",
|
|
396
|
+
yield stop({
|
|
330
397
|
error: message,
|
|
331
398
|
errorCode: code,
|
|
332
399
|
upgradeUrl: await buildUpgradeUrl(),
|
|
333
|
-
};
|
|
400
|
+
});
|
|
334
401
|
return;
|
|
335
402
|
}
|
|
336
403
|
if (code === "gateway_not_enabled") {
|
|
337
|
-
yield {
|
|
338
|
-
type: "stop",
|
|
339
|
-
reason: "error",
|
|
340
|
-
error: message,
|
|
341
|
-
errorCode: code,
|
|
342
|
-
};
|
|
404
|
+
yield stop({ error: message, errorCode: code });
|
|
343
405
|
return;
|
|
344
406
|
}
|
|
345
407
|
if (status === 401 || code === "unauthorized") {
|
|
346
|
-
await
|
|
347
|
-
yield {
|
|
348
|
-
type: "stop",
|
|
349
|
-
reason: "error",
|
|
408
|
+
await recordBuilderGatewayAuthFailure({ status, code, message });
|
|
409
|
+
yield stop({
|
|
350
410
|
error: "Builder authentication failed. Reconnect Builder (free tier available) via Settings.",
|
|
351
411
|
errorCode: "builder_auth_error",
|
|
352
|
-
};
|
|
412
|
+
});
|
|
353
413
|
return;
|
|
354
414
|
}
|
|
355
415
|
if (status === 403 && isBuilderCredentialAuthError(message)) {
|
|
356
|
-
await
|
|
357
|
-
yield {
|
|
358
|
-
type: "stop",
|
|
359
|
-
reason: "error",
|
|
416
|
+
await recordBuilderGatewayAuthFailure({ status, code, message });
|
|
417
|
+
yield stop({
|
|
360
418
|
error: "Builder authentication failed. Reconnect Builder (free tier available) via Settings.",
|
|
361
419
|
errorCode: "builder_auth_error",
|
|
362
|
-
};
|
|
420
|
+
});
|
|
363
421
|
return;
|
|
364
422
|
}
|
|
365
423
|
if (status === 403) {
|
|
366
|
-
yield {
|
|
367
|
-
type: "stop",
|
|
368
|
-
reason: "error",
|
|
369
|
-
error: message,
|
|
370
|
-
errorCode: code,
|
|
371
|
-
};
|
|
424
|
+
yield stop({ error: message, errorCode: code });
|
|
372
425
|
return;
|
|
373
426
|
}
|
|
374
427
|
if (code === "rate_limit_exceeded") {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
errorCode: code,
|
|
380
|
-
};
|
|
428
|
+
// The daily cap shares 429 with the transient throttle below and must not
|
|
429
|
+
// loop, so it carries NEITHER retry field: a bare `statusCode: 429` reads as
|
|
430
|
+
// retryable on its own.
|
|
431
|
+
yield stop({ error: message, errorCode: code });
|
|
381
432
|
return;
|
|
382
433
|
}
|
|
383
434
|
if (status === 429 || code === "too_many_concurrent_requests") {
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
yield {
|
|
389
|
-
type: "stop",
|
|
390
|
-
reason: "error",
|
|
391
|
-
error: `${message} (too many requests)`,
|
|
435
|
+
// Daily gateway caps use `rate_limit_exceeded` above and must not loop;
|
|
436
|
+
// this branch is the transient concurrency throttle, which does.
|
|
437
|
+
yield stop({
|
|
438
|
+
error: message,
|
|
392
439
|
errorCode: code,
|
|
393
|
-
|
|
440
|
+
statusCode: status,
|
|
441
|
+
providerRetryable: true,
|
|
442
|
+
});
|
|
394
443
|
return;
|
|
395
444
|
}
|
|
396
|
-
yield {
|
|
397
|
-
type: "stop",
|
|
398
|
-
reason: "error",
|
|
445
|
+
yield stop({
|
|
399
446
|
error: message,
|
|
400
447
|
errorCode: code,
|
|
401
|
-
|
|
448
|
+
statusCode: status,
|
|
449
|
+
...(isTransientGatewayFailure(message, status)
|
|
450
|
+
? { providerRetryable: true }
|
|
451
|
+
: {}),
|
|
452
|
+
});
|
|
402
453
|
}
|
|
403
454
|
// Yields one non-empty JSONL line at a time. Flushes any trailing content
|
|
404
455
|
// after the stream ends so a final event without a newline terminator
|
|
@@ -482,12 +533,12 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
482
533
|
}
|
|
483
534
|
catch {
|
|
484
535
|
const normalized = normalizeGatewayErrorText(line, 502);
|
|
485
|
-
yield {
|
|
486
|
-
type: "stop",
|
|
487
|
-
reason: "error",
|
|
536
|
+
yield gatewayErrorStop({
|
|
488
537
|
error: `Builder gateway returned invalid JSONL: ${normalized.slice(0, 240)}`,
|
|
489
538
|
errorCode: "http_502",
|
|
490
|
-
|
|
539
|
+
statusCode: 502,
|
|
540
|
+
providerRetryable: true,
|
|
541
|
+
}, captureContext.creditsLane);
|
|
491
542
|
return;
|
|
492
543
|
}
|
|
493
544
|
// Heartbeats are transport-level keepalives, not proof the model is
|
|
@@ -568,15 +619,13 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
568
619
|
yield* recoverUndeliveredToolCalls();
|
|
569
620
|
yield { type: "assistant-content", parts };
|
|
570
621
|
const reason = event.reason ?? "end_turn";
|
|
622
|
+
const stop = (details) => gatewayErrorStop(details, captureContext.creditsLane);
|
|
571
623
|
if (reason === "rate_limited") {
|
|
572
|
-
|
|
573
|
-
// isRetryableError picks it up and retries.
|
|
574
|
-
yield {
|
|
575
|
-
type: "stop",
|
|
576
|
-
reason: "error",
|
|
624
|
+
yield stop({
|
|
577
625
|
error: `rate_limit exceeded: ${event.error ?? "upstream provider rate limited"}`,
|
|
578
626
|
errorCode: "rate_limited",
|
|
579
|
-
|
|
627
|
+
providerRetryable: true,
|
|
628
|
+
});
|
|
580
629
|
}
|
|
581
630
|
else if (reason === "invalid_request") {
|
|
582
631
|
// errorCode has no retry-trigger keywords, so isRetryableError
|
|
@@ -590,12 +639,7 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
590
639
|
? event.code
|
|
591
640
|
: "invalid_request";
|
|
592
641
|
console.warn(`[builder-engine] stop reason=invalid_request model=${model} code=${errCode} error=${errMsg}`);
|
|
593
|
-
yield {
|
|
594
|
-
type: "stop",
|
|
595
|
-
reason: "error",
|
|
596
|
-
error: errMsg,
|
|
597
|
-
errorCode: errCode,
|
|
598
|
-
};
|
|
642
|
+
yield stop({ error: errMsg, errorCode: errCode });
|
|
599
643
|
}
|
|
600
644
|
else if (reason === "error") {
|
|
601
645
|
// Surface every diagnostic the gateway gave us so the user (and
|
|
@@ -628,10 +672,16 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
628
672
|
: isProviderConnectionError
|
|
629
673
|
? BUILDER_GATEWAY_NETWORK_ERROR_CODE
|
|
630
674
|
: (gatewayErrCode ??
|
|
631
|
-
(!explicitErrMsg
|
|
675
|
+
(!explicitErrMsg
|
|
676
|
+
? "builder_gateway_error"
|
|
677
|
+
: // A detailed in-stream error the gateway left uncoded:
|
|
678
|
+
// classify the RAW sentence here, because run persistence
|
|
679
|
+
// would otherwise do it downstream on the visitor line and
|
|
680
|
+
// record `unknown` on the credits lane alone.
|
|
681
|
+
classifyTerminalErrorCode(String(errMsg))));
|
|
632
682
|
console.error(`[builder-engine] stop reason=error model=${model} code=${errCode ?? "(none)"} error=${errMsg}`);
|
|
633
683
|
if (isCredentialAuthError) {
|
|
634
|
-
await
|
|
684
|
+
await recordBuilderGatewayAuthFailure({
|
|
635
685
|
code: typeof gatewayErrCode === "string" ? gatewayErrCode : errCode,
|
|
636
686
|
message: String(errMsg),
|
|
637
687
|
});
|
|
@@ -652,12 +702,16 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
652
702
|
rawEvent: event,
|
|
653
703
|
});
|
|
654
704
|
}
|
|
655
|
-
yield {
|
|
656
|
-
|
|
657
|
-
reason: "error",
|
|
658
|
-
error: errMsg,
|
|
705
|
+
yield stop({
|
|
706
|
+
error: String(errMsg),
|
|
659
707
|
...(errCode ? { errorCode: errCode } : {}),
|
|
660
|
-
|
|
708
|
+
// The upstream provider giving up ("Overloaded", a bare 529) is
|
|
709
|
+
// retryable, and the raw text is the only place it says so — a
|
|
710
|
+
// stop event carries no status.
|
|
711
|
+
...(isTransientGatewayFailure(String(errMsg))
|
|
712
|
+
? { providerRetryable: true }
|
|
713
|
+
: {}),
|
|
714
|
+
});
|
|
661
715
|
}
|
|
662
716
|
else if (reason === "end_turn" ||
|
|
663
717
|
reason === "tool_use" ||
|
|
@@ -666,11 +720,7 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
666
720
|
yield { type: "stop", reason };
|
|
667
721
|
}
|
|
668
722
|
else {
|
|
669
|
-
yield {
|
|
670
|
-
type: "stop",
|
|
671
|
-
reason: "error",
|
|
672
|
-
error: `Unknown stop reason: ${reason}`,
|
|
673
|
-
};
|
|
723
|
+
yield stop({ error: `Unknown stop reason: ${reason}` });
|
|
674
724
|
}
|
|
675
725
|
return;
|
|
676
726
|
}
|
|
@@ -683,11 +733,10 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
683
733
|
flushPending();
|
|
684
734
|
yield* recoverUndeliveredToolCalls();
|
|
685
735
|
yield { type: "assistant-content", parts };
|
|
686
|
-
yield {
|
|
687
|
-
type: "stop",
|
|
688
|
-
reason: "error",
|
|
736
|
+
yield gatewayErrorStop({
|
|
689
737
|
error: "Builder gateway stream ended without a stop event",
|
|
690
|
-
|
|
738
|
+
errorCode: BUILDER_GATEWAY_STREAM_ENDED_ERROR_CODE,
|
|
739
|
+
}, captureContext.creditsLane);
|
|
691
740
|
}
|
|
692
741
|
catch (err) {
|
|
693
742
|
const timedOut = captureContext.didGatewayTimeout?.() ?? false;
|
|
@@ -705,7 +754,7 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
|
|
|
705
754
|
: undefined,
|
|
706
755
|
});
|
|
707
756
|
}
|
|
708
|
-
yield createBuilderGatewayTimeoutStop(err, timedOut, gatewayTimeoutMs);
|
|
757
|
+
yield createBuilderGatewayTimeoutStop(err, timedOut, gatewayTimeoutMs, captureContext.creditsLane);
|
|
709
758
|
}
|
|
710
759
|
finally {
|
|
711
760
|
// Release the reader on every exit path — early returns (invalid JSONL,
|
|
@@ -914,18 +963,32 @@ function normalizeBuilderGatewayFetchError(err, timedOut, timeoutMs) {
|
|
|
914
963
|
}
|
|
915
964
|
return message;
|
|
916
965
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
966
|
+
/**
|
|
967
|
+
* Derived from the RAW error before `gatewayErrorStop` replaces the message: on
|
|
968
|
+
* the credits lane run-manager has no text left to classify at persistence time,
|
|
969
|
+
* and a run persisted as `unknown` reads as "do not attempt recovery".
|
|
970
|
+
*/
|
|
971
|
+
function createBuilderGatewayTimeoutStop(err, timedOut, timeoutMs, creditsLane) {
|
|
972
|
+
const error = normalizeBuilderGatewayFetchError(err, timedOut, timeoutMs);
|
|
973
|
+
if (timedOut) {
|
|
974
|
+
// Deliberately no `providerRetryable`: the timeout spent the whole request
|
|
975
|
+
// budget, so the recovery is a fresh invocation (the client's
|
|
976
|
+
// `builder_gateway_timeout` continuation), never an in-call retry.
|
|
977
|
+
return gatewayErrorStop({ error, errorCode: "builder_gateway_timeout" }, creditsLane);
|
|
978
|
+
}
|
|
979
|
+
if (isBuilderGatewayNetworkError(err)) {
|
|
980
|
+
return gatewayErrorStop({
|
|
981
|
+
error,
|
|
982
|
+
errorCode: BUILDER_GATEWAY_NETWORK_ERROR_CODE,
|
|
983
|
+
providerRetryable: true,
|
|
984
|
+
}, creditsLane);
|
|
985
|
+
}
|
|
986
|
+
const errorCode = classifyTerminalErrorCode(error);
|
|
987
|
+
return gatewayErrorStop({
|
|
988
|
+
error,
|
|
989
|
+
...(errorCode ? { errorCode } : {}),
|
|
990
|
+
...(isTransientGatewayFailure(error) ? { providerRetryable: true } : {}),
|
|
991
|
+
}, creditsLane);
|
|
929
992
|
}
|
|
930
993
|
function formatTimeoutMs(timeoutMs) {
|
|
931
994
|
if (timeoutMs < 1000)
|
|
@@ -18,10 +18,13 @@ export function registerBuiltinEngines() {
|
|
|
18
18
|
return;
|
|
19
19
|
_registered = true;
|
|
20
20
|
// ── Builder.io managed gateway ─────────────────────────────────────────────
|
|
21
|
-
// Registered first so
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
21
|
+
// Registered first, so it wins whenever a customer configured Builder — the
|
|
22
|
+
// legacy key pair included. Two credential shapes select it: a user's own
|
|
23
|
+
// connection via that pair, or the deployment's Builder-credits token plus
|
|
24
|
+
// space id, which is the only lane an anonymous visitor on a hosted site has.
|
|
25
|
+
// Only the injected Builder-credits set is marked deployInjected, so it alone
|
|
26
|
+
// steps aside for a provider key the customer set (see selectDetectedEngine);
|
|
27
|
+
// users who prefer BYO everywhere can still set AGENT_ENGINE_PREFER_BYO_KEY.
|
|
25
28
|
registerAgentEngine({
|
|
26
29
|
name: "builder",
|
|
27
30
|
label: "Builder.io Gateway",
|
|
@@ -30,6 +33,12 @@ export function registerBuiltinEngines() {
|
|
|
30
33
|
defaultModel: BUILDER_DEFAULT_MODEL,
|
|
31
34
|
supportedModels: BUILDER_SUPPORTED_MODELS,
|
|
32
35
|
requiredEnvVars: ["BUILDER_PRIVATE_KEY", "BUILDER_PUBLIC_KEY"],
|
|
36
|
+
alternateRequiredEnvVars: [
|
|
37
|
+
{
|
|
38
|
+
envVars: ["BUILDER_GATEWAY_TOKEN", "BUILDER_GATEWAY_SPACE_ID"],
|
|
39
|
+
deployInjected: true,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
33
42
|
create: (config) => createBuilderEngine(config),
|
|
34
43
|
});
|
|
35
44
|
// ── Anthropic ──────────────────────────────────────────────────────────────
|