@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
|
@@ -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
|
-
*
|
|
93
|
-
*
|
|
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
|
-
*
|
|
272
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
!!readDeployCredentialEnv(v)))
|
|
308
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
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
|
|
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
|
|
344
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
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
|
-
|
|
374
|
-
|
|
394
|
+
const match = await usableEnvCredentialMatch(entry);
|
|
395
|
+
if (match)
|
|
396
|
+
matches.push(match);
|
|
375
397
|
}
|
|
376
|
-
|
|
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
|
|
651
|
-
//
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
const creds = await
|
|
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
|
-
|
|
686
|
-
|
|
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
|
|
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
|
|
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] & {
|