deepline 0.3.145 → 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 +1 -1
- 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/worker-api-types.ts +1 -0
- package/dist/cli/index.js +57 -17
- package/dist/cli/index.mjs +57 -17
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -9
- package/dist/index.mjs +25 -9
- 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
|
@@ -3080,7 +3080,7 @@ export class DeeplineClient {
|
|
|
3080
3080
|
* guaranteed support for every model. Runtime AI SDK/Gateway errors remain
|
|
3081
3081
|
* authoritative for model-gated values.
|
|
3082
3082
|
*
|
|
3083
|
-
* @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"`
|
|
3084
3084
|
* @returns Model metadata, provider option shapes, and runnable examples
|
|
3085
3085
|
*/
|
|
3086
3086
|
async describeModel(model: string): Promise<DeeplineAgentModelDescription> {
|
|
@@ -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
|
+
};
|