@agent-native/core 0.159.5 → 0.160.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/assets/server/lib/generation.ts +10 -6
  3. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +18 -4
  4. package/corpus/templates/calendar/actions/create-event.ts +4 -2
  5. package/corpus/templates/calendar/actions/event-action-helpers.ts +56 -9
  6. package/corpus/templates/calendar/actions/update-event.ts +1 -0
  7. package/corpus/templates/calendar/agent-native.config.ts +1 -0
  8. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +8 -2
  9. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +102 -68
  10. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +150 -49
  11. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +57 -23
  12. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +4 -1
  13. package/corpus/templates/calendar/app/components/calendar/TimezoneSwitchDialog.tsx +70 -0
  14. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +127 -88
  15. package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +59 -25
  16. package/corpus/templates/calendar/app/hooks/use-event-drag.ts +55 -18
  17. package/corpus/templates/calendar/app/i18n-data.ts +60 -0
  18. package/corpus/templates/calendar/app/lib/all-day-layout.ts +16 -8
  19. package/corpus/templates/calendar/app/lib/calendar-drafts.ts +56 -0
  20. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +351 -0
  21. package/corpus/templates/calendar/app/lib/event-layout.ts +14 -8
  22. package/corpus/templates/calendar/app/lib/out-of-office.ts +29 -61
  23. package/corpus/templates/calendar/app/lib/working-location.ts +34 -2
  24. package/corpus/templates/calendar/app/pages/CalendarView.tsx +317 -139
  25. package/corpus/templates/calendar/changelog/2026-08-13-calendar-stays-pinned-to-the-saved-timezone-and-asks-before-.md +6 -0
  26. package/corpus/templates/calendar/changelog/2026-08-13-working-locations-can-be-added-from-calendar-days-with-a-ful.md +6 -0
  27. package/corpus/templates/calendar/changelog/2026-08-14-choosing-office-or-other-when-adding-a-working-location-creates.md +6 -0
  28. package/corpus/templates/calendar/changelog/2026-08-14-creating-an-other-working-location-now-keeps-the-custom-name.md +6 -0
  29. package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-keep-home-office-or-custom-titles.md +6 -0
  30. package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-now-keep-a-home-office-or-custom-title.md +6 -0
  31. package/corpus/templates/calendar/changelog/2026-08-14-turning-a-midnight-ending-working-location-back-to-all-day.md +6 -0
  32. package/corpus/templates/calendar/server/lib/google-calendar.ts +22 -1
  33. package/dist/agent/engine/builder-engine.d.ts +16 -2
  34. package/dist/agent/engine/builder-engine.js +178 -115
  35. package/dist/agent/engine/builtin.js +13 -4
  36. package/dist/agent/engine/credential-errors.d.ts +23 -0
  37. package/dist/agent/engine/credential-errors.js +19 -0
  38. package/dist/agent/engine/error-detail.d.ts +15 -0
  39. package/dist/agent/engine/error-detail.js +29 -0
  40. package/dist/agent/engine/registry.d.ts +14 -3
  41. package/dist/agent/engine/registry.js +106 -61
  42. package/dist/agent/engine/types.d.ts +16 -0
  43. package/dist/agent/engine/types.js +9 -0
  44. package/dist/agent/production-agent.d.ts +7 -0
  45. package/dist/agent/production-agent.js +58 -20
  46. package/dist/agent/run-manager.js +30 -0
  47. package/dist/agent/thread-data-builder.js +4 -0
  48. package/dist/agent/types.d.ts +9 -0
  49. package/dist/client/agent-chat-adapter.js +46 -15
  50. package/dist/client/error-format.js +25 -2
  51. package/dist/client/sse-event-processor.d.ts +2 -0
  52. package/dist/client/sse-event-processor.js +12 -1
  53. package/dist/collab/awareness.d.ts +2 -2
  54. package/dist/collab/struct-routes.d.ts +1 -1
  55. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  56. package/dist/jobs/background-automation-runner.js +9 -15
  57. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  58. package/dist/notifications/routes.d.ts +6 -6
  59. package/dist/observability/routes.d.ts +5 -5
  60. package/dist/resources/handlers.d.ts +1 -1
  61. package/dist/secrets/register-framework-secrets.js +25 -0
  62. package/dist/secrets/routes.d.ts +9 -9
  63. package/dist/server/agent-chat-plugin.js +9 -2
  64. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  65. package/dist/server/credential-provider.d.ts +80 -0
  66. package/dist/server/credential-provider.js +168 -1
  67. package/dist/server/google-realtime-session.d.ts +1 -1
  68. package/dist/server/google-realtime-session.js +6 -6
  69. package/dist/server/index.d.ts +1 -1
  70. package/dist/server/index.js +6 -1
  71. package/dist/server/realtime-token.d.ts +1 -1
  72. package/dist/server/realtime-voice.js +12 -6
  73. package/dist/server/transcribe-voice.d.ts +1 -1
  74. package/dist/server/transcribe-voice.js +18 -16
  75. package/dist/transcription/builder-transcription.js +8 -4
  76. package/package.json +1 -1
@@ -6,10 +6,33 @@ export declare const LLM_MISSING_CREDENTIALS_ERROR_CODE = "missing_credentials";
6
6
  */
7
7
  export declare const CREDENTIAL_STORE_UNAVAILABLE_ERROR_CODE = "credential_store_unavailable";
8
8
  export declare const LLM_MISSING_CREDENTIALS_MESSAGE = "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io (free tier available) or add a provider key.";
9
+ /**
10
+ * The one line a site visitor sees for every gateway rejection. Quota,
11
+ * concurrency, a revoked token, a disabled gateway and an unreadable credential
12
+ * store all read the same to someone with no account and no settings page.
13
+ */
14
+ export declare const GATEWAY_UNAVAILABLE_VISITOR_MESSAGE = "AI features aren't available on this site right now.";
15
+ /**
16
+ * Rewrite a gateway rejection for a visitor on a Builder-credits site: one
17
+ * message, and the real reason preserved on `errorCode` for the site owner, who
18
+ * is the only party who can act on it.
19
+ */
20
+ export declare function gatewayVisitorFacingError(errorCode?: string): {
21
+ error: string;
22
+ errorCode?: string;
23
+ };
9
24
  export declare function isLlmCredentialError(error: unknown, errorCode?: string | null): boolean;
10
25
  export declare function formatLlmCredentialErrorMessage(options?: {
11
26
  agentName?: string;
27
+ /**
28
+ * True when the reader is a visitor on a deployment that pays for its own AI:
29
+ * they have no Builder account, no Settings page and nothing to connect, so
30
+ * the owner-facing instructions name an action they cannot take. The real
31
+ * reason stays on the error code, which is where the owner reads it.
32
+ */
33
+ visitorFacing?: boolean;
12
34
  }): string;
13
35
  export declare function userFacingLlmCredentialError(error: unknown, options?: {
14
36
  agentName?: string;
37
+ visitorFacing?: boolean;
15
38
  }): string | null;
@@ -7,6 +7,23 @@ export const LLM_MISSING_CREDENTIALS_ERROR_CODE = "missing_credentials";
7
7
  */
8
8
  export const CREDENTIAL_STORE_UNAVAILABLE_ERROR_CODE = "credential_store_unavailable";
9
9
  export const LLM_MISSING_CREDENTIALS_MESSAGE = "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io (free tier available) or add a provider key.";
10
+ /**
11
+ * The one line a site visitor sees for every gateway rejection. Quota,
12
+ * concurrency, a revoked token, a disabled gateway and an unreadable credential
13
+ * store all read the same to someone with no account and no settings page.
14
+ */
15
+ export const GATEWAY_UNAVAILABLE_VISITOR_MESSAGE = "AI features aren't available on this site right now.";
16
+ /**
17
+ * Rewrite a gateway rejection for a visitor on a Builder-credits site: one
18
+ * message, and the real reason preserved on `errorCode` for the site owner, who
19
+ * is the only party who can act on it.
20
+ */
21
+ export function gatewayVisitorFacingError(errorCode) {
22
+ return {
23
+ error: GATEWAY_UNAVAILABLE_VISITOR_MESSAGE,
24
+ ...(errorCode ? { errorCode } : {}),
25
+ };
26
+ }
10
27
  const LLM_CREDENTIAL_KEYS = new Set([
11
28
  ...PROVIDER_ENV_VARS,
12
29
  "BUILDER_PRIVATE_KEY",
@@ -38,6 +55,8 @@ export function isLlmCredentialError(error, errorCode) {
38
55
  return MISSING_CREDENTIAL_PATTERNS.some((pattern) => pattern.test(message));
39
56
  }
40
57
  export function formatLlmCredentialErrorMessage(options) {
58
+ if (options?.visitorFacing)
59
+ return GATEWAY_UNAVAILABLE_VISITOR_MESSAGE;
41
60
  const agentName = options?.agentName?.trim();
42
61
  if (agentName) {
43
62
  return `The ${agentName} agent could not finish this request because that app needs an LLM connection. Open ${agentName}'s Manage agent > LLM, then connect Builder.io (free tier available) or add a provider key.`;
@@ -22,6 +22,21 @@ export declare function describeErrorWithCauses(err: unknown, maxLinks?: number)
22
22
  export declare function isProviderConnectionErrorMessage(message: string): boolean;
23
23
  /** `isProviderConnectionErrorMessage` over an error's full cause chain. */
24
24
  export declare function isProviderConnectionError(err: unknown): boolean;
25
+ /**
26
+ * The single context-window-overflow classifier, shared by the layer that reads
27
+ * a provider's raw reply and the layer that decides to trim and retry.
28
+ *
29
+ * It lives here, beside the transport classifier, because the Builder gateway
30
+ * reports an overflow as an ordinary 400 `invalid_request_error` whose prose is
31
+ * the only carrier — and on a Builder-credits deployment that prose is replaced
32
+ * by one visitor line before any agent-level predicate sees it. The engine
33
+ * therefore runs this against the RAW reply and hands the verdict on as a
34
+ * structural field; `isContextTooLongError` (production-agent) keeps calling it
35
+ * for every other engine, which still delivers its own message intact.
36
+ */
37
+ export declare function isContextOverflowMessage(message: string): boolean;
38
+ /** The overflow codes a provider or gateway may report instead of prose. */
39
+ export declare function isContextOverflowCode(code: string | undefined): boolean;
25
40
  /** Classification fields an AI SDK provider failure carries. */
26
41
  export interface ProviderErrorClassification {
27
42
  errorCode?: string;
@@ -60,6 +60,35 @@ export function isProviderConnectionErrorMessage(message) {
60
60
  export function isProviderConnectionError(err) {
61
61
  return isProviderConnectionErrorMessage(describeErrorWithCauses(err));
62
62
  }
63
+ /**
64
+ * The single context-window-overflow classifier, shared by the layer that reads
65
+ * a provider's raw reply and the layer that decides to trim and retry.
66
+ *
67
+ * It lives here, beside the transport classifier, because the Builder gateway
68
+ * reports an overflow as an ordinary 400 `invalid_request_error` whose prose is
69
+ * the only carrier — and on a Builder-credits deployment that prose is replaced
70
+ * by one visitor line before any agent-level predicate sees it. The engine
71
+ * therefore runs this against the RAW reply and hands the verdict on as a
72
+ * structural field; `isContextTooLongError` (production-agent) keeps calling it
73
+ * for every other engine, which still delivers its own message intact.
74
+ */
75
+ export function isContextOverflowMessage(message) {
76
+ const msg = message.toLowerCase();
77
+ return (msg.includes("context_length_exceeded") ||
78
+ msg.includes("input_too_long") ||
79
+ msg.includes("too many tokens") ||
80
+ msg.includes("prompt is too long") ||
81
+ msg.includes("reduce the length") ||
82
+ // Gemini phrasing
83
+ msg.includes("input token count exceeds") ||
84
+ msg.includes("request too large"));
85
+ }
86
+ /** The overflow codes a provider or gateway may report instead of prose. */
87
+ export function isContextOverflowCode(code) {
88
+ const normalized = (code ?? "").toLowerCase();
89
+ return (normalized.includes("context_length") ||
90
+ normalized.includes("input_too_long"));
91
+ }
63
92
  /**
64
93
  * Classify a provider error from the AI SDK, whichever way it surfaced.
65
94
  *
@@ -9,6 +9,16 @@
9
9
  */
10
10
  import { type BuilderCredentialLookupIdentity } from "../../server/credential-provider.js";
11
11
  import type { AgentEngine, EngineCapabilities } from "./types.js";
12
+ export interface AgentEngineEnvCredentialSet {
13
+ /** Every var here must resolve for the set to satisfy the engine. */
14
+ envVars: string[];
15
+ /**
16
+ * Injected by a deploy pipeline rather than configured by the owner. Selects
17
+ * when nothing else resolves, but never outranks an owner-configured
18
+ * credential — including the legacy Builder pair, which keeps normal priority.
19
+ */
20
+ deployInjected?: boolean;
21
+ }
12
22
  export interface AgentEngineEntry {
13
23
  /** Unique name, e.g. "anthropic", "ai-sdk:anthropic", "ai-sdk:openai" */
14
24
  name: string;
@@ -26,6 +36,8 @@ export interface AgentEngineEntry {
26
36
  supportedModels: readonly string[];
27
37
  /** Environment variables required for this engine to work */
28
38
  requiredEnvVars: string[];
39
+ /** Alternative credential shapes; detection treats these and `requiredEnvVars` as OR. */
40
+ alternateRequiredEnvVars?: AgentEngineEnvCredentialSet[];
29
41
  /** Create an engine instance from config */
30
42
  create(config: Record<string, unknown>): AgentEngine;
31
43
  }
@@ -89,9 +101,8 @@ export declare function resolveDelegatedRunModel(engine: ModelResolvableEngine,
89
101
  */
90
102
  export declare function resolveEnginePreservesCustomModels(entry: Pick<AgentEngineEntry, "name">): Promise<boolean>;
91
103
  /**
92
- * First registered engine whose requiredEnvVars are all set. Registration
93
- * order controls priority — the Builder gateway is registered first so it
94
- * wins when the Builder private key is present.
104
+ * Registered engines whose env credentials are all set. Selection is
105
+ * {@link selectDetectedEngine}'s job.
95
106
  *
96
107
  * Escape hatch: AGENT_ENGINE_PREFER_BYO_KEY=true skips the Builder engine
97
108
  * on the first pass, so an explicit provider key (ANTHROPIC_API_KEY etc.)
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { createRequire } from "node:module";
11
11
  import { getAppConfig } from "../../app-config/index.js";
12
- import { assertCredentialStoreReadable, canUseDeployCredentialFallbackForRequest, getBuilderCredentialAuthFailure, getProviderCredentialAuthFailure, readDeployCredentialEnv, resolveBuilderCredentialsDetailed, resolveSecret, } from "../../server/credential-provider.js";
12
+ import { assertCredentialStoreReadable, canUseDeployCredentialFallbackForRequest, getBuilderCredentialAuthFailure, getProviderCredentialAuthFailure, readDeployCredentialEnv, resolveBuilderCredentialsDetailed, resolveBuilderGatewayCredentialsDetailed, resolveSecret, } from "../../server/credential-provider.js";
13
13
  import { getSetting } from "../../settings/store.js";
14
14
  import { getAgentAppModelDefaultForCurrentRequest } from "../app-model-defaults.js";
15
15
  import { OLLAMA_BASE_URL_ENV_VAR, OPENAI_BASE_URL_ENV_VAR, } from "./openai-compatible-endpoint.js";
@@ -267,10 +267,35 @@ function assertAgentEnginePackageInstalled(entry) {
267
267
  : "";
268
268
  throw new Error(`[agent-engine] Engine "${entry.name}" requires optional packages that are not installed in this app.${installHint}`);
269
269
  }
270
+ /** The single source both detectors read, so alternates cannot diverge between them. */
271
+ function envCredentialSetsForEntry(entry) {
272
+ const sets = [];
273
+ if (entry.requiredEnvVars.length > 0) {
274
+ sets.push({ envVars: entry.requiredEnvVars, deployInjected: false });
275
+ }
276
+ for (const alternate of entry.alternateRequiredEnvVars ?? []) {
277
+ if (alternate.envVars.length > 0) {
278
+ sets.push({
279
+ envVars: alternate.envVars,
280
+ deployInjected: alternate.deployInjected === true,
281
+ });
282
+ }
283
+ }
284
+ return sets;
285
+ }
286
+ /**
287
+ * Registration order decides, except that an injected set loses to any
288
+ * owner-configured credential. Builder is registered first, so without that
289
+ * exception an injected token would move a BYO customer onto Builder credits.
290
+ */
291
+ function selectDetectedEngine(matches) {
292
+ return (matches.find((match) => !match.deployInjected)?.entry ??
293
+ matches[0]?.entry ??
294
+ null);
295
+ }
270
296
  /**
271
- * First registered engine whose requiredEnvVars are all set. Registration
272
- * order controls priority — the Builder gateway is registered first so it
273
- * wins when the Builder private key is present.
297
+ * Registered engines whose env credentials are all set. Selection is
298
+ * {@link selectDetectedEngine}'s job.
274
299
  *
275
300
  * Escape hatch: AGENT_ENGINE_PREFER_BYO_KEY=true skips the Builder engine
276
301
  * on the first pass, so an explicit provider key (ANTHROPIC_API_KEY etc.)
@@ -283,32 +308,22 @@ function assertAgentEnginePackageInstalled(entry) {
283
308
  */
284
309
  export function detectEngineFromEnv() {
285
310
  const preferByo = getAppConfig().agent.preferBringYourOwnKey;
286
- if (preferByo) {
287
- for (const entry of _registry.values()) {
288
- if (entry.name === "builder")
289
- continue;
290
- if (entry.requiredEnvVars.length === 0)
291
- continue;
292
- if (!isAgentEnginePackageInstalled(entry))
293
- continue;
294
- if (entry.requiredEnvVars.every((v) => canUseDeployCredentialFallbackForRequest(v) &&
295
- !!readDeployCredentialEnv(v))) {
296
- return entry;
297
- }
298
- }
299
- // No BYO key matched — fall through to include Builder as fallback.
300
- }
311
+ const matches = [];
301
312
  for (const entry of _registry.values()) {
302
- if (entry.requiredEnvVars.length === 0)
303
- continue;
304
313
  if (!isAgentEnginePackageInstalled(entry))
305
314
  continue;
306
- if (entry.requiredEnvVars.every((v) => canUseDeployCredentialFallbackForRequest(v) &&
307
- !!readDeployCredentialEnv(v))) {
308
- return entry;
309
- }
315
+ const set = envCredentialSetsForEntry(entry).find((candidate) => candidate.envVars.every((v) => canUseDeployCredentialFallbackForRequest(v) &&
316
+ !!readDeployCredentialEnv(v)));
317
+ if (set)
318
+ matches.push({ entry, deployInjected: set.deployInjected });
310
319
  }
311
- return null;
320
+ if (preferByo) {
321
+ const byo = matches.find((match) => match.entry.name !== "builder");
322
+ // No BYO key matched — fall through to include Builder as fallback.
323
+ if (byo)
324
+ return byo.entry;
325
+ }
326
+ return selectDetectedEngine(matches);
312
327
  }
313
328
  async function envKeyUsableForEntry(key) {
314
329
  if (!(canUseDeployCredentialFallbackForRequest(key) &&
@@ -320,16 +335,17 @@ async function envKeyUsableForEntry(key) {
320
335
  return false;
321
336
  return !(await getProviderCredentialAuthFailure({ key, value }));
322
337
  }
338
+ const BUILDER_LEGACY_ENV_PAIR = ["BUILDER_PRIVATE_KEY", "BUILDER_PUBLIC_KEY"];
339
+ function isBuilderLegacyEnvPair(envVars) {
340
+ return BUILDER_LEGACY_ENV_PAIR.every((key) => envVars.includes(key));
341
+ }
323
342
  /**
324
- * Builder's deploy-env fallback is checked as a pair, not per-key: the
325
- * auth-failure marker is fingerprinted from privateKey+publicKey together
326
- * (see `builderCredentialFingerprint`), so a single-key lookup can never
327
- * match it. Without this, a rejected deploy-level Builder key would keep
328
- * reporting "usable" through this env-only path forever — the same class of
329
- * bug as the per-scope check in `credential-provider.ts`'s
330
- * `isCompleteBuilderConnection`.
343
+ * The legacy pair's marker is fingerprinted from both keys together, so the
344
+ * per-var lookup in {@link envKeyUsableForEntry} can never match it and a rejected
345
+ * pair would report "usable" forever. Selected by credential shape, not engine
346
+ * name: every other set carries a per-var marker already.
331
347
  */
332
- async function hasUsableBuilderEnvKeys() {
348
+ async function hasUsableBuilderLegacyEnvPair() {
333
349
  const privateKey = canUseDeployCredentialFallbackForRequest("BUILDER_PRIVATE_KEY")
334
350
  ? readDeployCredentialEnv("BUILDER_PRIVATE_KEY")
335
351
  : null;
@@ -340,19 +356,31 @@ async function hasUsableBuilderEnvKeys() {
340
356
  return false;
341
357
  return !(await getBuilderCredentialAuthFailure({ privateKey, publicKey }));
342
358
  }
343
- async function hasUsableEnvKeys(entry) {
344
- if (!isAgentEnginePackageInstalled(entry))
359
+ async function isEnvCredentialSetUsable(set) {
360
+ // Every var in the set must resolve — the paired check answers only for the
361
+ // two legacy keys, so a set that carries them alongside anything else still
362
+ // owes the per-var check on the rest, exactly as `detectEngineFromEnv` does.
363
+ const pairedCheck = isBuilderLegacyEnvPair(set.envVars);
364
+ if (pairedCheck && !(await hasUsableBuilderLegacyEnvPair()))
345
365
  return false;
346
- if (entry.requiredEnvVars.length === 0)
347
- return false;
348
- if (entry.name === "builder")
349
- return hasUsableBuilderEnvKeys();
350
- for (const key of entry.requiredEnvVars) {
366
+ for (const key of set.envVars) {
367
+ if (pairedCheck && BUILDER_LEGACY_ENV_PAIR.includes(key))
368
+ continue;
351
369
  if (!(await envKeyUsableForEntry(key)))
352
370
  return false;
353
371
  }
354
372
  return true;
355
373
  }
374
+ async function usableEnvCredentialMatch(entry) {
375
+ if (!isAgentEnginePackageInstalled(entry))
376
+ return null;
377
+ for (const set of envCredentialSetsForEntry(entry)) {
378
+ if (await isEnvCredentialSetUsable(set)) {
379
+ return { entry, deployInjected: set.deployInjected };
380
+ }
381
+ }
382
+ return null;
383
+ }
356
384
  /**
357
385
  * Request-aware env auto-detect. Same priority as {@link detectEngineFromEnv},
358
386
  * but skips provider keys that currently have an auth-failure marker so a
@@ -361,19 +389,18 @@ async function hasUsableEnvKeys(entry) {
361
389
  */
362
390
  export async function detectEngineFromEnvForRequest() {
363
391
  const preferByo = getAppConfig().agent.preferBringYourOwnKey;
364
- if (preferByo) {
365
- for (const entry of _registry.values()) {
366
- if (entry.name === "builder")
367
- continue;
368
- if (await hasUsableEnvKeys(entry))
369
- return entry;
370
- }
371
- }
392
+ const matches = [];
372
393
  for (const entry of _registry.values()) {
373
- if (await hasUsableEnvKeys(entry))
374
- return entry;
394
+ const match = await usableEnvCredentialMatch(entry);
395
+ if (match)
396
+ matches.push(match);
375
397
  }
376
- return null;
398
+ if (preferByo) {
399
+ const byo = matches.find((match) => match.entry.name !== "builder");
400
+ if (byo)
401
+ return byo.entry;
402
+ }
403
+ return selectDetectedEngine(matches);
377
404
  }
378
405
  function shouldTraceEngineDetection() {
379
406
  return /^(1|true)$/i.test(process.env.AGENT_NATIVE_DEBUG_AGENT_ENGINE_DETECT ??
@@ -533,6 +560,15 @@ async function hasUsableBuilderConnection(identity) {
533
560
  assertCredentialStoreReadable(creds);
534
561
  return Boolean(creds.privateKey && creds.publicKey);
535
562
  }
563
+ /**
564
+ * Either lane. Not {@link hasUsableBuilderConnection}, which asks the narrower
565
+ * "did this user connect Builder" and is false on every credits-only site.
566
+ */
567
+ async function canRunBuilderEngine(identity) {
568
+ const creds = await resolveBuilderGatewayCredentialsDetailed(identity);
569
+ assertCredentialStoreReadable(creds);
570
+ return Boolean(creds.privateKey && creds.publicKey);
571
+ }
536
572
  async function resolveUsableProviderSecret(key) {
537
573
  const value = await resolveSecret(key);
538
574
  if (!value)
@@ -647,11 +683,11 @@ async function engineCreateConfigForEntry(entry, apiKey, extra, credentialResolu
647
683
  }
648
684
  if (entry.name === "builder" &&
649
685
  (credentialIdentity !== undefined || safeExtra.credentials == null)) {
650
- // Builder authentication is a private/public key pair, not the single
651
- // provider key carried by ResolveEngineConfig. Capture the scoped pair
652
- // while the verified request identity is available so a later stream or
653
- // detached run cannot resolve credentials from the wrong ambient context.
654
- const creds = await resolveBuilderCredentialsDetailed(credentialIdentity);
686
+ // Builder authentication is a token plus space id, not the single provider
687
+ // key carried by ResolveEngineConfig. Capture the gateway-lane pair while
688
+ // the verified request identity is available so a later stream or detached
689
+ // run cannot resolve credentials from the wrong ambient context.
690
+ const creds = await resolveBuilderGatewayCredentialsDetailed(credentialIdentity);
655
691
  assertCredentialStoreReadable(creds);
656
692
  if (credentialIdentity !== undefined ||
657
693
  creds.source !== null ||
@@ -661,6 +697,7 @@ async function engineCreateConfigForEntry(entry, apiKey, extra, credentialResolu
661
697
  publicKey: creds.publicKey,
662
698
  userId: creds.userId,
663
699
  orgName: creds.orgName,
700
+ lane: creds.lane,
664
701
  };
665
702
  }
666
703
  }
@@ -682,8 +719,16 @@ export function isStoredEngineUsable(stored, entry) {
682
719
  return true;
683
720
  if (entry.requiredEnvVars.length === 0)
684
721
  return true;
685
- return entry.requiredEnvVars.every((v) => canUseDeployCredentialFallbackForRequest(v) &&
686
- !!readDeployCredentialEnv(v));
722
+ // Every credential set, not just `requiredEnvVars`. Reading only the latter
723
+ // reports a Builder engine running on the injected gateway pair as
724
+ // unconfigured, which the engine-status endpoint surfaces as "no provider" and
725
+ // the composer refuses to start on — while the request path, which does read
726
+ // the alternates, runs the same engine perfectly well.
727
+ const sets = envCredentialSetsForEntry(entry);
728
+ if (sets.length === 0)
729
+ return true;
730
+ return sets.some((set) => set.envVars.every((v) => canUseDeployCredentialFallbackForRequest(v) &&
731
+ !!readDeployCredentialEnv(v)));
687
732
  }
688
733
  /**
689
734
  * Request-aware version of `isStoredEngineUsable`.
@@ -701,7 +746,7 @@ export async function isStoredEngineUsableForRequest(stored, entry, options = {}
701
746
  if (entry.requiredEnvVars.length === 0)
702
747
  return true;
703
748
  if (entry.name === "builder") {
704
- return hasUsableBuilderConnection(options.credentialIdentity);
749
+ return canRunBuilderEngine(options.credentialIdentity);
705
750
  }
706
751
  for (const key of entry.requiredEnvVars) {
707
752
  if (!(await resolveUsableProviderSecret(key)))
@@ -726,7 +771,7 @@ export async function isResolvedEngineUsableForRequest(engine, options = {}) {
726
771
  if (entry.requiredEnvVars.length === 0)
727
772
  return true;
728
773
  if (entry.name === "builder") {
729
- return hasUsableBuilderConnection(options.credentialIdentity);
774
+ return canRunBuilderEngine(options.credentialIdentity);
730
775
  }
731
776
  if (options.apiKey?.trim()) {
732
777
  const key = entry.requiredEnvVars[0];
@@ -26,11 +26,20 @@ export declare class EngineError extends Error {
26
26
  readonly statusCode?: number;
27
27
  /** Whether the provider explicitly marked this error as retryable. */
28
28
  readonly providerRetryable?: boolean;
29
+ /**
30
+ * Whether the request exceeded the model's context window. Set by engines that
31
+ * classified the provider's own reply, because the delivered message may not
32
+ * be that reply: a Builder-credits deployment replaces it with one visitor
33
+ * line, which leaves `isContextTooLongError` nothing to match and kills the
34
+ * one-shot trim-and-retry recovery.
35
+ */
36
+ readonly contextOverflow?: boolean;
29
37
  constructor(message: string, opts?: {
30
38
  errorCode?: string;
31
39
  upgradeUrl?: string;
32
40
  statusCode?: number;
33
41
  providerRetryable?: boolean;
42
+ contextOverflow?: boolean;
34
43
  });
35
44
  }
36
45
  /**
@@ -187,6 +196,13 @@ export type EngineEvent = {
187
196
  * should retry even if status code / message patterns don't match.
188
197
  */
189
198
  providerRetryable?: boolean;
199
+ /**
200
+ * The request exceeded the model's context window. Carried structurally
201
+ * for the same reason as `providerRetryable`: `error` is visitor copy on a
202
+ * Builder-credits deployment, so a verdict left in the prose is gone by
203
+ * the time the agent decides whether to trim and retry.
204
+ */
205
+ contextOverflow?: boolean;
190
206
  };
191
207
  export interface EngineCapabilities {
192
208
  /** Extended / adaptive thinking support */
@@ -25,6 +25,14 @@ export class EngineError extends Error {
25
25
  statusCode;
26
26
  /** Whether the provider explicitly marked this error as retryable. */
27
27
  providerRetryable;
28
+ /**
29
+ * Whether the request exceeded the model's context window. Set by engines that
30
+ * classified the provider's own reply, because the delivered message may not
31
+ * be that reply: a Builder-credits deployment replaces it with one visitor
32
+ * line, which leaves `isContextTooLongError` nothing to match and kills the
33
+ * one-shot trim-and-retry recovery.
34
+ */
35
+ contextOverflow;
28
36
  constructor(message, opts) {
29
37
  super(message);
30
38
  this.name = "EngineError";
@@ -32,5 +40,6 @@ export class EngineError extends Error {
32
40
  this.upgradeUrl = opts?.upgradeUrl;
33
41
  this.statusCode = opts?.statusCode;
34
42
  this.providerRetryable = opts?.providerRetryable;
43
+ this.contextOverflow = opts?.contextOverflow;
35
44
  }
36
45
  }
@@ -889,6 +889,13 @@ export declare function runAgentLoop(opts: {
889
889
  */
890
890
  processors?: Processor[];
891
891
  }): Promise<AgentLoopUsage>;
892
+ /** @internal exported for unit tests only */
893
+ export declare function isRecoverableContinuationError(event: {
894
+ type: "error";
895
+ error: string;
896
+ errorCode?: string;
897
+ recoverable?: boolean;
898
+ }): boolean;
892
899
  export declare function lastUnfinishedPreparingActionToolFromEvents(events: readonly AgentChatEvent[]): string | undefined;
893
900
  export declare function backgroundContinuationReasonForRun(run: ActiveRun): AgentLoopContinuationReason;
894
901
  export declare function runAgentLoopWithMainChatInternalContinuations(opts: Parameters<typeof runAgentLoop>[0] & {