@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { serializeBoundedRemoteJson } from "../integrations/remote-json-safety.js";
|
|
2
|
+
import { appendCodeAgentTranscriptEvent } from "./code-agent-runs.js";
|
|
3
|
+
export const PORTAL_TRANSFER_SCHEMA_VERSION = 1;
|
|
4
|
+
export const PORTAL_TRANSFER_MAX_CONTEXT_BYTES = 900_000;
|
|
5
|
+
const BINARY_FIELD_NAMES = new Set([
|
|
6
|
+
"base64",
|
|
7
|
+
"dataurl",
|
|
8
|
+
"image",
|
|
9
|
+
"imagebase64",
|
|
10
|
+
"imagedata",
|
|
11
|
+
"imagebytes",
|
|
12
|
+
"screenshot",
|
|
13
|
+
"screenshotbase64",
|
|
14
|
+
"screenshotdata",
|
|
15
|
+
"bytes",
|
|
16
|
+
"buffer",
|
|
17
|
+
]);
|
|
18
|
+
export function createPortalTransferContext(input) {
|
|
19
|
+
const sourceRunId = requireString(input.sourceRunId, "source run id");
|
|
20
|
+
const context = {
|
|
21
|
+
schemaVersion: PORTAL_TRANSFER_SCHEMA_VERSION,
|
|
22
|
+
sourceRunId,
|
|
23
|
+
...(input.sourceStatus
|
|
24
|
+
? { sourceStatus: requireString(input.sourceStatus, "source status") }
|
|
25
|
+
: {}),
|
|
26
|
+
...(input.sourcePhase
|
|
27
|
+
? { sourcePhase: requireString(input.sourcePhase, "source phase") }
|
|
28
|
+
: {}),
|
|
29
|
+
events: input.events.map((event, index) => normalizeSourceEvent(event, index)),
|
|
30
|
+
};
|
|
31
|
+
serializeBoundedRemoteJson(context, {
|
|
32
|
+
label: "Portal transcript context",
|
|
33
|
+
maxBytes: PORTAL_TRANSFER_MAX_CONTEXT_BYTES,
|
|
34
|
+
});
|
|
35
|
+
return context;
|
|
36
|
+
}
|
|
37
|
+
export function parsePortalTransferContext(value) {
|
|
38
|
+
if (value === undefined || value === null)
|
|
39
|
+
return null;
|
|
40
|
+
if (!isRecord(value) || value.schemaVersion !== 1) {
|
|
41
|
+
throw new Error("Portal transfer context has an unsupported version.");
|
|
42
|
+
}
|
|
43
|
+
const sourceRunId = requireString(value.sourceRunId, "source run id");
|
|
44
|
+
if (!Array.isArray(value.events)) {
|
|
45
|
+
throw new Error("Portal transfer context is missing transcript events.");
|
|
46
|
+
}
|
|
47
|
+
const events = value.events.map((event, index) => {
|
|
48
|
+
if (!isRecord(event)) {
|
|
49
|
+
throw new Error(`Portal transcript event ${index + 1} is invalid.`);
|
|
50
|
+
}
|
|
51
|
+
return normalizeSourceEvent(event, index);
|
|
52
|
+
});
|
|
53
|
+
const sourceStatus = optionalString(value.sourceStatus);
|
|
54
|
+
const sourcePhase = optionalString(value.sourcePhase);
|
|
55
|
+
const context = {
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
sourceRunId,
|
|
58
|
+
...(sourceStatus ? { sourceStatus } : {}),
|
|
59
|
+
...(sourcePhase ? { sourcePhase } : {}),
|
|
60
|
+
events,
|
|
61
|
+
};
|
|
62
|
+
serializeBoundedRemoteJson(context, {
|
|
63
|
+
label: "Portal transcript context",
|
|
64
|
+
maxBytes: PORTAL_TRANSFER_MAX_CONTEXT_BYTES,
|
|
65
|
+
});
|
|
66
|
+
return context;
|
|
67
|
+
}
|
|
68
|
+
export function appendPortalTransferTranscript(context, runId) {
|
|
69
|
+
const targetRunId = requireString(runId, "target run id");
|
|
70
|
+
let appended = 0;
|
|
71
|
+
for (const event of context.events) {
|
|
72
|
+
appendCodeAgentTranscriptEvent({
|
|
73
|
+
id: event.id,
|
|
74
|
+
runId: targetRunId,
|
|
75
|
+
kind: event.kind,
|
|
76
|
+
message: event.message,
|
|
77
|
+
createdAt: event.createdAt,
|
|
78
|
+
...(event.metadata
|
|
79
|
+
? {
|
|
80
|
+
metadata: {
|
|
81
|
+
...event.metadata,
|
|
82
|
+
portalTransfer: {
|
|
83
|
+
sourceRunId: context.sourceRunId,
|
|
84
|
+
sourceEventId: event.id,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
: {
|
|
89
|
+
metadata: {
|
|
90
|
+
portalTransfer: {
|
|
91
|
+
sourceRunId: context.sourceRunId,
|
|
92
|
+
sourceEventId: event.id,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
...(event.signal ? { signal: event.signal } : {}),
|
|
97
|
+
});
|
|
98
|
+
appended++;
|
|
99
|
+
}
|
|
100
|
+
return appended;
|
|
101
|
+
}
|
|
102
|
+
export function portalTransferContinuationPrompt(input) {
|
|
103
|
+
return [
|
|
104
|
+
"[Portal session continuation]",
|
|
105
|
+
`This coding session was moved to ${requireString(input.hostLabel, "Portal host label")}.`,
|
|
106
|
+
`Portal handoff: ${requireString(input.handoffId, "Portal handoff id")}`,
|
|
107
|
+
`The preceding ${input.eventCount} transcript event${input.eventCount === 1 ? "" : "s"} came from the original computer and is part of this session context.`,
|
|
108
|
+
"Review the transferred transcript and Portal workspace before acting. Continue the unfinished task from the latest state, and do not repeat work that is already complete.",
|
|
109
|
+
].join("\n");
|
|
110
|
+
}
|
|
111
|
+
function normalizeSourceEvent(value, index) {
|
|
112
|
+
const id = requireString(value.id, `transcript event ${index + 1} id`);
|
|
113
|
+
const kind = normalizeKind(value.kind ?? value.type, index);
|
|
114
|
+
const messageValue = value.message ?? value.text;
|
|
115
|
+
if (typeof messageValue !== "string") {
|
|
116
|
+
throw new Error(`Portal transcript event ${index + 1} is missing text.`);
|
|
117
|
+
}
|
|
118
|
+
const createdAt = requireString(value.createdAt, `transcript event ${index + 1} timestamp`);
|
|
119
|
+
const metadata = sanitizeMetadata(value.metadata);
|
|
120
|
+
const signal = value.signal === "credential-gap" ? "credential-gap" : undefined;
|
|
121
|
+
return {
|
|
122
|
+
schemaVersion: 1,
|
|
123
|
+
id,
|
|
124
|
+
kind,
|
|
125
|
+
message: messageValue,
|
|
126
|
+
createdAt,
|
|
127
|
+
...(metadata ? { metadata } : {}),
|
|
128
|
+
...(signal ? { signal } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function normalizeKind(value, index) {
|
|
132
|
+
const kind = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
133
|
+
if (kind === "user" ||
|
|
134
|
+
kind === "system" ||
|
|
135
|
+
kind === "note" ||
|
|
136
|
+
kind === "artifact" ||
|
|
137
|
+
kind === "status") {
|
|
138
|
+
return kind;
|
|
139
|
+
}
|
|
140
|
+
if (kind === "assistant" || kind === "human" || kind === "prompt") {
|
|
141
|
+
return kind === "assistant" ? "system" : "user";
|
|
142
|
+
}
|
|
143
|
+
throw new Error(`Portal transcript event ${index + 1} has an invalid kind.`);
|
|
144
|
+
}
|
|
145
|
+
function sanitizeMetadata(value) {
|
|
146
|
+
if (!isRecord(value))
|
|
147
|
+
return undefined;
|
|
148
|
+
const sanitized = sanitizeObject(value);
|
|
149
|
+
return Object.keys(sanitized).length > 0 ? sanitized : undefined;
|
|
150
|
+
}
|
|
151
|
+
function sanitizeObject(value) {
|
|
152
|
+
const result = {};
|
|
153
|
+
let binaryOmitted = false;
|
|
154
|
+
for (const [key, child] of Object.entries(value)) {
|
|
155
|
+
if (isBinaryFieldName(key)) {
|
|
156
|
+
binaryOmitted = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const sanitized = sanitizeValue(child);
|
|
160
|
+
if (sanitized !== undefined)
|
|
161
|
+
result[key] = sanitized;
|
|
162
|
+
}
|
|
163
|
+
if (binaryOmitted)
|
|
164
|
+
result.binaryContentOmitted = true;
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
function sanitizeValue(value) {
|
|
168
|
+
if (value === null ||
|
|
169
|
+
typeof value === "string" ||
|
|
170
|
+
typeof value === "number" ||
|
|
171
|
+
typeof value === "boolean") {
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(value)) {
|
|
175
|
+
return value
|
|
176
|
+
.map((item) => sanitizeValue(item))
|
|
177
|
+
.filter((item) => item !== undefined);
|
|
178
|
+
}
|
|
179
|
+
if (isRecord(value))
|
|
180
|
+
return sanitizeObject(value);
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
function isBinaryFieldName(key) {
|
|
184
|
+
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
185
|
+
return BINARY_FIELD_NAMES.has(normalized);
|
|
186
|
+
}
|
|
187
|
+
function isRecord(value) {
|
|
188
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
189
|
+
}
|
|
190
|
+
function requireString(value, label) {
|
|
191
|
+
const result = optionalString(value);
|
|
192
|
+
if (!result)
|
|
193
|
+
throw new Error(`Portal ${label} is missing.`);
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
function optionalString(value) {
|
|
197
|
+
if (typeof value !== "string")
|
|
198
|
+
return undefined;
|
|
199
|
+
const result = value.trim();
|
|
200
|
+
return result || undefined;
|
|
201
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unwrapAttachmentEnvelope } from "@agent-native/toolkit/composer/pasted-text";
|
|
2
2
|
import { actionPreparationContinuationNote } from "../agent/action-continuation-guidance.js";
|
|
3
|
-
import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
|
|
3
|
+
import { formatLlmCredentialErrorMessage, isLlmCredentialError, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
|
|
4
4
|
import { ANALYTICS_CLIENT_PLATFORM_HEADER } from "../shared/analytics-platform.js";
|
|
5
5
|
import { clearPendingTurnIfMatches, getActiveRun, setActiveRun, updateActiveRunSeq, clearActiveRun, clearActiveRunIfMatches, setPendingTurn, } from "./active-run-state.js";
|
|
6
6
|
import { getAnalyticsClientPlatform } from "./analytics-platform.js";
|
|
@@ -199,6 +199,25 @@ const BACKGROUND_TERMINAL_REASON_MESSAGES = {
|
|
|
199
199
|
missing_credentials: LLM_MISSING_CREDENTIALS_MESSAGE,
|
|
200
200
|
turn_continuation_budget_exhausted: "This request needed more automatic continuations than allowed and was stopped. Try breaking it into smaller steps.",
|
|
201
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Re-decide the mapped copy for whoever is reading it.
|
|
204
|
+
*
|
|
205
|
+
* The map above is client-authored copy for a server-produced code, and for the
|
|
206
|
+
* credential reasons that duplicates a decision the server already makes with
|
|
207
|
+
* `formatLlmCredentialErrorMessage({ visitorFacing })` — without the one input
|
|
208
|
+
* the decision needs. Earlier notes called this an unfixable residual gap on the
|
|
209
|
+
* grounds that the client cannot know the lane; the framing was wrong, because
|
|
210
|
+
* the lane is a fact about the deployment that the run snapshot can carry
|
|
211
|
+
* (`deploymentPaysForAi` on `/runs/active`). Everything else in the map is
|
|
212
|
+
* background-handoff copy that reads the same to either party.
|
|
213
|
+
*/
|
|
214
|
+
function laneAwareTerminalReasonMessage(mapped, terminalReason, deploymentPaysForAi) {
|
|
215
|
+
if (!mapped || !isLlmCredentialError(mapped, terminalReason))
|
|
216
|
+
return mapped;
|
|
217
|
+
return formatLlmCredentialErrorMessage({
|
|
218
|
+
visitorFacing: deploymentPaysForAi,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
202
221
|
/**
|
|
203
222
|
* `agent_runs.terminal_reason` values that mark a CHUNK boundary, not the end
|
|
204
223
|
* of the turn — `markBackgroundContinuationChunkTerminal` (production-agent.ts)
|
|
@@ -2280,38 +2299,50 @@ export function createAgentChatAdapter(options) {
|
|
|
2280
2299
|
const hasErrorTerminalReason = rawTerminalReason.startsWith("error:");
|
|
2281
2300
|
const terminalReason = rawTerminalReason.replace(/^error:/, "");
|
|
2282
2301
|
const mappedMessage = terminalReason
|
|
2283
|
-
? BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason]
|
|
2302
|
+
? laneAwareTerminalReasonMessage(BACKGROUND_TERMINAL_REASON_MESSAGES[terminalReason], terminalReason, lastKnown?.deploymentPaysForAi === true)
|
|
2284
2303
|
: undefined;
|
|
2285
2304
|
const mappedErrorCode = terminalReason === "missing_api_key" ||
|
|
2286
2305
|
terminalReason === LLM_MISSING_CREDENTIALS_ERROR_CODE
|
|
2287
2306
|
? LLM_MISSING_CREDENTIALS_ERROR_CODE
|
|
2288
2307
|
: terminalReason;
|
|
2289
|
-
if (mappedMessage) {
|
|
2290
|
-
yield* emitBackgroundTerminalError({
|
|
2291
|
-
message: mappedMessage,
|
|
2292
|
-
errorCode: mappedErrorCode,
|
|
2293
|
-
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2294
|
-
});
|
|
2295
|
-
return;
|
|
2296
|
-
}
|
|
2297
2308
|
if (hasErrorTerminalReason) {
|
|
2298
|
-
|
|
2309
|
+
// Never REPLACE the wording the server chose for this failure: a
|
|
2310
|
+
// deployment that pays for its own AI answers whoever is chatting
|
|
2311
|
+
// with one visitor line, and the map only knows the owner copy.
|
|
2312
|
+
// Only the captured error whose code IS the terminal reason is that
|
|
2313
|
+
// wording — an earlier auto-recoverable error in the same run is
|
|
2314
|
+
// stale (the cursor reset only clears it when the followed run
|
|
2315
|
+
// changes), and letting it outrank the terminal reason reports a
|
|
2316
|
+
// transient blip for a run that died of something else.
|
|
2317
|
+
const serverChosenError = lastRecoverableRunError?.errorCode === terminalReason
|
|
2318
|
+
? lastRecoverableRunError
|
|
2319
|
+
: null;
|
|
2320
|
+
if (serverChosenError) {
|
|
2299
2321
|
yield* emitBackgroundTerminalError({
|
|
2300
|
-
message:
|
|
2301
|
-
errorCode:
|
|
2322
|
+
message: serverChosenError.message,
|
|
2323
|
+
errorCode: serverChosenError.errorCode ||
|
|
2302
2324
|
mappedErrorCode ||
|
|
2303
2325
|
"background_run_failed",
|
|
2304
|
-
details:
|
|
2326
|
+
details: serverChosenError.details,
|
|
2305
2327
|
});
|
|
2306
2328
|
return;
|
|
2307
2329
|
}
|
|
2308
2330
|
yield* emitBackgroundTerminalError({
|
|
2309
|
-
message:
|
|
2331
|
+
message: mappedMessage ??
|
|
2332
|
+
"The agent's background run failed before its final response could be recovered. You can retry from the preserved chat context.",
|
|
2310
2333
|
errorCode: mappedErrorCode || "background_run_failed",
|
|
2311
2334
|
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2312
2335
|
});
|
|
2313
2336
|
return;
|
|
2314
2337
|
}
|
|
2338
|
+
if (mappedMessage) {
|
|
2339
|
+
yield* emitBackgroundTerminalError({
|
|
2340
|
+
message: mappedMessage,
|
|
2341
|
+
errorCode: mappedErrorCode,
|
|
2342
|
+
details: `terminal_reason: ${rawTerminalReason}`,
|
|
2343
|
+
});
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2315
2346
|
if (status === "completed") {
|
|
2316
2347
|
// The turn finished server-side; the events we managed to fold are
|
|
2317
2348
|
// the durable transcript. Never turn a terminal tool-only chunk
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GATEWAY_UNAVAILABLE_VISITOR_MESSAGE } from "../agent/engine/credential-errors.js";
|
|
1
2
|
/**
|
|
2
3
|
* Append a Builder CTA markdown link to gateway errors that users can fix
|
|
3
4
|
* outside the app. Used by both
|
|
@@ -33,8 +34,9 @@ function isSafeUpgradeUrl(url) {
|
|
|
33
34
|
}
|
|
34
35
|
export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
|
|
35
36
|
const normalized = normalizeChatError(errorMessage, errorCode);
|
|
36
|
-
if (
|
|
37
|
-
|
|
37
|
+
if (!isServerChosenVisitorMessage(normalized.message) &&
|
|
38
|
+
(errorCode === "gateway_not_enabled" ||
|
|
39
|
+
/space has not enabled the LLM gateway/i.test(normalized.message))) {
|
|
38
40
|
return `Error: ${normalized.message}\n\n[${OPEN_BUILDER_SPACE_SETTINGS_LABEL}](${BUILDER_SPACE_SETTINGS_URL})`;
|
|
39
41
|
}
|
|
40
42
|
if (errorCode === "builder_gateway_error") {
|
|
@@ -49,6 +51,22 @@ export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
|
|
|
49
51
|
}
|
|
50
52
|
return `Error: ${normalized.message}\n\n[${UPGRADE_AT_BUILDER_LABEL}](${upgradeUrl})`;
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* True when the server already decided what this reader may be told.
|
|
56
|
+
*
|
|
57
|
+
* A Builder-credits deployment answers every gateway rejection with one visitor
|
|
58
|
+
* line and keeps the real reason on `errorCode` for its owner. Every mapping
|
|
59
|
+
* below is keyed on that code, so re-deriving copy from it hands the visitor
|
|
60
|
+
* back the owner instruction the server just removed — "reconnect Builder in
|
|
61
|
+
* Settings" to someone with no account. This is an identity check against the
|
|
62
|
+
* exported constant, not a keyword match: the rewrite is the whole message.
|
|
63
|
+
*
|
|
64
|
+
* Deliberately not a `KNOWN_CHAT_ERROR_KEYS` entry: that map localizes copy,
|
|
65
|
+
* while this returns before any mapping runs at all.
|
|
66
|
+
*/
|
|
67
|
+
function isServerChosenVisitorMessage(text) {
|
|
68
|
+
return text === GATEWAY_UNAVAILABLE_VISITOR_MESSAGE;
|
|
69
|
+
}
|
|
52
70
|
const KNOWN_CHAT_ERROR_KEYS = new Map([
|
|
53
71
|
[
|
|
54
72
|
"No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io or add a provider key.",
|
|
@@ -200,6 +218,11 @@ export function normalizeChatError(errorMessage, errorCode) {
|
|
|
200
218
|
const looksHtml = /<html[\s>]|<body[\s>]|<head[\s>]/i.test(raw);
|
|
201
219
|
const text = looksHtml ? htmlToText(raw) : raw.trim();
|
|
202
220
|
const providerPayload = looksHtml ? null : parseProviderErrorPayload(text);
|
|
221
|
+
// Ahead of every mapping below, including the provider-payload fallback: the
|
|
222
|
+
// server already chose this reader's message, and any re-derivation from a
|
|
223
|
+
// code hands a visitor the owner instruction it deliberately removed.
|
|
224
|
+
if (isServerChosenVisitorMessage(text))
|
|
225
|
+
return { message: text };
|
|
203
226
|
const code = normalizeErrorCode(errorCode ?? providerPayload?.errorCode);
|
|
204
227
|
const providerMessage = providerPayload?.errorCode === "overloaded_error"
|
|
205
228
|
? "The model provider is overloaded right now. Wait a moment, then retry."
|
|
@@ -91,6 +91,8 @@ export interface SSEEvent {
|
|
|
91
91
|
upgradeUrl?: string;
|
|
92
92
|
details?: string;
|
|
93
93
|
recoverable?: boolean;
|
|
94
|
+
/** The engine said another attempt may succeed — see `AgentChatEvent`. */
|
|
95
|
+
providerRetryable?: boolean;
|
|
94
96
|
maxIterations?: number;
|
|
95
97
|
}
|
|
96
98
|
export type AgentAutoContinueReason = "run_timeout" | "loop_limit" | "no_progress" | "stream_ended" | "stale_run";
|
|
@@ -534,7 +534,12 @@ function isAutoRecoverableError(ev, errMsg) {
|
|
|
534
534
|
code === "http_504" ||
|
|
535
535
|
code === "rate_limited" ||
|
|
536
536
|
code === "too_many_concurrent_requests" ||
|
|
537
|
-
code === "overloaded_error"
|
|
537
|
+
code === "overloaded_error" ||
|
|
538
|
+
// A gateway stream that ended without a stop event. The partial turn is
|
|
539
|
+
// real, so this continues rather than retrying: the code carries what the
|
|
540
|
+
// message used to (`msg.includes("stream ended")` below), which a
|
|
541
|
+
// Builder-credits deployment replaces with its one visitor line.
|
|
542
|
+
code === "builder_gateway_stream_ended") {
|
|
538
543
|
return true;
|
|
539
544
|
}
|
|
540
545
|
if (ev.recoverable === true)
|
|
@@ -548,6 +553,12 @@ function isAutoRecoverableError(ev, errMsg) {
|
|
|
548
553
|
return false;
|
|
549
554
|
if (msg.includes("daily gateway request cap"))
|
|
550
555
|
return false;
|
|
556
|
+
// The engine's structural verdict, checked after every terminal code above so
|
|
557
|
+
// it can never revive a quota or auth rejection. It is the only retry signal
|
|
558
|
+
// left once the message is one visitor line: an upstream "Overloaded" carries
|
|
559
|
+
// no code, and its text is what the rewrite removed.
|
|
560
|
+
if (ev.providerRetryable === true)
|
|
561
|
+
return true;
|
|
551
562
|
// "gateway error" intentionally absent — that's the no-detail Builder
|
|
552
563
|
// gateway fallback and the production-agent already retries it
|
|
553
564
|
// synchronously up to MAX_RETRIES before the error escapes here. Treating
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
-
error?: undefined;
|
|
66
65
|
states: {
|
|
67
66
|
clientId: number;
|
|
68
67
|
state: string;
|
|
69
68
|
}[];
|
|
69
|
+
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
-
error?: undefined;
|
|
81
80
|
users: {
|
|
82
81
|
clientId: number;
|
|
83
82
|
lastSeen: number;
|
|
84
83
|
}[];
|
|
84
|
+
error?: undefined;
|
|
85
85
|
}>>;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
|
|
2
|
-
import { createBuilderEngine } from "../agent/engine/builder-engine.js";
|
|
3
2
|
import { getStoredModelForEngine, normalizeModelForEngine, resolveEngine, } from "../agent/engine/index.js";
|
|
4
3
|
import { actionsToEngineTools, filterInitialEngineTools, getOwnerActiveApiKey, } from "../agent/production-agent.js";
|
|
5
4
|
import { runAgentLoopDirectWithSoftTimeout } from "../agent/run-loop-with-resume.js";
|
|
@@ -10,7 +9,6 @@ import { resolveAutomationExecutionIdentity, } from "../automations/service.js";
|
|
|
10
9
|
import { createThread } from "../chat-threads/store.js";
|
|
11
10
|
import { queryOrgMembers } from "../org/context.js";
|
|
12
11
|
import { organizationIdFromResourceOwner, organizationResourceOwner, } from "../resources/store.js";
|
|
13
|
-
import { resolveBuilderCredentialsDetailed } from "../server/credential-provider.js";
|
|
14
12
|
import { runWithRequestContext, } from "../server/request-context.js";
|
|
15
13
|
import { attachAutomationRunThread, finishAutomationRun, startAutomationRun, } from "./run-history.js";
|
|
16
14
|
const BACKGROUND_RUN_STUCK_MS = 10 * 60_000;
|
|
@@ -238,23 +236,19 @@ async function executeBackgroundAutomation(options, deps, historyId) {
|
|
|
238
236
|
const availableTools = actionsToEngineTools(actions);
|
|
239
237
|
const tools = filterInitialEngineTools(availableTools, initialToolNames);
|
|
240
238
|
const userApiKey = await getOwnerActiveApiKey(ownerEmail);
|
|
241
|
-
|
|
239
|
+
// The run manager invokes its detached callback after the scheduler's
|
|
240
|
+
// setup stack has yielded, so the engine's credentials must be captured
|
|
241
|
+
// now, while the owner/org identity is explicit. Passing
|
|
242
|
+
// `credentialIdentity` is what makes resolveEngine capture them, on the
|
|
243
|
+
// same gateway lane the interactive path uses — a Builder-credits site has
|
|
244
|
+
// no per-user connection to find, so resolving that lane by hand here once
|
|
245
|
+
// left every scheduled automation dead while chat still worked.
|
|
246
|
+
const engine = deps.engine ??
|
|
242
247
|
(await resolveEngine({
|
|
243
248
|
apiKey: userApiKey ?? deps.apiKey,
|
|
244
249
|
appId: deps.appId,
|
|
250
|
+
credentialIdentity: { userEmail: ownerEmail, orgId },
|
|
245
251
|
}));
|
|
246
|
-
// The run manager invokes its detached callback after the scheduler's
|
|
247
|
-
// setup stack has yielded. Capture the verified Builder pair while the
|
|
248
|
-
// owner/org identity is explicit, then keep it on the engine so a
|
|
249
|
-
// concurrent serverless run cannot make this call look unauthenticated.
|
|
250
|
-
const engine = !deps.engine && resolvedEngine.name === "builder"
|
|
251
|
-
? createBuilderEngine({
|
|
252
|
-
credentials: await resolveBuilderCredentialsDetailed({
|
|
253
|
-
userEmail: ownerEmail,
|
|
254
|
-
orgId,
|
|
255
|
-
}),
|
|
256
|
-
})
|
|
257
|
-
: resolvedEngine;
|
|
258
252
|
const modelCandidate = automation.meta.model ??
|
|
259
253
|
deps.model ??
|
|
260
254
|
(await getStoredModelForEngine(engine, { appId: deps.appId })) ??
|
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
* DELETE /_agent-native/notifications/:id — delete
|
|
12
12
|
*/
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
|
-
error?: undefined;
|
|
15
14
|
count: number;
|
|
16
15
|
updated?: undefined;
|
|
16
|
+
error?: undefined;
|
|
17
17
|
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
|
-
|
|
19
|
+
count?: undefined;
|
|
20
20
|
updated: number;
|
|
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
|
-
|
|
29
|
+
count?: undefined;
|
|
30
30
|
updated?: undefined;
|
|
31
|
+
error?: undefined;
|
|
31
32
|
ok: boolean;
|
|
32
|
-
count?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
+
error?: undefined;
|
|
45
|
+
ok?: undefined;
|
|
44
46
|
summary: import("./types.js").TraceSummary;
|
|
45
47
|
spans: import("./types.js").TraceSpan[];
|
|
46
48
|
id?: undefined;
|
|
49
|
+
} | {
|
|
47
50
|
error?: undefined;
|
|
48
51
|
ok?: undefined;
|
|
49
|
-
} | {
|
|
50
52
|
summary?: undefined;
|
|
51
53
|
spans?: undefined;
|
|
52
54
|
id: string;
|
|
53
|
-
error?: undefined;
|
|
54
|
-
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
|
+
ok?: undefined;
|
|
56
57
|
summary?: undefined;
|
|
57
58
|
spans?: undefined;
|
|
58
59
|
id?: undefined;
|
|
59
60
|
error: any;
|
|
60
|
-
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
+
error?: undefined;
|
|
62
63
|
summary?: undefined;
|
|
63
64
|
spans?: undefined;
|
|
64
65
|
id?: undefined;
|
|
65
66
|
ok: boolean;
|
|
66
|
-
error?: undefined;
|
|
67
67
|
}>>;
|
|
@@ -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",
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
status?: undefined;
|
|
38
37
|
ok?: undefined;
|
|
38
|
+
status?: undefined;
|
|
39
39
|
} | {
|
|
40
|
+
error?: undefined;
|
|
40
41
|
ok: boolean;
|
|
41
42
|
status: string;
|
|
42
|
-
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
+
ok?: undefined;
|
|
44
45
|
error: string;
|
|
45
46
|
removed?: undefined;
|
|
46
|
-
ok?: 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
|
|
54
54
|
* or the current stored value without changing anything.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
+
ok?: undefined;
|
|
57
58
|
error: string;
|
|
58
59
|
note?: undefined;
|
|
59
|
-
ok?: 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
|
}>>;
|