deepline 0.3.237 → 0.3.239
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/dist/bundling-sources/sdk/src/client.ts +158 -295
- package/dist/bundling-sources/sdk/src/http.ts +1 -1
- package/dist/bundling-sources/sdk/src/index.ts +1 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/runs/observe-transport.ts +5 -468
- package/dist/bundling-sources/sdk/src/runs/watch-polling.ts +220 -0
- package/dist/bundling-sources/sdk/src/stream-reconnect.ts +9 -12
- package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +45 -7
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +27 -7
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +5 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +17 -0
- package/dist/cli/index.js +540 -2056
- package/dist/cli/index.mjs +540 -2056
- package/dist/index.d.mts +71 -57
- package/dist/index.d.ts +71 -57
- package/dist/index.js +297 -1732
- package/dist/index.mjs +297 -1732
- package/dist/install-integrity.json +1 -0
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { DeeplineError } from './errors.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* function ceiling even while the run keeps executing. Consumers that wait for
|
|
8
|
-
* a terminal status (`plays run --watch`, `runs tail`, `client.runs.tail`)
|
|
9
|
-
* therefore reconnect with full-jitter exponential backoff instead of treating
|
|
10
|
-
* a window end as a failure.
|
|
4
|
+
* Retry policy for the legacy run SSE compatibility method. Ordinary run
|
|
5
|
+
* watching uses a fixed REST snapshot cadence and shares only the transient
|
|
6
|
+
* read-error classification below.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
/** Base delay for the full-jitter exponential backoff between reconnects. */
|
|
@@ -29,7 +25,7 @@ export function streamReconnectDelayMs(attempt: number): number {
|
|
|
29
25
|
return Math.max(1, Math.floor(Math.random() * (cappedExponentialMs + 1)));
|
|
30
26
|
}
|
|
31
27
|
|
|
32
|
-
export function
|
|
28
|
+
export function isTransientPlayRunReadError(error: unknown): boolean {
|
|
33
29
|
if (
|
|
34
30
|
error instanceof DeeplineError &&
|
|
35
31
|
error.code === 'PLAY_STREAM_NETWORK_ABORTED'
|
|
@@ -37,10 +33,8 @@ export function isTransientPlayStreamError(error: unknown): boolean {
|
|
|
37
33
|
return true;
|
|
38
34
|
}
|
|
39
35
|
if (error instanceof DeeplineError && typeof error.statusCode === 'number') {
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// since the server may recover, but 4xx (especially 404 = run gone) is
|
|
43
|
-
// terminal and should not be hidden behind a silent retry loop.
|
|
36
|
+
// 5xx responses and network-level failures may recover. Client errors,
|
|
37
|
+
// especially 404, stay visible instead of disappearing into a retry loop.
|
|
44
38
|
return error.statusCode >= 500 && error.statusCode < 600;
|
|
45
39
|
}
|
|
46
40
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -48,3 +42,6 @@ export function isTransientPlayStreamError(error: unknown): boolean {
|
|
|
48
42
|
text,
|
|
49
43
|
);
|
|
50
44
|
}
|
|
45
|
+
|
|
46
|
+
/** @deprecated Use `isTransientPlayRunReadError`. */
|
|
47
|
+
export const isTransientPlayStreamError = isTransientPlayRunReadError;
|
|
@@ -263,10 +263,40 @@ function targetLabel(target: PlayActivityTarget): string {
|
|
|
263
263
|
case 'step':
|
|
264
264
|
return target.label ?? target.stepId;
|
|
265
265
|
case 'capacity':
|
|
266
|
-
return target.
|
|
266
|
+
return target.pool === 'provider'
|
|
267
|
+
? providerCapacityLabel(target.label)
|
|
268
|
+
: (target.label ?? `${target.pool} capacity`);
|
|
267
269
|
}
|
|
268
270
|
}
|
|
269
271
|
|
|
272
|
+
/** Older waits carried a rate scope rather than a customer-facing provider. */
|
|
273
|
+
export function providerCapacityLabel(label?: string): string {
|
|
274
|
+
if (!label) return 'The provider';
|
|
275
|
+
if (label.startsWith('org:')) return label.split(':')[1] || 'The provider';
|
|
276
|
+
return label.includes(':') ? 'The provider' : label;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function publicProviderCapacityObservation(
|
|
280
|
+
observation: PlayActivityObservation,
|
|
281
|
+
): PlayActivityObservation {
|
|
282
|
+
if (
|
|
283
|
+
observation.target.kind !== 'capacity' ||
|
|
284
|
+
observation.target.pool !== 'provider'
|
|
285
|
+
)
|
|
286
|
+
return observation;
|
|
287
|
+
if (
|
|
288
|
+
!observation.target.label?.includes(':') &&
|
|
289
|
+
!observation.activityId.startsWith('capacity:provider:org:')
|
|
290
|
+
)
|
|
291
|
+
return observation;
|
|
292
|
+
const label = providerCapacityLabel(observation.target.label);
|
|
293
|
+
return {
|
|
294
|
+
...observation,
|
|
295
|
+
activityId: `capacity:provider:${label}`,
|
|
296
|
+
target: { ...observation.target, label },
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
270
300
|
export function summarizePlayActivity(
|
|
271
301
|
observation: PlayActivityObservation,
|
|
272
302
|
): string {
|
|
@@ -297,10 +327,10 @@ export function summarizePlayActivity(
|
|
|
297
327
|
if (state.reason.kind === 'lease') {
|
|
298
328
|
return `Contended: waiting for ${state.reason.resourceKind} lease ${state.reason.resourceLabel}.`;
|
|
299
329
|
}
|
|
300
|
-
return `
|
|
330
|
+
return `Waiting: ${label}.`;
|
|
301
331
|
case 'retrying':
|
|
302
332
|
if (state.reason === 'rate_limit') {
|
|
303
|
-
return `Waiting for ${label}
|
|
333
|
+
return `Waiting for ${label}'s rate limit to allow more calls. Completed work is saved; remaining calls will continue automatically.`;
|
|
304
334
|
}
|
|
305
335
|
return `Delayed: ${label} will retry automatically.`;
|
|
306
336
|
case 'scheduled':
|
|
@@ -385,6 +415,7 @@ export function projectPlayRunActivity(input: {
|
|
|
385
415
|
status: string;
|
|
386
416
|
updatedAt?: number | null;
|
|
387
417
|
waitKind?: string | null;
|
|
418
|
+
waitBoundaryId?: string | null;
|
|
388
419
|
waitUntil?: number | null;
|
|
389
420
|
eventKey?: string | null;
|
|
390
421
|
runtimeBackend?: string | null;
|
|
@@ -410,17 +441,24 @@ export function projectPlayRunActivity(input: {
|
|
|
410
441
|
const latestExplicit = [...(input.explicit ?? [])]
|
|
411
442
|
.filter((candidate) => !isTerminalActivityState(candidate.state))
|
|
412
443
|
.sort((left, right) => right.observedAt - left.observedAt)[0];
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
444
|
+
// Retry-After is the earliest retry, not evidence that execution resumed.
|
|
445
|
+
// Retain the reason while its provider boundary is still waiting, including
|
|
446
|
+
// lagging readers with no wait projection. A different durable wait wins.
|
|
447
|
+
const stillWaitingForProvider =
|
|
448
|
+
normalizedStatus === 'waiting' &&
|
|
449
|
+
(!input.waitKind ||
|
|
450
|
+
(input.waitKind === 'sleep' &&
|
|
451
|
+
input.waitBoundaryId?.includes(':provider-backpressure-') === true));
|
|
416
452
|
const explicit =
|
|
453
|
+
!stillWaitingForProvider &&
|
|
417
454
|
latestExplicit?.target.kind === 'capacity' &&
|
|
418
455
|
latestExplicit.target.pool === 'provider' &&
|
|
419
456
|
latestExplicit.state.kind === 'retrying' &&
|
|
420
457
|
latestExplicit.state.retryAt < now
|
|
421
458
|
? null
|
|
422
459
|
: latestExplicit;
|
|
423
|
-
if (explicit)
|
|
460
|
+
if (explicit)
|
|
461
|
+
return projection(publicProviderCapacityObservation(explicit), now);
|
|
424
462
|
|
|
425
463
|
if (input.waitKind === 'detached_runner') {
|
|
426
464
|
return projection(
|
|
@@ -57,6 +57,7 @@ import type {
|
|
|
57
57
|
} from './governor/rate-state-backend';
|
|
58
58
|
import type { MapRowOutcome } from './durability-store';
|
|
59
59
|
import { stringifyPostgresJson } from './postgres-json';
|
|
60
|
+
import { providerCapacityLabel } from './activity-observation';
|
|
60
61
|
import { RUNTIME_ROW_OUTPUT_NOT_JSON_SERIALIZABLE } from './runtime-sheet-row-transition';
|
|
61
62
|
import {
|
|
62
63
|
RuntimeSheetRowWriter,
|
|
@@ -204,7 +205,7 @@ import {
|
|
|
204
205
|
TOOL_EXECUTE_RUNNER_ACCEPTS_VALUE,
|
|
205
206
|
TOOL_EXECUTE_PROVIDER_RATE_SCOPE_HEADER,
|
|
206
207
|
TOOL_EXECUTE_PROVIDER_RATE_RETRY_SNAPSHOT_HEADER,
|
|
207
|
-
|
|
208
|
+
isAttestedProvider429Response,
|
|
208
209
|
classifyToolExecuteHttpFailure,
|
|
209
210
|
createToolExecuteHttpFailureAttemptTracker,
|
|
210
211
|
parseToolExecuteRetryAfterMs,
|
|
@@ -9143,6 +9144,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9143
9144
|
|
|
9144
9145
|
successfulCount = Math.max(0, successfulCount - staleCompletedKeys.size);
|
|
9145
9146
|
failedCount = Math.max(0, failedCount - staleFailedKeys.size);
|
|
9147
|
+
this.#options.onMapRowsSettled?.({
|
|
9148
|
+
playName: this.#options.playName,
|
|
9149
|
+
runId: this.#options.runId,
|
|
9150
|
+
tableNamespace: resolvedTableNamespace,
|
|
9151
|
+
succeededRows: successfulCount,
|
|
9152
|
+
failedRows: failedCount,
|
|
9153
|
+
});
|
|
9146
9154
|
|
|
9147
9155
|
// Isolated failures are a partial result only when at least one admitted
|
|
9148
9156
|
// row completed. A fully failed admission run is systemic: preserve the
|
|
@@ -9995,6 +10003,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9995
10003
|
});
|
|
9996
10004
|
}
|
|
9997
10005
|
|
|
10006
|
+
this.#options.onMapRowsSettled?.({
|
|
10007
|
+
playName: this.#options.playName,
|
|
10008
|
+
runId: this.#options.runId,
|
|
10009
|
+
tableNamespace: resolvedTableNamespace,
|
|
10010
|
+
succeededRows:
|
|
10011
|
+
previouslyCompletedRowKeys.length + visibleCompletedRows.length,
|
|
10012
|
+
failedRows: durableFailedRows,
|
|
10013
|
+
});
|
|
10014
|
+
|
|
9998
10015
|
// This map's terminal rows have already crossed the persistence barrier
|
|
9999
10016
|
// above. Keep that verified snapshot as the authored return value instead
|
|
10000
10017
|
// of immediately re-reading the Runtime Sheet projection. A second read
|
|
@@ -14532,11 +14549,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14532
14549
|
retryAtMs: number,
|
|
14533
14550
|
): void {
|
|
14534
14551
|
const observedAt = Date.now();
|
|
14552
|
+
const label = providerCapacityLabel(provider);
|
|
14535
14553
|
this.emitExecutionEvent({
|
|
14536
14554
|
type: 'activity.observed',
|
|
14537
14555
|
observation: {
|
|
14538
|
-
activityId: `capacity:provider:${
|
|
14539
|
-
target: { kind: 'capacity', pool: 'provider', label
|
|
14556
|
+
activityId: `capacity:provider:${label}`,
|
|
14557
|
+
target: { kind: 'capacity', pool: 'provider', label },
|
|
14540
14558
|
state: {
|
|
14541
14559
|
kind: 'retrying',
|
|
14542
14560
|
reason: 'rate_limit',
|
|
@@ -19546,12 +19564,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19546
19564
|
: undefined;
|
|
19547
19565
|
}
|
|
19548
19566
|
if (
|
|
19549
|
-
dispatchedResponse
|
|
19550
|
-
dispatchedResponse.headers.get(
|
|
19551
|
-
TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER,
|
|
19552
|
-
) === '1'
|
|
19567
|
+
isAttestedProvider429Response(dispatchedResponse)
|
|
19553
19568
|
) {
|
|
19554
19569
|
this.resourceGovernor.recordProvider429();
|
|
19570
|
+
}
|
|
19571
|
+
if (
|
|
19572
|
+
dispatchedResponse.status === 429 &&
|
|
19573
|
+
isAttestedProvider429Response(dispatchedResponse)
|
|
19574
|
+
) {
|
|
19555
19575
|
provider429RetryDiagnosticStartedAt =
|
|
19556
19576
|
performance.now();
|
|
19557
19577
|
provider429RetryDiagnosticNextInvocationAttempt =
|
|
@@ -931,6 +931,14 @@ export interface ContextOptions {
|
|
|
931
931
|
*/
|
|
932
932
|
supersededKeys?: string[];
|
|
933
933
|
}>;
|
|
934
|
+
/** Distinct terminal rows after the map's persistence barrier, across resumes. */
|
|
935
|
+
onMapRowsSettled?: (input: {
|
|
936
|
+
playName?: string;
|
|
937
|
+
runId?: string;
|
|
938
|
+
tableNamespace: string;
|
|
939
|
+
succeededRows: number;
|
|
940
|
+
failedRows: number;
|
|
941
|
+
}) => void;
|
|
934
942
|
/**
|
|
935
943
|
* Persists coalesced, non-terminal row patches before a map suspends.
|
|
936
944
|
* Terminal rows use onMapRowsCompleted; this narrow seam only preserves
|
|
@@ -30,9 +30,9 @@ import { stripProvenanceTag } from './log-provenance';
|
|
|
30
30
|
* The canonical client-side projection of a persisted Play Run document into
|
|
31
31
|
* the live snapshot shape, plus the differ that turns successive snapshots
|
|
32
32
|
* into incremental `play.*` live events. This module is shared between the
|
|
33
|
-
* Vercel app (legacy SSE
|
|
34
|
-
* (`packages/sdk/src/runs/
|
|
35
|
-
* event
|
|
33
|
+
* Vercel app (dashboard and legacy SSE compatibility) and the SDK REST polling
|
|
34
|
+
* watcher (`packages/sdk/src/runs/watch-polling.ts`), so both use the same
|
|
35
|
+
* event projection from the persisted Run Snapshot. See ADR-0008.
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
export type PlayRunLiveStatus = PlayRunLifecycleStatus;
|
|
@@ -328,6 +328,7 @@ export function buildPlayRunStatusSnapshot(input: {
|
|
|
328
328
|
status: projectedSnapshot.status,
|
|
329
329
|
updatedAt: projectedSnapshot.updatedAt,
|
|
330
330
|
waitKind: input.run.waitKind ?? input.run.wait?.kind ?? null,
|
|
331
|
+
waitBoundaryId: input.run.wait?.boundaryId ?? input.run.activeBoundaryId,
|
|
331
332
|
waitUntil: input.run.waitUntil,
|
|
332
333
|
eventKey: input.run.wait?.eventKey ?? null,
|
|
333
334
|
runtimeBackend: input.run.runtimeBackend,
|
|
@@ -551,7 +552,7 @@ function diffLogLines(input: {
|
|
|
551
552
|
const missingCount = tailFirstSeq - 1 - input.lastLogSeq;
|
|
552
553
|
return {
|
|
553
554
|
lines: [
|
|
554
|
-
`[
|
|
555
|
+
`[watch] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
|
|
555
556
|
...logs,
|
|
556
557
|
],
|
|
557
558
|
lastLogSeq: totalLogCount,
|
|
@@ -496,6 +496,20 @@ export function runtimeSheetTerminalWriteCounts(input: {
|
|
|
496
496
|
};
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
+
/** Distinct settled counts include saved siblings, never skipped input duplicates. */
|
|
500
|
+
export function runtimeSheetTerminalMapCounts(
|
|
501
|
+
current: { persistedRows: number; succeededRows: number; failedRows: number },
|
|
502
|
+
map: { succeededRows: number; failedRows: number },
|
|
503
|
+
): { persistedRows: number; succeededRows: number; failedRows: number } {
|
|
504
|
+
const succeededRows = Math.max(current.succeededRows, map.succeededRows);
|
|
505
|
+
const failedRows = Math.max(current.failedRows, map.failedRows);
|
|
506
|
+
return {
|
|
507
|
+
persistedRows: Math.max(current.persistedRows, succeededRows + failedRows),
|
|
508
|
+
succeededRows,
|
|
509
|
+
failedRows,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
499
513
|
async function vercelBypassHeaders(
|
|
500
514
|
config: PlayRunnerExecutionConfig,
|
|
501
515
|
): Promise<Record<string, string>> {
|
|
@@ -2308,6 +2322,35 @@ async function run(
|
|
|
2308
2322
|
const MAP_ROW_UPDATED_EMIT_INTERVAL_MS = 200;
|
|
2309
2323
|
const MAP_ROW_UPDATED_EMIT_SAMPLE_RATE = 100;
|
|
2310
2324
|
return (event: PlayExecutionEvent) => {
|
|
2325
|
+
if (event.type === 'map.suspended') {
|
|
2326
|
+
const datasetId = createRuntimeDatasetId(
|
|
2327
|
+
config.context.playName!,
|
|
2328
|
+
event.artifactTableNamespace,
|
|
2329
|
+
);
|
|
2330
|
+
const settlement = runtimeMapDatasetSettlements.get(datasetId);
|
|
2331
|
+
if (settlement) {
|
|
2332
|
+
// This event follows the map writer's durability barrier. Only
|
|
2333
|
+
// report accepted writes, not progress (which includes duplicates).
|
|
2334
|
+
emitRunnerEvent(
|
|
2335
|
+
{
|
|
2336
|
+
type: 'execution_event',
|
|
2337
|
+
event: {
|
|
2338
|
+
type: 'dataset.lifecycle',
|
|
2339
|
+
datasetId,
|
|
2340
|
+
path: settlement.path,
|
|
2341
|
+
tableNamespace: settlement.tableNamespace,
|
|
2342
|
+
phase: 'registered',
|
|
2343
|
+
persistedRows: settlement.persistedRows,
|
|
2344
|
+
succeededRows: settlement.succeededRows,
|
|
2345
|
+
failedRows: settlement.failedRows,
|
|
2346
|
+
complete: false,
|
|
2347
|
+
at: event.at,
|
|
2348
|
+
},
|
|
2349
|
+
},
|
|
2350
|
+
options,
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2311
2354
|
if (event.type === 'map.row.updated') {
|
|
2312
2355
|
mapRowUpdatedCount++;
|
|
2313
2356
|
const now = Date.now();
|
|
@@ -2638,6 +2681,19 @@ async function run(
|
|
|
2638
2681
|
),
|
|
2639
2682
|
});
|
|
2640
2683
|
},
|
|
2684
|
+
onMapRowsSettled: (input) => {
|
|
2685
|
+
const datasetId = createRuntimeDatasetId(
|
|
2686
|
+
input.playName ?? config.context.playName!,
|
|
2687
|
+
input.tableNamespace,
|
|
2688
|
+
);
|
|
2689
|
+
const settlement = runtimeMapDatasetSettlements.get(datasetId);
|
|
2690
|
+
if (settlement && (!input.runId || settlement.runId === input.runId)) {
|
|
2691
|
+
Object.assign(
|
|
2692
|
+
settlement,
|
|
2693
|
+
runtimeSheetTerminalMapCounts(settlement, input),
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
},
|
|
2641
2697
|
onMapRowsCompleted: async (input) => {
|
|
2642
2698
|
// Persist executed rows (data + cell meta) into the tenant sheet —
|
|
2643
2699
|
// the sheet is the source of truth for dataset state, and the
|
|
@@ -86,6 +86,23 @@ export const TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER =
|
|
|
86
86
|
'x-deepline-provider-throttle';
|
|
87
87
|
export const TOOL_EXECUTE_PROVIDER_PACING_WAIT_CODE = 'PROVIDER_PACING_WAIT';
|
|
88
88
|
|
|
89
|
+
/** A trusted provider 429 remains observable if the gateway cannot commit its body. */
|
|
90
|
+
export function isAttestedProvider429Response(response: {
|
|
91
|
+
status: number;
|
|
92
|
+
headers: Pick<Headers, 'get'>;
|
|
93
|
+
}): boolean {
|
|
94
|
+
if (response.headers.get(TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER) !== '1') {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (response.status === 429) return true;
|
|
98
|
+
const code = response.headers.get('x-deepline-error-code');
|
|
99
|
+
return (
|
|
100
|
+
response.status === 409 &&
|
|
101
|
+
(code === 'durable_invocation_outcome_unknown' ||
|
|
102
|
+
code === 'durable_invocation_commit_failed')
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
/**
|
|
90
107
|
* Parse the advertisement as a comma-separated token list so a later runner can
|
|
91
108
|
* add a capability without a second header. An absent or unparsable value means
|