@agent-native/core 0.161.1 → 0.161.4
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/templates/analytics/actions/compose-dashboard.ts +5 -2
- package/corpus/templates/analytics/actions/export-dashboard-panel-to-google-sheet.ts +11 -5
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +89 -2
- package/corpus/templates/analytics/actions/update-dashboard.ts +14 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +89 -41
- package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +8 -3
- package/corpus/templates/analytics/server/lib/error-capture.ts +6 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +187 -44
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +44 -16
- package/corpus/templates/clips/actions/save-browser-transcript.ts +20 -4
- package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +167 -35
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +8 -1
- package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +39 -2
- package/corpus/templates/slides/actions/list-decks.ts +36 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +17 -6
- package/dist/agent/engine/builder-engine.js +37 -19
- package/dist/agent/engine/types.d.ts +29 -0
- package/dist/agent/engine/types.js +6 -0
- package/dist/agent/production-agent.js +2 -0
- package/dist/agent/run-manager.d.ts +6 -0
- package/dist/agent/run-manager.js +27 -0
- package/dist/agent/run-store.d.ts +5 -5
- package/dist/agent/run-store.js +52 -15
- package/dist/client/AssistantChat.d.ts +1 -0
- package/dist/client/AssistantChat.js +10 -1
- package/dist/db/client.js +10 -2
- package/dist/db/create-get-db.js +42 -0
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/onboarding-html.js +22 -77
- package/dist/server/poll.d.ts +5 -5
- package/dist/server/poll.js +19 -26
- package/dist/server/release-migrations.js +4 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/auth-copy.d.ts +7 -0
- package/dist/shared/auth-copy.js +77 -0
- package/dist/shared/mcp-embed-headers.js +8 -4
- package/dist/vite/client.js +94 -3
- package/dist/workspace-connections/migrations.d.ts +18 -0
- package/dist/workspace-connections/migrations.js +153 -0
- package/package.json +1 -1
|
@@ -26,6 +26,8 @@ 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
|
+
/** Upstream request id, when the provider/gateway supplied one. */
|
|
30
|
+
readonly requestId?: string;
|
|
29
31
|
/**
|
|
30
32
|
* Whether the request exceeded the model's context window. Set by engines that
|
|
31
33
|
* classified the provider's own reply, because the delivered message may not
|
|
@@ -34,12 +36,16 @@ export declare class EngineError extends Error {
|
|
|
34
36
|
* one-shot trim-and-retry recovery.
|
|
35
37
|
*/
|
|
36
38
|
readonly contextOverflow?: boolean;
|
|
39
|
+
/** Sizes and counts of the failed request; see {@link EngineRequestShape}. */
|
|
40
|
+
readonly requestShape?: EngineRequestShape;
|
|
37
41
|
constructor(message: string, opts?: {
|
|
38
42
|
errorCode?: string;
|
|
39
43
|
upgradeUrl?: string;
|
|
40
44
|
statusCode?: number;
|
|
41
45
|
providerRetryable?: boolean;
|
|
46
|
+
requestId?: string;
|
|
42
47
|
contextOverflow?: boolean;
|
|
48
|
+
requestShape?: EngineRequestShape;
|
|
43
49
|
});
|
|
44
50
|
}
|
|
45
51
|
/**
|
|
@@ -196,6 +202,13 @@ export type EngineEvent = {
|
|
|
196
202
|
* should retry even if status code / message patterns don't match.
|
|
197
203
|
*/
|
|
198
204
|
providerRetryable?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Upstream request id, when the provider/gateway supplies one. This is
|
|
207
|
+
* the only key that ties a user-facing error back to the upstream log,
|
|
208
|
+
* so it must survive to the capture even when the error also carries a
|
|
209
|
+
* message — an opaque message is not a diagnostic.
|
|
210
|
+
*/
|
|
211
|
+
requestId?: string;
|
|
199
212
|
/**
|
|
200
213
|
* The request exceeded the model's context window. Carried structurally
|
|
201
214
|
* for the same reason as `providerRetryable`: `error` is visitor copy on a
|
|
@@ -203,7 +216,23 @@ export type EngineEvent = {
|
|
|
203
216
|
* the time the agent decides whether to trim and retry.
|
|
204
217
|
*/
|
|
205
218
|
contextOverflow?: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Sizes and counts of the request that failed. Never prompt or user
|
|
221
|
+
* content — the point is to make "what did we send" answerable from a
|
|
222
|
+
* capture, which an opaque gateway 500 otherwise leaves unanswerable.
|
|
223
|
+
*/
|
|
224
|
+
requestShape?: EngineRequestShape;
|
|
206
225
|
};
|
|
226
|
+
/**
|
|
227
|
+
* Shape-only description of what an engine put on the wire. Every field is a
|
|
228
|
+
* size, a count, or a model id, so it is safe to attach to an error capture.
|
|
229
|
+
*/
|
|
230
|
+
export interface EngineRequestShape {
|
|
231
|
+
model: string;
|
|
232
|
+
payloadBytes: number;
|
|
233
|
+
toolCount: number;
|
|
234
|
+
messageCount: number;
|
|
235
|
+
}
|
|
207
236
|
export interface EngineCapabilities {
|
|
208
237
|
/** Extended / adaptive thinking support */
|
|
209
238
|
thinking: boolean;
|
|
@@ -25,6 +25,8 @@ export class EngineError extends Error {
|
|
|
25
25
|
statusCode;
|
|
26
26
|
/** Whether the provider explicitly marked this error as retryable. */
|
|
27
27
|
providerRetryable;
|
|
28
|
+
/** Upstream request id, when the provider/gateway supplied one. */
|
|
29
|
+
requestId;
|
|
28
30
|
/**
|
|
29
31
|
* Whether the request exceeded the model's context window. Set by engines that
|
|
30
32
|
* classified the provider's own reply, because the delivered message may not
|
|
@@ -33,6 +35,8 @@ export class EngineError extends Error {
|
|
|
33
35
|
* one-shot trim-and-retry recovery.
|
|
34
36
|
*/
|
|
35
37
|
contextOverflow;
|
|
38
|
+
/** Sizes and counts of the failed request; see {@link EngineRequestShape}. */
|
|
39
|
+
requestShape;
|
|
36
40
|
constructor(message, opts) {
|
|
37
41
|
super(message);
|
|
38
42
|
this.name = "EngineError";
|
|
@@ -40,6 +44,8 @@ export class EngineError extends Error {
|
|
|
40
44
|
this.upgradeUrl = opts?.upgradeUrl;
|
|
41
45
|
this.statusCode = opts?.statusCode;
|
|
42
46
|
this.providerRetryable = opts?.providerRetryable;
|
|
47
|
+
this.requestId = opts?.requestId;
|
|
43
48
|
this.contextOverflow = opts?.contextOverflow;
|
|
49
|
+
this.requestShape = opts?.requestShape;
|
|
44
50
|
}
|
|
45
51
|
}
|
|
@@ -3752,6 +3752,8 @@ export async function runAgentLoop(opts) {
|
|
|
3752
3752
|
statusCode: event.statusCode,
|
|
3753
3753
|
providerRetryable: event.providerRetryable,
|
|
3754
3754
|
contextOverflow: event.contextOverflow,
|
|
3755
|
+
requestId: event.requestId,
|
|
3756
|
+
requestShape: event.requestShape,
|
|
3755
3757
|
});
|
|
3756
3758
|
}
|
|
3757
3759
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EngineRequestShape } from "./engine/types.js";
|
|
1
2
|
import type { AgentChatEvent, RunEvent, RunStatus } from "./types.js";
|
|
2
3
|
export interface ActiveRun {
|
|
3
4
|
runId: string;
|
|
@@ -233,6 +234,11 @@ export declare function resolveSqlSubscriptionPollMs(now: number, activePollUnti
|
|
|
233
234
|
*/
|
|
234
235
|
export declare function nextSqlSubscriptionEmptyPolls(current: number, hadEvents: boolean, now: number, activePollUntil: number): number;
|
|
235
236
|
export declare function resolveSqlSubscriptionRetryMs(consecutiveFailures: number): number;
|
|
237
|
+
/**
|
|
238
|
+
* Sentry tags are strings, and an absent shape must stay absent: a run that
|
|
239
|
+
* failed before the request was built did not send a zero-byte payload.
|
|
240
|
+
*/
|
|
241
|
+
export declare function engineRequestShapeTags(shape: EngineRequestShape | undefined): Record<string, string>;
|
|
236
242
|
export interface StartRunOptions {
|
|
237
243
|
/** Keep a request-scoped serverless invocation alive for this run. */
|
|
238
244
|
waitUntil?: (promise: Promise<unknown>) => void;
|
|
@@ -299,6 +299,20 @@ function getRunErrorCode(err) {
|
|
|
299
299
|
// only when the run row is persisted.
|
|
300
300
|
return classifyTerminalErrorCode(describeErrorWithCauses(err));
|
|
301
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Sentry tags are strings, and an absent shape must stay absent: a run that
|
|
304
|
+
* failed before the request was built did not send a zero-byte payload.
|
|
305
|
+
*/
|
|
306
|
+
export function engineRequestShapeTags(shape) {
|
|
307
|
+
if (!shape)
|
|
308
|
+
return {};
|
|
309
|
+
return {
|
|
310
|
+
engineModel: shape.model,
|
|
311
|
+
enginePayloadBytes: String(shape.payloadBytes),
|
|
312
|
+
engineToolCount: String(shape.toolCount),
|
|
313
|
+
engineMessageCount: String(shape.messageCount),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
302
316
|
function getEngineRunErrorDetails(err) {
|
|
303
317
|
if (err.statusCode === 429)
|
|
304
318
|
return err.message;
|
|
@@ -1060,6 +1074,10 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1060
1074
|
let pendingTerminalEvent = null;
|
|
1061
1075
|
const captureRunError = (error, phase) => {
|
|
1062
1076
|
const errorCode = getRunErrorCode(error);
|
|
1077
|
+
// A gateway error often arrives as one opaque user-facing sentence, so the
|
|
1078
|
+
// structured fields EngineError already carries are the whole diagnostic.
|
|
1079
|
+
// Dropping them here left operators with an error id and nothing to join on.
|
|
1080
|
+
const engineError = error instanceof EngineError ? error : null;
|
|
1063
1081
|
captureError(error, {
|
|
1064
1082
|
route: "/_agent-native/agent-chat",
|
|
1065
1083
|
aiTraceId: runId,
|
|
@@ -1070,6 +1088,15 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
|
|
|
1070
1088
|
softTimedOut: softTimedOut ? "true" : "false",
|
|
1071
1089
|
abortReason: run.abortReason,
|
|
1072
1090
|
errorCode,
|
|
1091
|
+
gatewayRequestId: engineError?.requestId,
|
|
1092
|
+
statusCode: engineError?.statusCode != null
|
|
1093
|
+
? String(engineError.statusCode)
|
|
1094
|
+
: undefined,
|
|
1095
|
+
// What we sent, in sizes and counts only. A gateway rejection describes
|
|
1096
|
+
// nothing about the request behind it, so without these an oversized
|
|
1097
|
+
// payload and an upstream outage produce the same capture — which is
|
|
1098
|
+
// how one gateway 500 cost a night of guessing.
|
|
1099
|
+
...engineRequestShapeTags(engineError?.requestShape),
|
|
1073
1100
|
},
|
|
1074
1101
|
extra: {
|
|
1075
1102
|
runId,
|
|
@@ -715,11 +715,11 @@ export declare function getRunOutcomeCounters(options?: {
|
|
|
715
715
|
terminalReason: string;
|
|
716
716
|
count: number;
|
|
717
717
|
}>>;
|
|
718
|
-
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
|
|
718
|
+
/**
|
|
719
|
+
* Run cleanup is scheduled after every completed run, including completions
|
|
720
|
+
* from several concurrent requests in one isolate. Share one sweep locally;
|
|
721
|
+
* Postgres additionally serializes the durable prune across isolates.
|
|
722
|
+
*/
|
|
723
723
|
export declare function cleanupOldRuns(olderThanMs: number, erroredOlderThanMs?: number): Promise<void>;
|
|
724
724
|
/**
|
|
725
725
|
* List recent unsuccessful runs (errored, aborted, and truncated) for cut-off
|
package/dist/agent/run-store.js
CHANGED
|
@@ -2357,6 +2357,8 @@ const RUN_OUTCOME_DAY_MS = 86_400_000;
|
|
|
2357
2357
|
* every run id a user pasted into a bug report was gone before anyone looked.
|
|
2358
2358
|
*/
|
|
2359
2359
|
const UNSUCCESSFUL_STATUS_SQL_LIST = `('errored', 'aborted', 'truncated')`;
|
|
2360
|
+
const RUN_OUTCOME_PRUNE_BATCH_LIMIT = 200;
|
|
2361
|
+
const RUN_OUTCOME_PRUNE_LOCK_KEY = "agent-native:run-outcome-prune";
|
|
2360
2362
|
/**
|
|
2361
2363
|
* Fold the terminal outcomes of the rows `cleanupOldRuns` is about to delete
|
|
2362
2364
|
* into `agent_run_outcome_daily`, so success/failure RATES survive pruning even
|
|
@@ -2364,29 +2366,46 @@ const UNSUCCESSFUL_STATUS_SQL_LIST = `('errored', 'aborted', 'truncated')`;
|
|
|
2364
2366
|
* covers exactly the unpruned ones, so a rate over any window is
|
|
2365
2367
|
* `getRunOutcomeCounters()` plus the live rows — no gap, no double count.
|
|
2366
2368
|
*
|
|
2367
|
-
*
|
|
2368
|
-
*
|
|
2369
|
-
* Grouping the returned rows in TypeScript avoids
|
|
2369
|
+
* Postgres callers take a transaction-scoped advisory lease before the claim.
|
|
2370
|
+
* The bounded DELETE ... RETURNING is still the source of truth for which rows
|
|
2371
|
+
* this invocation owns. Grouping the returned rows in TypeScript avoids
|
|
2372
|
+
* dialect-specific date SQL.
|
|
2370
2373
|
* Counter upserts run in the same transaction as the delete; a failed upsert
|
|
2371
2374
|
* rolls back the claim so the source rows remain available for a retry.
|
|
2372
2375
|
*/
|
|
2373
2376
|
async function pruneAndRollUpPrunedRunOutcomes(client, cutoff, erroredCutoff) {
|
|
2374
2377
|
const prune = async (tx) => {
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2378
|
+
if (isPostgres()) {
|
|
2379
|
+
const lockResult = await tx.execute({
|
|
2380
|
+
sql: "SELECT pg_try_advisory_xact_lock(hashtextextended(?, 0::bigint)) AS acquired",
|
|
2381
|
+
args: [RUN_OUTCOME_PRUNE_LOCK_KEY],
|
|
2382
|
+
});
|
|
2383
|
+
const acquired = lockResult.rows[0]?.acquired;
|
|
2384
|
+
if (acquired !== true && acquired !== "t")
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2383
2387
|
const { rows } = await tx.execute({
|
|
2384
2388
|
sql: `DELETE FROM agent_runs
|
|
2385
|
-
WHERE
|
|
2386
|
-
|
|
2387
|
-
|
|
2389
|
+
WHERE id IN (
|
|
2390
|
+
SELECT id FROM agent_runs
|
|
2391
|
+
WHERE (status = 'completed' AND completed_at < ?)
|
|
2392
|
+
OR (status IN ${UNSUCCESSFUL_STATUS_SQL_LIST} AND completed_at < ?)
|
|
2393
|
+
ORDER BY completed_at ASC, id ASC
|
|
2394
|
+
LIMIT ${RUN_OUTCOME_PRUNE_BATCH_LIMIT}
|
|
2395
|
+
)
|
|
2396
|
+
RETURNING id, status, completed_at, terminal_reason`,
|
|
2388
2397
|
args: [cutoff, erroredCutoff],
|
|
2389
2398
|
});
|
|
2399
|
+
const runIds = rows
|
|
2400
|
+
.map((row) => row.id)
|
|
2401
|
+
.filter((id) => typeof id === "string");
|
|
2402
|
+
if (runIds.length > 0) {
|
|
2403
|
+
const placeholders = runIds.map(() => "?").join(", ");
|
|
2404
|
+
await tx.execute({
|
|
2405
|
+
sql: `DELETE FROM agent_run_events WHERE run_id IN (${placeholders})`,
|
|
2406
|
+
args: runIds,
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2390
2409
|
const groups = new Map();
|
|
2391
2410
|
for (const row of rows) {
|
|
2392
2411
|
const outcome = row;
|
|
@@ -2470,7 +2489,8 @@ export async function getRunOutcomeCounters(options) {
|
|
|
2470
2489
|
* pruned at `olderThanMs`; errored/aborted/truncated runs are kept until
|
|
2471
2490
|
* `erroredOlderThanMs` (a longer window, falling back to `olderThanMs`) so
|
|
2472
2491
|
* their event log survives for cut-off pattern analysis via listErroredRuns. */
|
|
2473
|
-
|
|
2492
|
+
let cleanupOldRunsInFlight;
|
|
2493
|
+
async function cleanupOldRunsInternal(olderThanMs, erroredOlderThanMs) {
|
|
2474
2494
|
await ensureRunTables();
|
|
2475
2495
|
const client = getDbExec();
|
|
2476
2496
|
const cutoff = Date.now() - olderThanMs;
|
|
@@ -2567,6 +2587,23 @@ export async function cleanupOldRuns(olderThanMs, erroredOlderThanMs) {
|
|
|
2567
2587
|
// counting the same source rows.
|
|
2568
2588
|
await pruneAndRollUpPrunedRunOutcomes(client, cutoff, erroredCutoff);
|
|
2569
2589
|
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Run cleanup is scheduled after every completed run, including completions
|
|
2592
|
+
* from several concurrent requests in one isolate. Share one sweep locally;
|
|
2593
|
+
* Postgres additionally serializes the durable prune across isolates.
|
|
2594
|
+
*/
|
|
2595
|
+
export function cleanupOldRuns(olderThanMs, erroredOlderThanMs) {
|
|
2596
|
+
if (cleanupOldRunsInFlight)
|
|
2597
|
+
return cleanupOldRunsInFlight;
|
|
2598
|
+
const current = cleanupOldRunsInternal(olderThanMs, erroredOlderThanMs);
|
|
2599
|
+
let settled;
|
|
2600
|
+
settled = current.finally(() => {
|
|
2601
|
+
if (cleanupOldRunsInFlight === settled)
|
|
2602
|
+
cleanupOldRunsInFlight = undefined;
|
|
2603
|
+
});
|
|
2604
|
+
cleanupOldRunsInFlight = settled;
|
|
2605
|
+
return settled;
|
|
2606
|
+
}
|
|
2570
2607
|
/**
|
|
2571
2608
|
* List recent unsuccessful runs (errored, aborted, and truncated) for cut-off
|
|
2572
2609
|
* pattern analysis. Read-only, bounded, and ordered newest-first. Surfaced via
|
|
@@ -14,6 +14,7 @@ import { type ContentPart } from "./sse-event-processor.js";
|
|
|
14
14
|
import type { ChatThreadScope, ChatThreadSnapshot } from "./use-chat-threads.js";
|
|
15
15
|
export { AssistantMessageListErrorBoundary, AssistantUiStaleIndexErrorBoundary, assistantUiRecoverableRenderErrorKind, isAssistantUiRecoverableRenderError, isAssistantUiStaleIndexError, } from "./assistant-ui-recovery.js";
|
|
16
16
|
export { displayableUserMessageText } from "./chat/message-components.js";
|
|
17
|
+
export declare function shouldSuppressUnauthenticatedDesktopThreadRestore(surface: AgentChatSurfaceKind, status: number): boolean;
|
|
17
18
|
type AssistantUiMessageResourceShape = {
|
|
18
19
|
id: string;
|
|
19
20
|
content: readonly unknown[];
|
|
@@ -44,6 +44,11 @@ import { useRunStuckDetection } from "./use-run-stuck-detection.js";
|
|
|
44
44
|
import { cn } from "./utils.js";
|
|
45
45
|
export { AssistantMessageListErrorBoundary, AssistantUiStaleIndexErrorBoundary, assistantUiRecoverableRenderErrorKind, isAssistantUiRecoverableRenderError, isAssistantUiStaleIndexError, } from "./assistant-ui-recovery.js";
|
|
46
46
|
export { displayableUserMessageText } from "./chat/message-components.js";
|
|
47
|
+
// Desktop chat mounts beside the parent identity gate, so an unauthenticated
|
|
48
|
+
// relay is an expected empty state until that gate establishes a session.
|
|
49
|
+
export function shouldSuppressUnauthenticatedDesktopThreadRestore(surface, status) {
|
|
50
|
+
return surface === "desktop" && (status === 401 || status === 403);
|
|
51
|
+
}
|
|
47
52
|
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
48
53
|
export function assistantUiMessageListStructureKey(messages) {
|
|
49
54
|
return JSON.stringify(messages.map((message) => [
|
|
@@ -2654,7 +2659,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
2654
2659
|
const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}`);
|
|
2655
2660
|
if (!res.ok) {
|
|
2656
2661
|
if (!cancelled) {
|
|
2657
|
-
setThreadRestoreError(res.status
|
|
2662
|
+
setThreadRestoreError(shouldSuppressUnauthenticatedDesktopThreadRestore(agentChatSurface, res.status)
|
|
2663
|
+
? null
|
|
2664
|
+
: res.status === 404
|
|
2665
|
+
? "not-found"
|
|
2666
|
+
: "unavailable");
|
|
2658
2667
|
}
|
|
2659
2668
|
return;
|
|
2660
2669
|
}
|
package/dist/db/client.js
CHANGED
|
@@ -1258,7 +1258,12 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
|
|
|
1258
1258
|
const { rawSql, args } = sqlAndArgs(sql);
|
|
1259
1259
|
const { timeoutMs } = dbExecQueryBudget(sql);
|
|
1260
1260
|
const pgSql = sqliteToPostgresParams(rawSql);
|
|
1261
|
-
|
|
1261
|
+
// Neon only accepts multiple SQL commands through its simple protocol;
|
|
1262
|
+
// the transaction start has no parameters, so use that overload.
|
|
1263
|
+
const runQuery = () => args.length === 0 && rawSql.includes(";")
|
|
1264
|
+
? client.query(pgSql)
|
|
1265
|
+
: client.query(pgSql, args);
|
|
1266
|
+
const result = await withDbTimeout("query", () => runQuery(), timeoutOverrideMs ?? timeoutMs);
|
|
1262
1267
|
return {
|
|
1263
1268
|
rows: result.rows,
|
|
1264
1269
|
rowsAffected: result.rowCount ?? 0,
|
|
@@ -1404,7 +1409,10 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
|
|
|
1404
1409
|
},
|
|
1405
1410
|
};
|
|
1406
1411
|
try {
|
|
1407
|
-
|
|
1412
|
+
// Send the transaction start and idle reaper together. Neon
|
|
1413
|
+
// transaction pooling can ignore startup parameters, and a
|
|
1414
|
+
// worker can die between separate BEGIN and SET LOCAL calls.
|
|
1415
|
+
await queryNeonClient(client, "BEGIN; SET LOCAL idle_in_transaction_session_timeout = 30000");
|
|
1408
1416
|
const result = await fn(tx);
|
|
1409
1417
|
await queryNeonClient(client, "COMMIT");
|
|
1410
1418
|
releaseClient();
|
package/dist/db/create-get-db.js
CHANGED
|
@@ -40,6 +40,43 @@ function getNeonServerlessDrizzle() {
|
|
|
40
40
|
export function isSqlRead(sql) {
|
|
41
41
|
return /^\s*(SELECT|WITH\s)/i.test(sql);
|
|
42
42
|
}
|
|
43
|
+
const NEON_IDLE_IN_TRANSACTION_TIMEOUT_SQL = "SET LOCAL idle_in_transaction_session_timeout = 30000";
|
|
44
|
+
function queryText(sql) {
|
|
45
|
+
if (typeof sql === "string")
|
|
46
|
+
return sql;
|
|
47
|
+
if (sql && typeof sql === "object" && "text" in sql) {
|
|
48
|
+
const text = sql.text;
|
|
49
|
+
return typeof text === "string" ? text : "";
|
|
50
|
+
}
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
function isBeginQuery(sql) {
|
|
54
|
+
return /^\s*BEGIN(?:\s|$)/i.test(queryText(sql));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Drizzle sends BEGIN through the client returned by pool.connect(), so a
|
|
58
|
+
* pool startup parameter alone is not enough protection when Neon routes the
|
|
59
|
+
* connection through a transaction pooler. Put the idle timeout in the same
|
|
60
|
+
* simple-protocol message as BEGIN; a worker killed before its next query
|
|
61
|
+
* still leaves a backend that will reap itself.
|
|
62
|
+
*/
|
|
63
|
+
function guardNeonTransactionClient(client) {
|
|
64
|
+
return new Proxy(client, {
|
|
65
|
+
get(target, prop) {
|
|
66
|
+
if (prop !== "query") {
|
|
67
|
+
const value = target[prop];
|
|
68
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
69
|
+
}
|
|
70
|
+
return (...args) => {
|
|
71
|
+
const sql = args[0];
|
|
72
|
+
if (!isBeginQuery(sql))
|
|
73
|
+
return target.query(...args);
|
|
74
|
+
const text = queryText(sql).replace(/;\s*$/, "");
|
|
75
|
+
return target.query(`${text}; ${NEON_IDLE_IN_TRANSACTION_TIMEOUT_SQL}`);
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
43
80
|
/**
|
|
44
81
|
* Wraps a @neondatabase/serverless Pool so every query goes through
|
|
45
82
|
* the same withDbTimeout + retryOnConnectionError resilience that the
|
|
@@ -129,6 +166,11 @@ export function buildResilientNeonPool(pool) {
|
|
|
129
166
|
get(target, prop) {
|
|
130
167
|
if (prop === "query")
|
|
131
168
|
return resilientQuery;
|
|
169
|
+
if (prop === "connect") {
|
|
170
|
+
return (...args) => target
|
|
171
|
+
.connect(...args)
|
|
172
|
+
.then((client) => guardNeonTransactionClient(client));
|
|
173
|
+
}
|
|
132
174
|
const val = target[prop];
|
|
133
175
|
return typeof val === "function" ? val.bind(target) : val;
|
|
134
176
|
},
|
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
+
error?: undefined;
|
|
44
45
|
summary: import("./types.js").TraceSummary;
|
|
45
46
|
spans: import("./types.js").TraceSpan[];
|
|
46
47
|
id?: undefined;
|
|
47
|
-
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
+
error?: undefined;
|
|
50
51
|
summary?: undefined;
|
|
51
52
|
spans?: undefined;
|
|
52
53
|
id: string;
|
|
53
|
-
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
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
|
}>>;
|
|
@@ -75,8 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
-
id?: undefined;
|
|
79
78
|
message?: undefined;
|
|
79
|
+
id?: 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;
|
|
97
96
|
message?: undefined;
|
|
98
97
|
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;
|
|
@@ -27,10 +27,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
27
27
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
28
28
|
error: any;
|
|
29
29
|
} | {
|
|
30
|
+
error?: undefined;
|
|
30
31
|
ok: boolean;
|
|
31
32
|
key: string;
|
|
32
33
|
baseUrlKey?: string;
|
|
33
34
|
scope: AgentEngineApiKeyScope;
|
|
34
|
-
error?: undefined;
|
|
35
35
|
}>>;
|
|
36
36
|
export {};
|