@agent-native/core 0.159.3 → 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 (90) 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/chat-first/app-pane.js +1 -1
  51. package/dist/client/chat-first/copy.js +1 -0
  52. package/dist/client/error-format.js +25 -2
  53. package/dist/client/sse-event-processor.d.ts +2 -0
  54. package/dist/client/sse-event-processor.js +12 -1
  55. package/dist/feature-flags/index.d.ts +0 -2
  56. package/dist/feature-flags/index.js +3 -2
  57. package/dist/feature-flags/server.d.ts +2 -0
  58. package/dist/feature-flags/server.js +2 -0
  59. package/dist/index.d.ts +2 -1
  60. package/dist/index.js +2 -1
  61. package/dist/jobs/background-automation-runner.js +9 -15
  62. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  63. package/dist/notifications/routes.d.ts +3 -3
  64. package/dist/observability/routes.d.ts +2 -2
  65. package/dist/progress/routes.d.ts +1 -1
  66. package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
  67. package/dist/resources/handlers.d.ts +1 -1
  68. package/dist/secrets/register-framework-secrets.js +25 -0
  69. package/dist/secrets/routes.d.ts +6 -6
  70. package/dist/server/agent-chat-plugin.js +9 -2
  71. package/dist/server/auth.js +10 -0
  72. package/dist/server/credential-provider.d.ts +80 -0
  73. package/dist/server/credential-provider.js +168 -1
  74. package/dist/server/google-realtime-session.d.ts +1 -1
  75. package/dist/server/google-realtime-session.js +6 -6
  76. package/dist/server/index.d.ts +2 -2
  77. package/dist/server/index.js +7 -2
  78. package/dist/server/onboarding-html.js +16 -257
  79. package/dist/server/realtime-token.d.ts +1 -1
  80. package/dist/server/realtime-voice.js +12 -6
  81. package/dist/server/transcribe-voice.d.ts +1 -1
  82. package/dist/server/transcribe-voice.js +18 -16
  83. package/dist/shared/auth-copy.d.ts +34 -0
  84. package/dist/shared/auth-copy.js +293 -0
  85. package/dist/shared/index.d.ts +1 -0
  86. package/dist/shared/index.js +1 -0
  87. package/dist/transcription/builder-transcription.js +8 -4
  88. package/docs/content/actions-advanced.mdx +1 -1
  89. package/docs/content/portal.mdx +21 -8
  90. package/package.json +3 -1
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
137
137
  inputSchema: {
138
138
  type: string;
139
139
  properties: {
140
+ count?: undefined;
140
141
  query?: undefined;
141
142
  minutes?: undefined;
142
143
  limit?: undefined;
143
144
  clientHint?: undefined;
144
145
  timestamp?: undefined;
145
146
  chapterId?: undefined;
146
- count?: undefined;
147
147
  startAt?: undefined;
148
148
  endAt?: undefined;
149
149
  reason?: undefined;
@@ -159,6 +159,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
159
159
  inputSchema: {
160
160
  type: string;
161
161
  properties: {
162
+ count?: undefined;
162
163
  query: {
163
164
  type: string;
164
165
  description: string;
@@ -174,7 +175,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
174
175
  clientHint?: undefined;
175
176
  timestamp?: undefined;
176
177
  chapterId?: undefined;
177
- count?: undefined;
178
178
  startAt?: undefined;
179
179
  endAt?: undefined;
180
180
  reason?: undefined;
@@ -190,6 +190,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
190
190
  inputSchema: {
191
191
  type: string;
192
192
  properties: {
193
+ count?: undefined;
193
194
  minutes: {
194
195
  type: string;
195
196
  description: string;
@@ -199,7 +200,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
199
200
  clientHint?: undefined;
200
201
  timestamp?: undefined;
201
202
  chapterId?: undefined;
202
- count?: undefined;
203
203
  startAt?: undefined;
204
204
  endAt?: undefined;
205
205
  reason?: undefined;
@@ -216,6 +216,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
216
216
  type: string;
217
217
  required: string[];
218
218
  properties: {
219
+ count?: undefined;
219
220
  query: {
220
221
  type: string;
221
222
  description: string;
@@ -234,7 +235,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
234
235
  };
235
236
  timestamp?: undefined;
236
237
  chapterId?: undefined;
237
- count?: undefined;
238
238
  startAt?: undefined;
239
239
  endAt?: undefined;
240
240
  reason?: undefined;
@@ -250,6 +250,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
250
250
  type: string;
251
251
  required: string[];
252
252
  properties: {
253
+ count?: undefined;
253
254
  query?: undefined;
254
255
  minutes?: undefined;
255
256
  limit?: undefined;
@@ -263,7 +264,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
263
264
  description: string;
264
265
  };
265
266
  chapterId?: undefined;
266
- count?: undefined;
267
267
  startAt?: undefined;
268
268
  endAt?: undefined;
269
269
  includeMicrophone?: undefined;
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
314
314
  type: string;
315
315
  required: string[];
316
316
  properties: {
317
+ count?: undefined;
317
318
  query?: undefined;
318
319
  minutes?: undefined;
319
320
  limit?: undefined;
320
321
  clientHint?: undefined;
321
322
  timestamp?: undefined;
322
323
  chapterId?: undefined;
323
- count?: undefined;
324
324
  startAt: {
325
325
  type: string;
326
326
  description: string;
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
349
349
  type: string;
350
350
  required: string[];
351
351
  properties: {
352
+ count?: undefined;
352
353
  query?: undefined;
353
354
  minutes?: undefined;
354
355
  limit?: undefined;
355
356
  clientHint?: undefined;
356
357
  timestamp?: undefined;
357
358
  chapterId?: undefined;
358
- count?: undefined;
359
359
  startAt?: undefined;
360
360
  endAt?: undefined;
361
361
  reason?: undefined;
@@ -16,18 +16,18 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
16
16
  error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
+ count?: undefined;
19
20
  updated: number;
20
21
  error?: undefined;
21
22
  ok?: undefined;
22
- count?: undefined;
23
23
  } | {
24
+ count?: undefined;
24
25
  updated?: undefined;
25
26
  error: string;
26
27
  ok?: undefined;
27
- count?: undefined;
28
28
  } | {
29
+ count?: undefined;
29
30
  updated?: undefined;
30
31
  error?: undefined;
31
32
  ok: boolean;
32
- count?: undefined;
33
33
  }>>;
@@ -41,14 +41,14 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- ok?: undefined;
45
44
  error?: undefined;
45
+ ok?: undefined;
46
46
  summary: import("./types.js").TraceSummary;
47
47
  spans: import("./types.js").TraceSpan[];
48
48
  id?: undefined;
49
49
  } | {
50
- ok?: undefined;
51
50
  error?: undefined;
51
+ ok?: undefined;
52
52
  summary?: undefined;
53
53
  spans?: undefined;
54
54
  id: string;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
@@ -75,8 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- message?: undefined;
79
78
  id?: undefined;
79
+ message?: undefined;
80
80
  deleted?: undefined;
81
81
  providers: {
82
82
  id: string;
@@ -93,9 +93,9 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
93
93
  registered?: undefined;
94
94
  label?: undefined;
95
95
  } | {
96
+ id?: undefined;
96
97
  message?: undefined;
97
98
  count?: undefined;
98
- id?: undefined;
99
99
  deleted?: undefined;
100
100
  providers?: undefined;
101
101
  found: boolean;
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- ok?: undefined;
52
51
  error: string;
52
+ ok?: undefined;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -107,6 +107,31 @@ export function registerFrameworkSecrets() {
107
107
  });
108
108
  }
109
109
  }
110
+ if (!getRequiredSecret("ANTHROPIC_API_KEY")) {
111
+ registerRequiredSecret({
112
+ key: "ANTHROPIC_API_KEY",
113
+ label: "Anthropic API key",
114
+ description: "Bring your own Claude key instead of routing model calls through Builder.io.",
115
+ docsUrl: "https://console.anthropic.com/settings/keys",
116
+ scope: "user",
117
+ kind: "api-key",
118
+ required: false,
119
+ validator: async (value) => {
120
+ const response = await fetch("https://api.anthropic.com/v1/models", {
121
+ headers: {
122
+ "x-api-key": value,
123
+ "anthropic-version": "2023-06-01",
124
+ },
125
+ });
126
+ return response.ok
127
+ ? { ok: true }
128
+ : {
129
+ ok: false,
130
+ error: `Anthropic rejected the key (HTTP ${response.status}).`,
131
+ };
132
+ },
133
+ });
134
+ }
110
135
  if (!getRequiredSecret("OPENAI_API_KEY")) {
111
136
  registerRequiredSecret({
112
137
  key: "OPENAI_API_KEY",
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
37
37
  ok?: undefined;
38
38
  status?: undefined;
39
39
  } | {
40
+ error?: undefined;
40
41
  ok: boolean;
41
42
  status: string;
42
- error?: undefined;
43
43
  } | {
44
44
  ok?: undefined;
45
45
  error: string;
46
46
  removed?: undefined;
47
47
  } | {
48
+ error?: undefined;
48
49
  ok: boolean;
49
50
  removed: boolean;
50
- error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
58
58
  error: string;
59
59
  note?: undefined;
60
60
  } | {
61
+ error?: undefined;
61
62
  ok: boolean;
62
63
  note?: undefined;
63
- error?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  ok: boolean;
66
67
  note: string;
67
- error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
+ error?: undefined;
98
99
  ok: boolean;
99
100
  key: string;
100
- error?: undefined;
101
101
  } | {
102
+ error?: undefined;
102
103
  ok: boolean;
103
104
  removed: boolean;
104
- error?: undefined;
105
105
  }>>;
@@ -814,11 +814,11 @@ export function createAgentChatPlugin(options) {
814
814
  try {
815
815
  if (webGroupEnabled) {
816
816
  const { createWebSearchToolEntry } = await import("../extensions/web-search-tool.js");
817
- const { getBuilderWebSearchBaseUrl, resolveBuilderCredentials, resolveSecret, } = await import("./credential-provider.js");
817
+ const { getBuilderWebSearchBaseUrl, resolveBuilderGatewayCredentials, resolveSecret, } = await import("./credential-provider.js");
818
818
  const { getBuilderGatewayRequestHeaders } = await import("../agent/engine/builder-gateway-headers.js");
819
819
  webSearchTool = createWebSearchToolEntry({
820
820
  resolveSecret,
821
- resolveBuilderCredentials,
821
+ resolveBuilderCredentials: resolveBuilderGatewayCredentials,
822
822
  getBuilderWebSearchBaseUrl,
823
823
  getBuilderRequestHeaders: getBuilderGatewayRequestHeaders,
824
824
  });
@@ -4161,6 +4161,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4161
4161
  const workerClaim = run.runId
4162
4162
  ? await readBackgroundRunClaim(run.runId).catch(() => null)
4163
4163
  : null;
4164
+ const { isBuilderGatewayDeployConfigured } = await import("./credential-provider.js");
4164
4165
  return {
4165
4166
  active: true,
4166
4167
  runId: run.runId,
@@ -4177,6 +4178,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4177
4178
  // `/runs/active?threadId=...` and inspect `diagStage`.
4178
4179
  dispatchMode: run.dispatchMode ?? null,
4179
4180
  terminalReason: run.terminalReason ?? null,
4181
+ // Who is paying for AI here, which is also who is reading a
4182
+ // failure. `terminalReason` is a bare error CODE, and the client
4183
+ // owns the copy for the handoff failures that never produce an
4184
+ // error event — so without this it has to author credential copy
4185
+ // for a reader it cannot identify, and picks the owner's.
4186
+ deploymentPaysForAi: isBuilderGatewayDeployConfigured(),
4180
4187
  diagStage: run.diagStage ?? null,
4181
4188
  workerStage: workerClaim?.workerStage ?? null,
4182
4189
  // Server clock so the client computes "stuck" elapsed time
@@ -1981,6 +1981,16 @@ function createAuthGuardFn(app) {
1981
1981
  p === `${MCP_PUBLIC_ROUTE_PREFIX}/oauth/register`) {
1982
1982
  return;
1983
1983
  }
1984
+ // Remote hosts authenticate these relay calls with their device token,
1985
+ // not a browser session. Keep this list exact: sibling remote routes such
1986
+ // as register, enqueue, and computer controls are session-authenticated.
1987
+ if (p === "/_agent-native/integrations/remote/unregister" ||
1988
+ p === "/_agent-native/integrations/remote/heartbeat" ||
1989
+ p === "/_agent-native/integrations/remote/poll" ||
1990
+ p === "/_agent-native/integrations/remote/result" ||
1991
+ p === "/_agent-native/integrations/remote/run-events") {
1992
+ return;
1993
+ }
1984
1994
  // Cross-app SSO ("Sign in with Agent-Native") — CLIENT side. Both the
1985
1995
  // `/login` entry point and the `/callback` (hit by a user who is, by
1986
1996
  // definition, NOT yet signed in to THIS app) must bypass the blanket
@@ -83,6 +83,10 @@ export declare function readDeployCredentialEnv(key: string): string | undefined
83
83
  */
84
84
  export declare function isDeployCredentialFallbackAllowed(): boolean;
85
85
  export declare function canUseDeployCredentialFallbackForRequest(key?: string): boolean;
86
+ /** The raw `btk-` gateway PAT a Builder-credits deploy injects. */
87
+ export declare const BUILDER_GATEWAY_TOKEN_ENV_VAR = "BUILDER_GATEWAY_TOKEN";
88
+ /** The space id that pairs with it, sent as `x-builder-api-key`. */
89
+ export declare const BUILDER_GATEWAY_SPACE_ID_ENV_VAR = "BUILDER_GATEWAY_SPACE_ID";
86
90
  type BuilderCredentialSource = "user" | "org" | "workspace" | "env";
87
91
  export declare function isBuilderPrivateKey(value: string | null | undefined): boolean;
88
92
  export interface BuilderCredentialLookupIdentity {
@@ -177,6 +181,67 @@ export declare function resolveBuilderCredentials(identity?: BuilderCredentialLo
177
181
  isEnterprise: boolean | null;
178
182
  isFreeAccount: boolean | null;
179
183
  }>;
184
+ /** `gateway-deploy` means the site pays, so owner-facing copy must not be shown. */
185
+ export type BuilderGatewayLane = "identity" | "gateway-deploy";
186
+ export interface BuilderGatewayCredentialsDetailed extends BuilderCredentialsDetailed {
187
+ lane: BuilderGatewayLane | null;
188
+ }
189
+ /**
190
+ * Both halves required: the gateway 403s a token with no space id before it
191
+ * consults any route policy, so half a pair is not a usable credential.
192
+ */
193
+ export declare function resolveUsableBuilderGatewayDeployCredentials(): Promise<{
194
+ token: string;
195
+ spaceId: string;
196
+ } | null>;
197
+ /**
198
+ * Weaker than "usable" on purpose: a present-but-rejected token still means the
199
+ * reader is a visitor. Excludes the dev preview, which carries the same token but
200
+ * is read by the project owner.
201
+ */
202
+ export declare function isBuilderGatewayDeployConfigured(): boolean;
203
+ /** One decision for every gateway-lane consumer; a per-consumer copy drifts. */
204
+ export declare function gatewayLaneUnavailableMessage(ownerFacing: string): string;
205
+ /**
206
+ * Gateway-lane credentials. Fall-through: the request's own Builder connection,
207
+ * then the deployment's credits pair, then the legacy pair.
208
+ *
209
+ * Identity-bearing calls (asset upload, design systems, Admin GraphQL, browser
210
+ * agent) must keep using `resolveBuilderCredentials`: a `['gateway']` token 403s
211
+ * on all of them.
212
+ */
213
+ export declare function resolveBuilderGatewayCredentialsDetailed(identity?: BuilderCredentialLookupIdentity): Promise<BuilderGatewayCredentialsDetailed>;
214
+ /**
215
+ * Gateway-lane credentials in the same shape as `resolveBuilderCredentials`, so
216
+ * a consumer moves lane by changing which resolver it calls and nothing else.
217
+ */
218
+ export declare function resolveBuilderGatewayCredentials(identity?: BuilderCredentialLookupIdentity): Promise<{
219
+ privateKey: string | null;
220
+ publicKey: string | null;
221
+ userId: string | null;
222
+ orgName: string | null;
223
+ orgKind: string | null;
224
+ subscription: string | null;
225
+ subscriptionLevel: string | null;
226
+ subscriptionName: string | null;
227
+ isEnterprise: boolean | null;
228
+ isFreeAccount: boolean | null;
229
+ }>;
230
+ export interface BuilderGatewayAuth {
231
+ /** `Bearer <token>` for the `Authorization` header. */
232
+ authorization: string;
233
+ /** Send as `x-builder-api-key`. Null only for a legacy single-key deployment. */
234
+ spaceId: string | null;
235
+ /** Send as `x-builder-user-id` when the lane carries a Builder user. */
236
+ userId: string | null;
237
+ }
238
+ /**
239
+ * The gate for gateway-lane features. Not `resolveHasBuilderPrivateKey`, which is
240
+ * identity-only and false on a credits site.
241
+ */
242
+ export declare function resolveHasBuilderGatewayCredential(): Promise<boolean>;
243
+ /** Gateway-lane `resolveBuilderAuthHeader`, same fall-through order. */
244
+ export declare function resolveBuilderGatewayAuth(): Promise<BuilderGatewayAuth | null>;
180
245
  /**
181
246
  * Stale markers expire so a rejected model or a transient gateway failure
182
247
  * cannot pin a signed-in user to "Builder not connected" forever. Only a
@@ -235,6 +300,21 @@ export declare function clearProviderCredentialAuthFailure(opts: {
235
300
  key?: string | null;
236
301
  value?: string | null;
237
302
  }): Promise<void>;
303
+ /**
304
+ * `builderCredentialFingerprint` needs both legacy keys, so on a credits site the
305
+ * legacy marker is a no-op and a rejected token would be resent forever. The
306
+ * gateway lane fingerprints its single token through the provider marker.
307
+ */
308
+ export declare function recordBuilderGatewayAuthFailure(details?: {
309
+ status?: number;
310
+ code?: string;
311
+ message?: string;
312
+ }): Promise<void>;
313
+ /** Each clear is a no-op off its own lane, so callers need not know which they are on. */
314
+ export declare function clearBuilderGatewayAuthFailure(creds: {
315
+ privateKey?: string | null;
316
+ publicKey?: string | null;
317
+ }): Promise<void>;
238
318
  /**
239
319
  * Write Builder credentials to `app_secrets`.
240
320
  *
@@ -18,7 +18,7 @@
18
18
  * (e.g. additional Builder-hosted services) without rewrites.
19
19
  */
20
20
  import { createHash } from "node:crypto";
21
- import { CREDENTIAL_STORE_UNAVAILABLE_ERROR_CODE } from "../agent/engine/credential-errors.js";
21
+ import { CREDENTIAL_STORE_UNAVAILABLE_ERROR_CODE, GATEWAY_UNAVAILABLE_VISITOR_MESSAGE, } from "../agent/engine/credential-errors.js";
22
22
  import { getAppConfig } from "../app-config/index.js";
23
23
  import { getDbExec, isLocalDatabase, isTransientDatabaseError, } from "../db/client.js";
24
24
  import { getOrgSetting } from "../settings/org-settings.js";
@@ -128,6 +128,12 @@ export function readDeployCredentialEnv(key) {
128
128
  }
129
129
  const APP_PROVIDED_DEPLOY_CREDENTIAL_KEYS = new Set([
130
130
  "ANTHROPIC_API_KEY",
131
+ // The Builder-credits pair pays for the deployed app's own model calls and
132
+ // carries no end-user identity — the token is scoped to ['gateway'] and can
133
+ // make no identity-bearing Builder call. The legacy BUILDER_PRIVATE_KEY /
134
+ // BUILDER_PUBLIC_KEY pair can, so it must never be added to this set.
135
+ "BUILDER_GATEWAY_TOKEN",
136
+ "BUILDER_GATEWAY_SPACE_ID",
131
137
  "EMAIL_FROM",
132
138
  "EMAIL_INBOUND_WEBHOOK_SECRET",
133
139
  "EMAIL_AGENT_ADDRESS",
@@ -180,6 +186,10 @@ export function canUseDeployCredentialFallbackForRequest(key) {
180
186
  return true;
181
187
  return isLocalDatabase();
182
188
  }
189
+ /** The raw `btk-` gateway PAT a Builder-credits deploy injects. */
190
+ export const BUILDER_GATEWAY_TOKEN_ENV_VAR = "BUILDER_GATEWAY_TOKEN";
191
+ /** The space id that pairs with it, sent as `x-builder-api-key`. */
192
+ export const BUILDER_GATEWAY_SPACE_ID_ENV_VAR = "BUILDER_GATEWAY_SPACE_ID";
183
193
  const BUILDER_CREDENTIAL_KEYS = [
184
194
  "BUILDER_PRIVATE_KEY",
185
195
  "BUILDER_PUBLIC_KEY",
@@ -702,6 +712,132 @@ export async function resolveBuilderCredentials(identity) {
702
712
  isFreeAccount,
703
713
  };
704
714
  }
715
+ /**
716
+ * Both halves required: the gateway 403s a token with no space id before it
717
+ * consults any route policy, so half a pair is not a usable credential.
718
+ */
719
+ export async function resolveUsableBuilderGatewayDeployCredentials() {
720
+ const token = canUseDeployCredentialFallbackForRequest(BUILDER_GATEWAY_TOKEN_ENV_VAR)
721
+ ? readDeployCredentialEnv(BUILDER_GATEWAY_TOKEN_ENV_VAR)
722
+ : undefined;
723
+ const spaceId = canUseDeployCredentialFallbackForRequest(BUILDER_GATEWAY_SPACE_ID_ENV_VAR)
724
+ ? readDeployCredentialEnv(BUILDER_GATEWAY_SPACE_ID_ENV_VAR)
725
+ : undefined;
726
+ if (!token || !spaceId)
727
+ return null;
728
+ const failure = await getProviderCredentialAuthFailure({
729
+ key: BUILDER_GATEWAY_TOKEN_ENV_VAR,
730
+ value: token,
731
+ });
732
+ return failure ? null : { token, spaceId };
733
+ }
734
+ /**
735
+ * Weaker than "usable" on purpose: a present-but-rejected token still means the
736
+ * reader is a visitor. Excludes the dev preview, which carries the same token but
737
+ * is read by the project owner.
738
+ */
739
+ export function isBuilderGatewayDeployConfigured() {
740
+ if (isHostedWorkspaceRuntime())
741
+ return false;
742
+ return Boolean(readDeployCredentialEnv(BUILDER_GATEWAY_TOKEN_ENV_VAR));
743
+ }
744
+ /** One decision for every gateway-lane consumer; a per-consumer copy drifts. */
745
+ export function gatewayLaneUnavailableMessage(ownerFacing) {
746
+ return isBuilderGatewayDeployConfigured()
747
+ ? GATEWAY_UNAVAILABLE_VISITOR_MESSAGE
748
+ : ownerFacing;
749
+ }
750
+ /**
751
+ * Gateway-lane credentials. Fall-through: the request's own Builder connection,
752
+ * then the deployment's credits pair, then the legacy pair.
753
+ *
754
+ * Identity-bearing calls (asset upload, design systems, Admin GraphQL, browser
755
+ * agent) must keep using `resolveBuilderCredentials`: a `['gateway']` token 403s
756
+ * on all of them.
757
+ */
758
+ export async function resolveBuilderGatewayCredentialsDetailed(identity) {
759
+ const scoped = await resolveBuilderCredentialsDetailed(identity);
760
+ if (scoped.source && scoped.source !== "env") {
761
+ return { ...scoped, lane: "identity" };
762
+ }
763
+ // A COMPLETE legacy pair in env is owner-configured, not deploy-injected, so it
764
+ // outranks the gateway here — `selectDetectedEngine` gives it priority for the
765
+ // same reason, and letting the gateway win would pick `builder` on the
766
+ // customer's own pair and then bill the call to the project's gateway space.
767
+ // An incomplete pair is not a usable credential and still falls through.
768
+ if (scoped.privateKey && scoped.publicKey) {
769
+ return { ...scoped, lane: "identity" };
770
+ }
771
+ const gateway = await resolveUsableBuilderGatewayDeployCredentials();
772
+ if (gateway) {
773
+ // Same discipline as `resolveBuilderCredential`: a deploy fallback must not
774
+ // turn an unreadable credential store into a clean connected result.
775
+ assertCredentialStoreReadable(scoped);
776
+ return {
777
+ privateKey: gateway.token,
778
+ publicKey: gateway.spaceId,
779
+ userId: null,
780
+ orgName: null,
781
+ orgKind: null,
782
+ subscription: null,
783
+ subscriptionLevel: null,
784
+ subscriptionName: null,
785
+ isEnterprise: null,
786
+ isFreeAccount: null,
787
+ source: "env",
788
+ lookupFailed: scoped.lookupFailed,
789
+ cause: scoped.cause,
790
+ lane: "gateway-deploy",
791
+ };
792
+ }
793
+ return { ...scoped, lane: scoped.source ? "identity" : null };
794
+ }
795
+ /**
796
+ * Gateway-lane credentials in the same shape as `resolveBuilderCredentials`, so
797
+ * a consumer moves lane by changing which resolver it calls and nothing else.
798
+ */
799
+ export async function resolveBuilderGatewayCredentials(identity) {
800
+ const { privateKey, publicKey, userId, orgName, orgKind, subscription, subscriptionLevel, subscriptionName, isEnterprise, isFreeAccount, } = await resolveBuilderGatewayCredentialsDetailed(identity);
801
+ return {
802
+ privateKey,
803
+ publicKey,
804
+ userId,
805
+ orgName,
806
+ orgKind,
807
+ subscription,
808
+ subscriptionLevel,
809
+ subscriptionName,
810
+ isEnterprise,
811
+ isFreeAccount,
812
+ };
813
+ }
814
+ /**
815
+ * The gate for gateway-lane features. Not `resolveHasBuilderPrivateKey`, which is
816
+ * identity-only and false on a credits site.
817
+ */
818
+ export async function resolveHasBuilderGatewayCredential() {
819
+ return Boolean(await resolveBuilderGatewayAuth());
820
+ }
821
+ /** Gateway-lane `resolveBuilderAuthHeader`, same fall-through order. */
822
+ export async function resolveBuilderGatewayAuth() {
823
+ const creds = await resolveBuilderGatewayCredentialsDetailed();
824
+ const token = creds.privateKey?.trim();
825
+ const spaceId = creds.publicKey?.trim();
826
+ if (token && spaceId) {
827
+ return {
828
+ authorization: `Bearer ${token}`,
829
+ spaceId,
830
+ userId: creds.userId?.trim() || null,
831
+ };
832
+ }
833
+ // Single-key deployments predate the space id and still authenticate on a
834
+ // `bpk-` private key alone. A gateway token never reaches this branch — its
835
+ // pair is required above.
836
+ const legacyKey = (await resolveBuilderPrivateKey())?.trim();
837
+ return legacyKey
838
+ ? { authorization: `Bearer ${legacyKey}`, spaceId: null, userId: null }
839
+ : null;
840
+ }
705
841
  const BUILDER_AUTH_FAILURE_SETTING_PREFIX = "builder-auth-failure:";
706
842
  /**
707
843
  * Stale markers expire so a rejected model or a transient gateway failure
@@ -882,6 +1018,37 @@ export async function clearProviderCredentialAuthFailure(opts) {
882
1018
  // A stale failure marker should not block writing or using fresh keys.
883
1019
  }
884
1020
  }
1021
+ /**
1022
+ * `builderCredentialFingerprint` needs both legacy keys, so on a credits site the
1023
+ * legacy marker is a no-op and a rejected token would be resent forever. The
1024
+ * gateway lane fingerprints its single token through the provider marker.
1025
+ */
1026
+ export async function recordBuilderGatewayAuthFailure(details) {
1027
+ try {
1028
+ const creds = await resolveBuilderGatewayCredentialsDetailed();
1029
+ if (creds.lane === "gateway-deploy" && creds.privateKey) {
1030
+ await recordProviderCredentialAuthFailure({
1031
+ key: BUILDER_GATEWAY_TOKEN_ENV_VAR,
1032
+ value: creds.privateKey,
1033
+ ...details,
1034
+ });
1035
+ return;
1036
+ }
1037
+ }
1038
+ catch {
1039
+ // Best-effort marker only; the chat error is still returned to the caller.
1040
+ return;
1041
+ }
1042
+ await recordBuilderCredentialAuthFailure(details);
1043
+ }
1044
+ /** Each clear is a no-op off its own lane, so callers need not know which they are on. */
1045
+ export async function clearBuilderGatewayAuthFailure(creds) {
1046
+ await clearBuilderCredentialAuthFailure(creds);
1047
+ await clearProviderCredentialAuthFailure({
1048
+ key: BUILDER_GATEWAY_TOKEN_ENV_VAR,
1049
+ value: creds.privateKey,
1050
+ });
1051
+ }
885
1052
  /**
886
1053
  * Write Builder credentials to `app_secrets`.
887
1054
  *
@@ -8,6 +8,6 @@ export declare function resolveGoogleRealtimeCredentials(opts: {
8
8
  orgId?: string | null;
9
9
  }): Promise<string | null>;
10
10
  export declare function createGoogleRealtimeSessionHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<GoogleRealtimeSessionResponse | {
11
- error: any;
11
+ error: string;
12
12
  }>>;
13
13
  export {};