deepline 0.3.144 → 0.3.146
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 +53 -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/shared_libs/play-runtime/async-operation.ts +40 -4
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +259 -7
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +58 -19
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +17 -11
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +57 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +73 -16
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +57 -3
- package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +35 -3
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +1 -0
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +11 -1
- package/dist/cli/index.js +85 -17
- package/dist/cli/index.mjs +85 -17
- package/dist/{compiler-manifest-BIqRyj5m.d.mts → compiler-manifest-BJBNPTWt.d.mts} +2 -1
- package/dist/{compiler-manifest-BIqRyj5m.d.ts → compiler-manifest-BJBNPTWt.d.ts} +2 -1
- package/dist/index.d.mts +26 -4
- package/dist/index.d.ts +26 -4
- package/dist/index.js +78 -10
- package/dist/index.mjs +78 -10
- package/dist/install-integrity.json +2 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- 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
|
@@ -1960,6 +1960,21 @@ export type WorkspacesNamespace = {
|
|
|
1960
1960
|
}) => Promise<WorkspaceCreateResult>;
|
|
1961
1961
|
};
|
|
1962
1962
|
|
|
1963
|
+
/** One authenticated per-request usage event from `/api/v2/usage/events`. */
|
|
1964
|
+
export type BillingUsageEvent = {
|
|
1965
|
+
id: string | null;
|
|
1966
|
+
provider: string;
|
|
1967
|
+
operation: string;
|
|
1968
|
+
status: string;
|
|
1969
|
+
request_id: string;
|
|
1970
|
+
billing_outcome_reason: string | null;
|
|
1971
|
+
credits: number | null;
|
|
1972
|
+
billing_mode: string | null;
|
|
1973
|
+
pricing_model: string | null;
|
|
1974
|
+
policy_id: string | null;
|
|
1975
|
+
created_at: string;
|
|
1976
|
+
};
|
|
1977
|
+
|
|
1963
1978
|
/**
|
|
1964
1979
|
* Public `client.billing` namespace for CLI commands and programmatic callers.
|
|
1965
1980
|
* Covers plans, subscription state, cancellation, and invoice/receipt history.
|
|
@@ -1989,6 +2004,8 @@ export type BillingNamespace = {
|
|
|
1989
2004
|
/** Subscription invoices plus credit purchase receipts, newest first. */
|
|
1990
2005
|
list: (options?: { limit?: number }) => Promise<BillingInvoicesResult>;
|
|
1991
2006
|
};
|
|
2007
|
+
/** Read one exact execution outcome using the request_id returned by executeTool. */
|
|
2008
|
+
usageEvent: (requestId: string) => Promise<BillingUsageEvent>;
|
|
1992
2009
|
/** Metronome-authored target catalog and current Contract projection. */
|
|
1993
2010
|
targetPlans: () => Promise<TargetBillingPlansResult>;
|
|
1994
2011
|
/** Normalized target billing state. */
|
|
@@ -2600,6 +2617,7 @@ export class DeeplineClient {
|
|
|
2600
2617
|
invoices: {
|
|
2601
2618
|
list: (options) => this.listBillingInvoices(options),
|
|
2602
2619
|
},
|
|
2620
|
+
usageEvent: (requestId) => this.getBillingUsageEvent(requestId),
|
|
2603
2621
|
targetPlans: () => this.getTargetBillingPlans(),
|
|
2604
2622
|
targetStatus: () => this.getTargetBillingStatus(),
|
|
2605
2623
|
autoRecharge: {
|
|
@@ -3062,7 +3080,7 @@ export class DeeplineClient {
|
|
|
3062
3080
|
* guaranteed support for every model. Runtime AI SDK/Gateway errors remain
|
|
3063
3081
|
* authoritative for model-gated values.
|
|
3064
3082
|
*
|
|
3065
|
-
* @param model - Gateway model id such as `"openai/gpt-5.
|
|
3083
|
+
* @param model - Exact-case Gateway model id such as `"openai/gpt-5.6-luna"`
|
|
3066
3084
|
* @returns Model metadata, provider option shapes, and runnable examples
|
|
3067
3085
|
*/
|
|
3068
3086
|
async describeModel(model: string): Promise<DeeplineAgentModelDescription> {
|
|
@@ -5803,6 +5821,40 @@ export class DeeplineClient {
|
|
|
5803
5821
|
return this.http.get<BillingPlansResult>('/api/v2/billing/catalog/current');
|
|
5804
5822
|
}
|
|
5805
5823
|
|
|
5824
|
+
/**
|
|
5825
|
+
* Read the authenticated usage record for one execution request. The
|
|
5826
|
+
* request id comes from the original `executeTool` result and is not a
|
|
5827
|
+
* retry or idempotency token.
|
|
5828
|
+
*/
|
|
5829
|
+
async getBillingUsageEvent(requestId: string): Promise<BillingUsageEvent> {
|
|
5830
|
+
const normalizedRequestId = requestId.trim();
|
|
5831
|
+
if (
|
|
5832
|
+
normalizedRequestId.length === 0 ||
|
|
5833
|
+
normalizedRequestId.length > 200 ||
|
|
5834
|
+
normalizedRequestId !== requestId
|
|
5835
|
+
) {
|
|
5836
|
+
throw new DeeplineError(
|
|
5837
|
+
'Usage request_id must contain 1–200 characters with no leading or trailing whitespace.',
|
|
5838
|
+
undefined,
|
|
5839
|
+
'INVALID_USAGE_REQUEST_ID',
|
|
5840
|
+
);
|
|
5841
|
+
}
|
|
5842
|
+
const response = await this.http.get<{
|
|
5843
|
+
entries?: BillingUsageEvent[];
|
|
5844
|
+
}>(
|
|
5845
|
+
`/api/v2/usage/events?request_id=${encodeURIComponent(normalizedRequestId)}`,
|
|
5846
|
+
);
|
|
5847
|
+
const event = response.entries?.[0];
|
|
5848
|
+
if (!event) {
|
|
5849
|
+
throw new DeeplineError(
|
|
5850
|
+
'No usage event was found for this request_id.',
|
|
5851
|
+
undefined,
|
|
5852
|
+
'USAGE_EVENT_NOT_FOUND',
|
|
5853
|
+
);
|
|
5854
|
+
}
|
|
5855
|
+
return event;
|
|
5856
|
+
}
|
|
5857
|
+
|
|
5806
5858
|
/**
|
|
5807
5859
|
* Charge the saved payment method and add Deepline credits to the active
|
|
5808
5860
|
* workspace. Prefer `client.billing.topUp(...)`.
|
|
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
|
|
|
200
200
|
// getters keep their established compatibility behavior.
|
|
201
201
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
202
202
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
203
|
-
version: '0.3.
|
|
203
|
+
version: '0.3.146',
|
|
204
204
|
updateSummary:
|
|
205
205
|
'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
|
|
206
206
|
packageCapabilities: {
|
|
@@ -79,6 +79,12 @@ export type AsyncOperationResultMaterialization = {
|
|
|
79
79
|
|
|
80
80
|
export type AsyncOperationContractWire = {
|
|
81
81
|
version: 1;
|
|
82
|
+
/**
|
|
83
|
+
* Keep a default synchronous Play call in the runner process: launch the
|
|
84
|
+
* provider job, expose its handle in run progress, then poll companion tools
|
|
85
|
+
* until terminal. Direct tools.execute callers keep their own legacy policy.
|
|
86
|
+
*/
|
|
87
|
+
runtimeManagedWait?: true;
|
|
82
88
|
/** Preserve the provider's historical public async metadata exactly. */
|
|
83
89
|
compatibility?: {
|
|
84
90
|
/** null intentionally suppresses the legacy asyncGetAction projection. */
|
|
@@ -285,10 +291,7 @@ export function defineAsyncOperation(
|
|
|
285
291
|
positiveInteger(contract.polling.intervalMs, 'polling.intervalMs');
|
|
286
292
|
positiveInteger(contract.polling.timeoutMs, 'polling.timeoutMs');
|
|
287
293
|
if (contract.polling.checkTimeoutMs !== undefined) {
|
|
288
|
-
positiveInteger(
|
|
289
|
-
contract.polling.checkTimeoutMs,
|
|
290
|
-
'polling.checkTimeoutMs',
|
|
291
|
-
);
|
|
294
|
+
positiveInteger(contract.polling.checkTimeoutMs, 'polling.checkTimeoutMs');
|
|
292
295
|
}
|
|
293
296
|
if (contract.polling.intervalInputField !== undefined) {
|
|
294
297
|
required(contract.polling.intervalInputField, 'polling.intervalInputField');
|
|
@@ -906,6 +909,24 @@ export async function executeAsyncOperationLifecycle<Response>(input: {
|
|
|
906
909
|
responseValue: (response: Response) => unknown;
|
|
907
910
|
sleep?: (ms: number) => Promise<void>;
|
|
908
911
|
now?: () => number;
|
|
912
|
+
/** Called after the provider handle is known and before the first poll. */
|
|
913
|
+
onJobStarted?: (input: { jobId: string }) => void;
|
|
914
|
+
/** Called after each successful status read, including running outcomes. */
|
|
915
|
+
onPoll?: (input: {
|
|
916
|
+
jobId: string;
|
|
917
|
+
pollAttempt: number;
|
|
918
|
+
elapsedMs: number;
|
|
919
|
+
result: unknown;
|
|
920
|
+
outcome: AsyncOperationTerminalOutcome;
|
|
921
|
+
}) => void;
|
|
922
|
+
/** Called when an upstream 429 delays a status check. */
|
|
923
|
+
onRateLimit?: (input: {
|
|
924
|
+
jobId: string;
|
|
925
|
+
pollAttempt: number;
|
|
926
|
+
rateLimitAttempt: number;
|
|
927
|
+
retryAfterMs: number | null;
|
|
928
|
+
delayMs: number;
|
|
929
|
+
}) => void;
|
|
909
930
|
/** Legacy direct callers historically poll once immediately after launch. */
|
|
910
931
|
pollImmediately?: boolean;
|
|
911
932
|
}): Promise<
|
|
@@ -939,6 +960,7 @@ export async function executeAsyncOperationLifecycle<Response>(input: {
|
|
|
939
960
|
`Async operation ${input.contract.lifecycle.startAction} returned running without a provider job id.`,
|
|
940
961
|
);
|
|
941
962
|
}
|
|
963
|
+
input.onJobStarted?.({ jobId });
|
|
942
964
|
const sleep =
|
|
943
965
|
input.sleep ??
|
|
944
966
|
((ms) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
|
|
@@ -1072,6 +1094,13 @@ export async function executeAsyncOperationLifecycle<Response>(input: {
|
|
|
1072
1094
|
rateLimitAttempt: consecutiveRateLimitAttempts,
|
|
1073
1095
|
retryAfterMs: rateLimit.retryAfterMs,
|
|
1074
1096
|
});
|
|
1097
|
+
input.onRateLimit?.({
|
|
1098
|
+
jobId,
|
|
1099
|
+
pollAttempt,
|
|
1100
|
+
rateLimitAttempt: consecutiveRateLimitAttempts,
|
|
1101
|
+
retryAfterMs: rateLimit.retryAfterMs,
|
|
1102
|
+
delayMs: nextPollDelayMs,
|
|
1103
|
+
});
|
|
1075
1104
|
continue;
|
|
1076
1105
|
}
|
|
1077
1106
|
if (!pollResponse) {
|
|
@@ -1083,6 +1112,13 @@ export async function executeAsyncOperationLifecycle<Response>(input: {
|
|
|
1083
1112
|
input.contract,
|
|
1084
1113
|
previousPollResult,
|
|
1085
1114
|
);
|
|
1115
|
+
input.onPoll?.({
|
|
1116
|
+
jobId,
|
|
1117
|
+
pollAttempt,
|
|
1118
|
+
elapsedMs: Math.max(0, now() - startedAt),
|
|
1119
|
+
result: previousPollResult,
|
|
1120
|
+
outcome: classification.outcome,
|
|
1121
|
+
});
|
|
1086
1122
|
if (classification.outcome === 'running') {
|
|
1087
1123
|
pollAttempt += 1;
|
|
1088
1124
|
continue;
|
|
@@ -267,7 +267,14 @@ import {
|
|
|
267
267
|
type DurableReceiptExecutionStore,
|
|
268
268
|
} from './durable-receipt-execution';
|
|
269
269
|
import {
|
|
270
|
+
asyncOperationLaunchInput,
|
|
271
|
+
buildAsyncOperationActionInput,
|
|
272
|
+
classifyAsyncOperationResult,
|
|
273
|
+
executeAsyncOperationLifecycle,
|
|
274
|
+
extractAsyncOperationJobId,
|
|
275
|
+
readAsyncOperationPath,
|
|
270
276
|
asyncOperationShouldWait,
|
|
277
|
+
hasExplicitAsyncOperationTerminalMatch,
|
|
271
278
|
type AsyncOperationContractWire,
|
|
272
279
|
} from './async-operation';
|
|
273
280
|
import {
|
|
@@ -1510,6 +1517,13 @@ function cancelRuntimeResponseBody(response: Response): void {
|
|
|
1510
1517
|
}
|
|
1511
1518
|
}
|
|
1512
1519
|
|
|
1520
|
+
function shellQuoteProviderJobCommandValue(value: string): string {
|
|
1521
|
+
// Close the single-quoted token, emit a literal apostrophe, then reopen it.
|
|
1522
|
+
// This keeps copied status commands safe even when provider job IDs contain
|
|
1523
|
+
// shell metacharacters.
|
|
1524
|
+
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1513
1527
|
const NO_SECRET_RESOLUTION_RETRY: SecretResolutionRetryDecision = {
|
|
1514
1528
|
retry: false,
|
|
1515
1529
|
retryDelayMs: 0,
|
|
@@ -1859,6 +1873,7 @@ type NativeBatchBodyAttribution = {
|
|
|
1859
1873
|
|
|
1860
1874
|
type ToolExecutionApiOptions = {
|
|
1861
1875
|
timeoutMs?: number;
|
|
1876
|
+
abortSignal?: AbortSignal;
|
|
1862
1877
|
durableCallReceiptKey?: string | null;
|
|
1863
1878
|
/**
|
|
1864
1879
|
* Complete-result receipt the execution relay may publish before returning
|
|
@@ -8886,6 +8901,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8886
8901
|
),
|
|
8887
8902
|
pendingRowsCount: Math.max(0, totalInputCount - processedCount),
|
|
8888
8903
|
failedRowsCount: Math.max(0, failedCount - staleFailedKeys.size),
|
|
8904
|
+
supersededRows: staleCompletedKeys.size + staleFailedKeys.size,
|
|
8889
8905
|
startedAt:
|
|
8890
8906
|
this.checkpoint.mapFrames?.[mapScope.mapInvocationId]?.startedAt ??
|
|
8891
8907
|
Date.now(),
|
|
@@ -8915,6 +8931,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8915
8931
|
),
|
|
8916
8932
|
progressFailedOffset: () =>
|
|
8917
8933
|
Math.max(0, failedCount - staleFailedKeys.size),
|
|
8934
|
+
progressSupersededOffset: () =>
|
|
8935
|
+
staleCompletedKeys.size + staleFailedKeys.size,
|
|
8918
8936
|
progressTotalRows: () => totalInputCount,
|
|
8919
8937
|
rowFeed: execution.rowFeed,
|
|
8920
8938
|
retainedRowsMemoryBudgetBytes:
|
|
@@ -9140,6 +9158,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9140
9158
|
completedRowsCount: completedRows,
|
|
9141
9159
|
pendingRowsCount: 0,
|
|
9142
9160
|
failedRowsCount: failedCount,
|
|
9161
|
+
supersededRows: staleCompletedKeys.size + staleFailedKeys.size,
|
|
9143
9162
|
startedAt:
|
|
9144
9163
|
this.checkpoint.mapFrames?.[mapScope.mapInvocationId]?.startedAt ??
|
|
9145
9164
|
Date.now(),
|
|
@@ -9154,6 +9173,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9154
9173
|
completedRows,
|
|
9155
9174
|
failedRows: failedCount,
|
|
9156
9175
|
totalRows: totalInputCount,
|
|
9176
|
+
supersededRows: staleCompletedKeys.size + staleFailedKeys.size,
|
|
9157
9177
|
at: Date.now(),
|
|
9158
9178
|
});
|
|
9159
9179
|
throw new AllAdmittedRowsFailedError({
|
|
@@ -9182,6 +9202,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9182
9202
|
),
|
|
9183
9203
|
pendingRowsCount: 0,
|
|
9184
9204
|
failedRowsCount: failedCount,
|
|
9205
|
+
supersededRows: staleCompletedKeys.size + staleFailedKeys.size,
|
|
9185
9206
|
startedAt:
|
|
9186
9207
|
this.checkpoint.mapFrames?.[mapScope.mapInvocationId]?.startedAt ??
|
|
9187
9208
|
Date.now(),
|
|
@@ -9202,6 +9223,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9202
9223
|
),
|
|
9203
9224
|
failedRows: failedCount,
|
|
9204
9225
|
totalRows: totalInputCount,
|
|
9226
|
+
supersededRows: staleCompletedKeys.size + staleFailedKeys.size,
|
|
9205
9227
|
at: Date.now(),
|
|
9206
9228
|
});
|
|
9207
9229
|
|
|
@@ -9986,6 +10008,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9986
10008
|
retainedRowsMemoryBudgetBytes?: number;
|
|
9987
10009
|
progressCompletedOffset?: number | (() => number);
|
|
9988
10010
|
progressFailedOffset?: number | (() => number);
|
|
10011
|
+
progressSupersededOffset?: number | (() => number);
|
|
9989
10012
|
progressTotalRows?: number | (() => number);
|
|
9990
10013
|
/**
|
|
9991
10014
|
* When set, `items`, `executionRowKeys` and `executionRowIndexes` grow
|
|
@@ -10329,6 +10352,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10329
10352
|
liveNumber(runtimeOptions?.progressFailedOffset, 0) +
|
|
10330
10353
|
liveFrameSets.failedRowsCount,
|
|
10331
10354
|
}),
|
|
10355
|
+
supersededRows: liveNumber(
|
|
10356
|
+
runtimeOptions?.progressSupersededOffset,
|
|
10357
|
+
existing.supersededRows ?? 0,
|
|
10358
|
+
),
|
|
10332
10359
|
...(input.activeBoundaryId !== undefined
|
|
10333
10360
|
? { activeBoundaryId: input.activeBoundaryId }
|
|
10334
10361
|
: {}),
|
|
@@ -10358,6 +10385,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10358
10385
|
completedRowKeys.size,
|
|
10359
10386
|
failedRows,
|
|
10360
10387
|
totalRows: currentTotalRows(),
|
|
10388
|
+
supersededRows: liveNumber(
|
|
10389
|
+
runtimeOptions?.progressSupersededOffset,
|
|
10390
|
+
existing.supersededRows ?? 0,
|
|
10391
|
+
),
|
|
10361
10392
|
// Inline child aggregates ride the single-writer progress event so
|
|
10362
10393
|
// fan-out never contends on a per-child mutation. See ADR 0013.
|
|
10363
10394
|
...this.inlineChildAggregateEventFields(),
|
|
@@ -17781,6 +17812,189 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17781
17812
|
this.providerBodyHandlesByRowColumn.delete(rowKey);
|
|
17782
17813
|
}
|
|
17783
17814
|
|
|
17815
|
+
private async resolveAsyncOperationInRuntime(input: {
|
|
17816
|
+
toolId: string;
|
|
17817
|
+
contract: AsyncOperationContractWire;
|
|
17818
|
+
startInput: Record<string, unknown>;
|
|
17819
|
+
startResponse: ParsedToolExecuteResponse;
|
|
17820
|
+
}): Promise<ParsedToolExecuteResponse> {
|
|
17821
|
+
const startResult = input.startResponse.toolResponse?.raw;
|
|
17822
|
+
const jobId = extractAsyncOperationJobId(input.contract, startResult);
|
|
17823
|
+
const hasTerminalMatch = hasExplicitAsyncOperationTerminalMatch(
|
|
17824
|
+
input.contract,
|
|
17825
|
+
startResult,
|
|
17826
|
+
);
|
|
17827
|
+
const startClassification = classifyAsyncOperationResult(
|
|
17828
|
+
input.contract,
|
|
17829
|
+
startResult,
|
|
17830
|
+
);
|
|
17831
|
+
const launchIsRunning =
|
|
17832
|
+
input.contract.launch?.unclassifiedJobOutcome === 'running' &&
|
|
17833
|
+
!hasTerminalMatch;
|
|
17834
|
+
|
|
17835
|
+
if (
|
|
17836
|
+
hasTerminalMatch &&
|
|
17837
|
+
(startClassification.outcome === 'failed' ||
|
|
17838
|
+
startClassification.outcome === 'cancelled')
|
|
17839
|
+
) {
|
|
17840
|
+
const statusAction = jobId
|
|
17841
|
+
? input.contract.lifecycle.pollActions[0]
|
|
17842
|
+
: undefined;
|
|
17843
|
+
const statusCommand =
|
|
17844
|
+
jobId && statusAction
|
|
17845
|
+
? ` Check its status with: deepline tools execute ${shellQuoteProviderJobCommandValue(statusAction.action)}` +
|
|
17846
|
+
` --input ${shellQuoteProviderJobCommandValue(JSON.stringify(buildAsyncOperationActionInput({ mapping: statusAction.input, jobId, startInput: input.startInput, startResult })))} --json`
|
|
17847
|
+
: '';
|
|
17848
|
+
throw new Error(
|
|
17849
|
+
`${startClassification.message ?? `Provider task ${jobId ?? input.toolId} ended ${startClassification.outcome}.`}${statusCommand}`,
|
|
17850
|
+
);
|
|
17851
|
+
}
|
|
17852
|
+
|
|
17853
|
+
if (!jobId) {
|
|
17854
|
+
if (launchIsRunning) {
|
|
17855
|
+
throw new Error(
|
|
17856
|
+
`Async operation ${input.toolId} returned no provider job id; it could not be polled safely.`,
|
|
17857
|
+
);
|
|
17858
|
+
}
|
|
17859
|
+
return input.startResponse;
|
|
17860
|
+
}
|
|
17861
|
+
if (hasTerminalMatch && startClassification.outcome !== 'running') {
|
|
17862
|
+
return input.startResponse;
|
|
17863
|
+
}
|
|
17864
|
+
if (!launchIsRunning && startClassification.outcome !== 'running') {
|
|
17865
|
+
return input.startResponse;
|
|
17866
|
+
}
|
|
17867
|
+
|
|
17868
|
+
const statusAction = input.contract.lifecycle.pollActions[0];
|
|
17869
|
+
if (!statusAction) {
|
|
17870
|
+
throw new Error(
|
|
17871
|
+
`Async operation ${input.toolId} has no status action for provider job ${jobId}.`,
|
|
17872
|
+
);
|
|
17873
|
+
}
|
|
17874
|
+
const statusInput = buildAsyncOperationActionInput({
|
|
17875
|
+
mapping: statusAction.input,
|
|
17876
|
+
jobId,
|
|
17877
|
+
startInput: input.startInput,
|
|
17878
|
+
startResult,
|
|
17879
|
+
});
|
|
17880
|
+
const statusCommand =
|
|
17881
|
+
`deepline tools execute ${shellQuoteProviderJobCommandValue(statusAction.action)}` +
|
|
17882
|
+
` --input ${shellQuoteProviderJobCommandValue(JSON.stringify(statusInput))} --json`;
|
|
17883
|
+
let lastProgressLogAtMs = 0;
|
|
17884
|
+
const lifecycle = await executeAsyncOperationLifecycle({
|
|
17885
|
+
contract: input.contract,
|
|
17886
|
+
startInput: input.startInput,
|
|
17887
|
+
startResult,
|
|
17888
|
+
jobId,
|
|
17889
|
+
executeAction: ({ action, input: actionInput, timeoutMs, abortSignal }) =>
|
|
17890
|
+
this.callToolExecutionAPI(action, actionInput, {
|
|
17891
|
+
timeoutMs,
|
|
17892
|
+
abortSignal,
|
|
17893
|
+
skipAsyncOperationResolution: true,
|
|
17894
|
+
uncachedAsyncLifecycleCall: true,
|
|
17895
|
+
}),
|
|
17896
|
+
responseValue: (response) => response.toolResponse?.raw,
|
|
17897
|
+
onJobStarted: () => {
|
|
17898
|
+
this.runtimeLog(
|
|
17899
|
+
`Provider task ${jobId} is running. This Play remains synchronous while it checks for completion. ` +
|
|
17900
|
+
`Canceling the Play stops this wait, not the accepted provider job; it may still incur Deepline credits. ` +
|
|
17901
|
+
`Check it separately with: ${statusCommand}`,
|
|
17902
|
+
);
|
|
17903
|
+
},
|
|
17904
|
+
onPoll: ({ pollAttempt, elapsedMs, outcome, result }) => {
|
|
17905
|
+
if (
|
|
17906
|
+
outcome !== 'running' ||
|
|
17907
|
+
elapsedMs - lastProgressLogAtMs >= 15_000
|
|
17908
|
+
) {
|
|
17909
|
+
const statusPaths = input.contract.terminal.rules.flatMap((rule) =>
|
|
17910
|
+
rule.conditions.map((condition) => condition.path),
|
|
17911
|
+
);
|
|
17912
|
+
const providerStatus = statusPaths
|
|
17913
|
+
.map((path) => readAsyncOperationPath(result, path))
|
|
17914
|
+
.find((value): value is string => typeof value === 'string');
|
|
17915
|
+
this.runtimeLog(
|
|
17916
|
+
`Provider task ${jobId}: status check ${pollAttempt + 1} returned ${outcome}` +
|
|
17917
|
+
`${providerStatus ? ` (provider status: ${providerStatus})` : ''} after ${Math.round(elapsedMs / 1_000)}s.`,
|
|
17918
|
+
);
|
|
17919
|
+
lastProgressLogAtMs = elapsedMs;
|
|
17920
|
+
}
|
|
17921
|
+
},
|
|
17922
|
+
onRateLimit: ({
|
|
17923
|
+
pollAttempt,
|
|
17924
|
+
rateLimitAttempt,
|
|
17925
|
+
retryAfterMs,
|
|
17926
|
+
delayMs,
|
|
17927
|
+
}) => {
|
|
17928
|
+
this.runtimeLog(
|
|
17929
|
+
`Provider task ${jobId}: status check received HTTP 429 on retry ${rateLimitAttempt} (after ${pollAttempt} successful checks). ` +
|
|
17930
|
+
`${retryAfterMs === null ? 'No Retry-After was supplied' : `Retry-After was ${retryAfterMs}ms`}; retrying after ${delayMs}ms.`,
|
|
17931
|
+
);
|
|
17932
|
+
},
|
|
17933
|
+
}).catch((error: unknown) => {
|
|
17934
|
+
const surfacedError =
|
|
17935
|
+
error instanceof Error ? error : new Error(String(error));
|
|
17936
|
+
surfacedError.message =
|
|
17937
|
+
`${surfacedError.message}\nProvider task ${jobId} may still be running. ` +
|
|
17938
|
+
`Check its status with: ${statusCommand}`;
|
|
17939
|
+
throw surfacedError;
|
|
17940
|
+
});
|
|
17941
|
+
|
|
17942
|
+
if (lifecycle.kind === 'timed_out') {
|
|
17943
|
+
const timeoutError = new Error(
|
|
17944
|
+
`Provider task ${jobId} did not finish before the wait limit. ` +
|
|
17945
|
+
`Check its status with: ${statusCommand}`,
|
|
17946
|
+
);
|
|
17947
|
+
this.runtimeLog(timeoutError.message, { level: 'error' });
|
|
17948
|
+
throw timeoutError;
|
|
17949
|
+
}
|
|
17950
|
+
if (
|
|
17951
|
+
lifecycle.classification.outcome === 'failed' ||
|
|
17952
|
+
lifecycle.classification.outcome === 'cancelled'
|
|
17953
|
+
) {
|
|
17954
|
+
const terminalError = new Error(
|
|
17955
|
+
`${lifecycle.classification.message ?? `Provider task ${jobId} ended ${lifecycle.classification.outcome}.`} ` +
|
|
17956
|
+
`Check its status with: ${statusCommand}`,
|
|
17957
|
+
);
|
|
17958
|
+
this.runtimeLog(terminalError.message, { level: 'error' });
|
|
17959
|
+
throw terminalError;
|
|
17960
|
+
}
|
|
17961
|
+
if (!lifecycle.materialized) {
|
|
17962
|
+
const error = new Error(
|
|
17963
|
+
`Provider task ${jobId} completed without a materialized result. ` +
|
|
17964
|
+
`Check its status with: ${statusCommand}`,
|
|
17965
|
+
);
|
|
17966
|
+
this.runtimeLog(error.message, { level: 'error' });
|
|
17967
|
+
throw error;
|
|
17968
|
+
}
|
|
17969
|
+
|
|
17970
|
+
const materialized = lifecycle.materialized;
|
|
17971
|
+
const originalRawV2 = input.startResponse.toolResponse?.rawV2;
|
|
17972
|
+
const rawV2 =
|
|
17973
|
+
originalRawV2 &&
|
|
17974
|
+
typeof originalRawV2 === 'object' &&
|
|
17975
|
+
!Array.isArray(originalRawV2)
|
|
17976
|
+
? {
|
|
17977
|
+
...(originalRawV2 as Record<string, unknown>),
|
|
17978
|
+
data: materialized.result,
|
|
17979
|
+
}
|
|
17980
|
+
: { data: materialized.result };
|
|
17981
|
+
const responseMeta = input.startResponse.toolResponse?.meta;
|
|
17982
|
+
return {
|
|
17983
|
+
...input.startResponse,
|
|
17984
|
+
status: materialized.outcome === 'no_result' ? 'no_result' : 'completed',
|
|
17985
|
+
toolResponse: {
|
|
17986
|
+
...input.startResponse.toolResponse,
|
|
17987
|
+
raw: materialized.result,
|
|
17988
|
+
rawV2,
|
|
17989
|
+
view: 'data',
|
|
17990
|
+
},
|
|
17991
|
+
result: {
|
|
17992
|
+
data: materialized.result,
|
|
17993
|
+
...(responseMeta ? { meta: responseMeta } : {}),
|
|
17994
|
+
},
|
|
17995
|
+
};
|
|
17996
|
+
}
|
|
17997
|
+
|
|
17784
17998
|
private async callToolExecutionAPI(
|
|
17785
17999
|
toolId: string,
|
|
17786
18000
|
input: Record<string, unknown>,
|
|
@@ -17803,6 +18017,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17803
18017
|
const shouldWaitForAsyncOperation = Boolean(
|
|
17804
18018
|
asyncContract && asyncOperationShouldWait(asyncContract, input),
|
|
17805
18019
|
);
|
|
18020
|
+
const shouldResolveAsyncOperationInRuntime = Boolean(
|
|
18021
|
+
asyncContract?.runtimeManagedWait === true &&
|
|
18022
|
+
asyncContract.lifecycle.startAction === toolId &&
|
|
18023
|
+
shouldWaitForAsyncOperation,
|
|
18024
|
+
);
|
|
18025
|
+
const shouldWaitAtExecutionBoundary =
|
|
18026
|
+
shouldWaitForAsyncOperation && !shouldResolveAsyncOperationInRuntime;
|
|
17806
18027
|
// An explicitly opted-in caller-managed launch gives the Play author the
|
|
17807
18028
|
// provider handle and leaves polling to the authored graph. Do not let a
|
|
17808
18029
|
// provider's legacy synchronous helper turn a wide start wave into
|
|
@@ -18035,7 +18256,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18035
18256
|
let providerRateLimitRetryFromInvocationAttempt: number | null = null;
|
|
18036
18257
|
const serializedRequestBody = (invocationAttempt: number) =>
|
|
18037
18258
|
JSON.stringify({
|
|
18038
|
-
payload:
|
|
18259
|
+
payload: shouldResolveAsyncOperationInRuntime
|
|
18260
|
+
? asyncOperationLaunchInput(asyncContract!, toolInputSnapshot)
|
|
18261
|
+
: toolInputSnapshot,
|
|
18039
18262
|
metadata: {
|
|
18040
18263
|
parent_run_id: this.#options.runId,
|
|
18041
18264
|
invocation_attempt: invocationAttempt,
|
|
@@ -18050,7 +18273,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18050
18273
|
// admission proof distinct from the optional provider fence:
|
|
18051
18274
|
// ordinary calls are governor-managed too.
|
|
18052
18275
|
runtime_governor_managed: true,
|
|
18053
|
-
...(
|
|
18276
|
+
...(shouldWaitAtExecutionBoundary
|
|
18054
18277
|
? { async_operation_wait: true }
|
|
18055
18278
|
: {}),
|
|
18056
18279
|
...(shouldPreferAsyncProviderExecution
|
|
@@ -18278,11 +18501,26 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18278
18501
|
// not share the coordinator's TTL environment.
|
|
18279
18502
|
const hasReceiptHeartbeat = Boolean(options?.heartbeatReceipt);
|
|
18280
18503
|
const abortController =
|
|
18281
|
-
timeoutMs || hasReceiptHeartbeat
|
|
18504
|
+
timeoutMs || hasReceiptHeartbeat || options?.abortSignal
|
|
18505
|
+
? new AbortController()
|
|
18506
|
+
: null;
|
|
18507
|
+
const forwardAbortSignal = () => {
|
|
18508
|
+
if (!options?.abortSignal?.aborted) return;
|
|
18509
|
+
abortController?.abort(options.abortSignal.reason);
|
|
18510
|
+
};
|
|
18282
18511
|
const receiptHeartbeat = options?.heartbeatReceipt;
|
|
18283
18512
|
let heartbeatFailure: unknown = null;
|
|
18284
18513
|
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
|
18285
18514
|
try {
|
|
18515
|
+
if (options?.abortSignal?.aborted) {
|
|
18516
|
+
forwardAbortSignal();
|
|
18517
|
+
} else {
|
|
18518
|
+
options?.abortSignal?.addEventListener(
|
|
18519
|
+
'abort',
|
|
18520
|
+
forwardAbortSignal,
|
|
18521
|
+
{ once: true },
|
|
18522
|
+
);
|
|
18523
|
+
}
|
|
18286
18524
|
const ownershipStartedAt = Date.now();
|
|
18287
18525
|
await options?.beforeProviderCall?.();
|
|
18288
18526
|
if (runtimeReceiptReadTraceEnabled) {
|
|
@@ -18971,6 +19209,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18971
19209
|
if (timeoutHandle) {
|
|
18972
19210
|
clearTimeout(timeoutHandle);
|
|
18973
19211
|
}
|
|
19212
|
+
options?.abortSignal?.removeEventListener(
|
|
19213
|
+
'abort',
|
|
19214
|
+
forwardAbortSignal,
|
|
19215
|
+
);
|
|
18974
19216
|
}
|
|
18975
19217
|
|
|
18976
19218
|
if (!response) {
|
|
@@ -19235,6 +19477,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19235
19477
|
);
|
|
19236
19478
|
}
|
|
19237
19479
|
const parsed = parseToolExecuteResponse(toolId, responseData);
|
|
19480
|
+
const resolved = shouldResolveAsyncOperationInRuntime
|
|
19481
|
+
? await this.resolveAsyncOperationInRuntime({
|
|
19482
|
+
toolId,
|
|
19483
|
+
contract: asyncContract!,
|
|
19484
|
+
startInput: toolInputSnapshot,
|
|
19485
|
+
startResponse: parsed,
|
|
19486
|
+
})
|
|
19487
|
+
: parsed;
|
|
19238
19488
|
// Only the configured execution relay may mint this header. The
|
|
19239
19489
|
// ordinary app route (and every direct provider path) keeps the
|
|
19240
19490
|
// established full-payload completion behavior even if an
|
|
@@ -19249,14 +19499,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19249
19499
|
: null;
|
|
19250
19500
|
setSpanAttributes(span, {
|
|
19251
19501
|
'plays.tool_result_kind':
|
|
19252
|
-
|
|
19502
|
+
resolved.result == null
|
|
19253
19503
|
? 'null'
|
|
19254
|
-
: Array.isArray(
|
|
19504
|
+
: Array.isArray(resolved.result)
|
|
19255
19505
|
? 'array'
|
|
19256
|
-
: typeof
|
|
19506
|
+
: typeof resolved.result,
|
|
19257
19507
|
});
|
|
19258
19508
|
toolCallSucceeded = true;
|
|
19259
|
-
return receiptOutputRef
|
|
19509
|
+
return receiptOutputRef
|
|
19510
|
+
? { ...resolved, receiptOutputRef }
|
|
19511
|
+
: resolved;
|
|
19260
19512
|
}
|
|
19261
19513
|
},
|
|
19262
19514
|
);
|
|
@@ -489,6 +489,8 @@ export interface MapExecutionFrame {
|
|
|
489
489
|
completedRowsCount?: number;
|
|
490
490
|
pendingRowsCount?: number;
|
|
491
491
|
failedRowsCount?: number;
|
|
492
|
+
/** Terminal rows omitted from this run's latest view because a newer write won. */
|
|
493
|
+
supersededRows?: number;
|
|
492
494
|
activeBoundaryId?: string | null;
|
|
493
495
|
startedAt: number;
|
|
494
496
|
updatedAt: number;
|
|
@@ -590,6 +592,8 @@ export type PlayExecutionEvent =
|
|
|
590
592
|
totalRows: number;
|
|
591
593
|
completedRows: number;
|
|
592
594
|
pendingRows: number;
|
|
595
|
+
/** Rows omitted because a newer Runtime Sheet write already won. */
|
|
596
|
+
supersededRows?: number;
|
|
593
597
|
at: number;
|
|
594
598
|
}
|
|
595
599
|
| {
|
|
@@ -601,6 +605,8 @@ export type PlayExecutionEvent =
|
|
|
601
605
|
totalRows: number;
|
|
602
606
|
completedRows: number;
|
|
603
607
|
pendingRows: number;
|
|
608
|
+
/** Rows omitted because a newer Runtime Sheet write already won. */
|
|
609
|
+
supersededRows?: number;
|
|
604
610
|
at: number;
|
|
605
611
|
}
|
|
606
612
|
| {
|
|
@@ -612,6 +618,8 @@ export type PlayExecutionEvent =
|
|
|
612
618
|
completedRows: number;
|
|
613
619
|
failedRows: number;
|
|
614
620
|
totalRows?: number;
|
|
621
|
+
/** Rows whose Runtime Sheet write lost to a newer write; not failures. */
|
|
622
|
+
supersededRows?: number;
|
|
615
623
|
/**
|
|
616
624
|
* Inline child-play composition aggregates for this run. Maintained by the
|
|
617
625
|
* single-writer progress path (never per child), so fan-out cannot contend.
|
|
@@ -644,6 +652,8 @@ export type PlayExecutionEvent =
|
|
|
644
652
|
completedRows: number;
|
|
645
653
|
failedRows: number;
|
|
646
654
|
totalRows?: number;
|
|
655
|
+
/** Rows whose Runtime Sheet write lost to a newer write; not failures. */
|
|
656
|
+
supersededRows?: number;
|
|
647
657
|
/**
|
|
648
658
|
* Inline child-play composition aggregates for this run. Maintained by the
|
|
649
659
|
* single-writer progress path (never per child). See ADR 0013.
|
|
@@ -371,6 +371,7 @@ export type PlayRunnerResult =
|
|
|
371
371
|
/** Customer-safe runtime observations that must survive bounded log tails. */
|
|
372
372
|
runtimeDiagnostics?: CtxFetchHttpFailureDiagnostic[];
|
|
373
373
|
outputRowCount?: number;
|
|
374
|
+
rowOutcomes?: PlayRunnerRowOutcomeSummary;
|
|
374
375
|
logs: string[];
|
|
375
376
|
stats: Record<string, unknown>;
|
|
376
377
|
steps: PlayStep[];
|
|
@@ -406,5 +407,14 @@ export type PlayRunnerResult =
|
|
|
406
407
|
totalRows?: number;
|
|
407
408
|
inserted?: number;
|
|
408
409
|
skipped?: number;
|
|
410
|
+
rowOutcomes?: PlayRunnerRowOutcomeSummary;
|
|
409
411
|
runtimeTiming?: PlayRunnerRuntimeTiming;
|
|
410
412
|
};
|
|
413
|
+
|
|
414
|
+
export type PlayRunnerRowOutcomeSummary = {
|
|
415
|
+
completedRows: number;
|
|
416
|
+
failedRows: number;
|
|
417
|
+
totalRows: number;
|
|
418
|
+
/** Rows omitted because a newer Runtime Sheet write took precedence. */
|
|
419
|
+
supersededRows?: number;
|
|
420
|
+
};
|