deepline 0.3.58 → 0.3.60
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 +3 -0
- package/dist/bundling-sources/sdk/src/config.ts +90 -0
- package/dist/bundling-sources/sdk/src/play.ts +45 -12
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +31 -11
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +138 -62
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +39 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +10 -54
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +116 -20
- package/dist/bundling-sources/shared_libs/plays/core.ts +46 -13
- package/dist/cli/index.js +464 -60
- package/dist/cli/index.mjs +458 -54
- package/dist/{compiler-manifest-B47AA7As.d.mts → compiler-manifest-IkyvsUO-.d.mts} +38 -10
- package/dist/{compiler-manifest-B47AA7As.d.ts → compiler-manifest-IkyvsUO-.d.ts} +38 -10
- package/dist/index.d.mts +41 -19
- package/dist/index.d.ts +41 -19
- package/dist/index.js +58 -5
- package/dist/index.mjs +58 -5
- 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/plays/bundle-play-file.mjs +57 -5
- package/package.json +1 -1
|
@@ -2057,6 +2057,9 @@ export class DeeplineClient {
|
|
|
2057
2057
|
currentPublishedVersion:
|
|
2058
2058
|
play.currentPublishedVersion ?? play.liveRevision?.version ?? null,
|
|
2059
2059
|
latestRunId: play.latestRunId ?? detail.latestRuns[0]?.workflowId ?? null,
|
|
2060
|
+
...(play.triggerMetadata
|
|
2061
|
+
? { triggerMetadata: play.triggerMetadata }
|
|
2062
|
+
: {}),
|
|
2060
2063
|
...(play.runtimeLimit ? { runtimeLimit: play.runtimeLimit } : {}),
|
|
2061
2064
|
...(play.activeScheduledPlays
|
|
2062
2065
|
? { activeScheduledPlays: play.activeScheduledPlays }
|
|
@@ -94,6 +94,27 @@ export type ProjectPinTarget =
|
|
|
94
94
|
candidates: string[];
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Where the CLI obtained the credentials for the current invocation.
|
|
99
|
+
*
|
|
100
|
+
* This is provenance only. SDK consumers may intentionally use host-level
|
|
101
|
+
* credentials, so CLI-only safety policy must be enforced by the CLI command
|
|
102
|
+
* dispatcher rather than by {@link resolveConfig}.
|
|
103
|
+
*/
|
|
104
|
+
export type CliAuthProvenance = {
|
|
105
|
+
scope: 'env' | 'folder' | 'global';
|
|
106
|
+
project:
|
|
107
|
+
| {
|
|
108
|
+
state: 'project';
|
|
109
|
+
dir: string;
|
|
110
|
+
pinPath: string;
|
|
111
|
+
source: 'marker' | 'cowork' | 'folder';
|
|
112
|
+
}
|
|
113
|
+
| { state: 'not_project' }
|
|
114
|
+
| { state: 'ambiguous_cowork_project'; candidates: string[] };
|
|
115
|
+
folderAuthPath: string | null;
|
|
116
|
+
};
|
|
117
|
+
|
|
97
118
|
/**
|
|
98
119
|
* Convert a base URL to a filesystem-safe slug for per-host config storage.
|
|
99
120
|
*
|
|
@@ -676,6 +697,75 @@ export function getActiveProjectAuthSource(
|
|
|
676
697
|
return loadProjectEnvCandidates(startDir)[0] ?? null;
|
|
677
698
|
}
|
|
678
699
|
|
|
700
|
+
function findNearestProjectMarkerDir(startDir: string): string | null {
|
|
701
|
+
let current = resolve(startDir);
|
|
702
|
+
while (true) {
|
|
703
|
+
if (
|
|
704
|
+
COWORK_PROJECT_MARKERS.some((marker) => existsSync(join(current, marker)))
|
|
705
|
+
) {
|
|
706
|
+
return current;
|
|
707
|
+
}
|
|
708
|
+
const parent = dirname(current);
|
|
709
|
+
if (parent === current) return null;
|
|
710
|
+
current = parent;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Resolve credential provenance and whether the command is being run from a
|
|
716
|
+
* project. A project is a marked checkout, a Cowork-mounted project, or a
|
|
717
|
+
* folder that already carries Deepline project auth.
|
|
718
|
+
*
|
|
719
|
+
* The result intentionally does not expose an API key. It lets the CLI reject
|
|
720
|
+
* cloud mutations which would otherwise inherit a different project's shared
|
|
721
|
+
* host-level organization selection.
|
|
722
|
+
*/
|
|
723
|
+
export function resolveCliAuthProvenance(
|
|
724
|
+
config: Pick<ResolvedConfig, 'baseUrl' | 'apiKey'>,
|
|
725
|
+
startDir: string = process.cwd(),
|
|
726
|
+
): CliAuthProvenance {
|
|
727
|
+
const envApiKey = process.env[API_KEY_ENV]?.trim();
|
|
728
|
+
const folderAuth = getResolvedProjectAuthSource(
|
|
729
|
+
config.baseUrl,
|
|
730
|
+
config.apiKey,
|
|
731
|
+
startDir,
|
|
732
|
+
);
|
|
733
|
+
const pinTarget = resolveProjectPinTarget(startDir);
|
|
734
|
+
const markerDir = findNearestProjectMarkerDir(startDir);
|
|
735
|
+
const project = !pinTarget.ok
|
|
736
|
+
? {
|
|
737
|
+
state: 'ambiguous_cowork_project' as const,
|
|
738
|
+
candidates: pinTarget.candidates,
|
|
739
|
+
}
|
|
740
|
+
: folderAuth
|
|
741
|
+
? {
|
|
742
|
+
state: 'project' as const,
|
|
743
|
+
dir: dirname(folderAuth.filePath),
|
|
744
|
+
pinPath: folderAuth.filePath,
|
|
745
|
+
source: 'folder' as const,
|
|
746
|
+
}
|
|
747
|
+
: pinTarget.source === 'cowork'
|
|
748
|
+
? {
|
|
749
|
+
state: 'project' as const,
|
|
750
|
+
dir: pinTarget.dir,
|
|
751
|
+
pinPath: join(pinTarget.dir, PROJECT_DEEPLINE_ENV_FILE),
|
|
752
|
+
source: 'cowork' as const,
|
|
753
|
+
}
|
|
754
|
+
: markerDir
|
|
755
|
+
? {
|
|
756
|
+
state: 'project' as const,
|
|
757
|
+
dir: markerDir,
|
|
758
|
+
pinPath: join(markerDir, PROJECT_DEEPLINE_ENV_FILE),
|
|
759
|
+
source: 'marker' as const,
|
|
760
|
+
}
|
|
761
|
+
: { state: 'not_project' as const };
|
|
762
|
+
return {
|
|
763
|
+
scope: envApiKey ? 'env' : folderAuth ? 'folder' : 'global',
|
|
764
|
+
project,
|
|
765
|
+
folderAuthPath: folderAuth?.filePath ?? null,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
679
769
|
export {
|
|
680
770
|
baseUrlSlug,
|
|
681
771
|
loadCliEnv,
|
|
@@ -241,7 +241,8 @@ export type PlayFetchResponse = PlayAuthoringFetchResponse;
|
|
|
241
241
|
*
|
|
242
242
|
* @sdkReference runtime 030
|
|
243
243
|
*/
|
|
244
|
-
export type PlayBindings =
|
|
244
|
+
export type PlayBindings<TInput = Record<string, unknown>> =
|
|
245
|
+
PlayAuthoringBindings<TInput>;
|
|
245
246
|
export type SqlListenerOperation = PlaySqlListenerOperation;
|
|
246
247
|
export type SqlListenerFilterScalar = PlaySqlListenerFilterScalar;
|
|
247
248
|
export type SqlListenerFilterOperator = PlaySqlListenerFilterOperator;
|
|
@@ -873,14 +874,28 @@ export type DefinedPlay<
|
|
|
873
874
|
DeeplineNamedPlay<TInput, TOutput>
|
|
874
875
|
>;
|
|
875
876
|
|
|
876
|
-
type
|
|
877
|
+
type PlayHandlerInput<THandler> = THandler extends (
|
|
878
|
+
context: DeeplinePlayRuntimeContext,
|
|
879
|
+
input: infer TInput,
|
|
880
|
+
) => Promise<PlayReturnObject>
|
|
881
|
+
? TInput
|
|
882
|
+
: never;
|
|
883
|
+
|
|
884
|
+
type PlayHandlerOutput<THandler> = THandler extends (
|
|
885
|
+
context: DeeplinePlayRuntimeContext,
|
|
886
|
+
input: unknown,
|
|
887
|
+
) => Promise<infer TOutput extends PlayReturnObject>
|
|
888
|
+
? TOutput
|
|
889
|
+
: never;
|
|
890
|
+
|
|
891
|
+
type PlayMetadata<TInput = Record<string, unknown>> = {
|
|
877
892
|
name: string;
|
|
878
893
|
description?: string;
|
|
879
|
-
bindings?: PlayBindings
|
|
894
|
+
bindings?: PlayBindings<TInput>;
|
|
880
895
|
inputSchema?: Record<string, unknown>;
|
|
881
|
-
billing?: PlayBindings['billing'];
|
|
882
|
-
runtime?: PlayBindings['runtime'];
|
|
883
|
-
compatibility?: PlayBindings['compatibility'];
|
|
896
|
+
billing?: PlayBindings<TInput>['billing'];
|
|
897
|
+
runtime?: PlayBindings<TInput>['runtime'];
|
|
898
|
+
compatibility?: PlayBindings<TInput>['compatibility'];
|
|
884
899
|
};
|
|
885
900
|
|
|
886
901
|
const PLAY_METADATA_SYMBOL = Symbol.for('deepline.play.metadata');
|
|
@@ -1608,6 +1623,18 @@ export function defineInput<TInput>(
|
|
|
1608
1623
|
export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
1609
1624
|
config: DefinePlayConfig<TInput, TOutput>,
|
|
1610
1625
|
): DefinedPlay<TInput, TOutput>;
|
|
1626
|
+
/** @internal Contextually type unannotated monitor-event handlers as unknown. */
|
|
1627
|
+
export function definePlay<TOutput extends PlayReturnObject>(
|
|
1628
|
+
name: string,
|
|
1629
|
+
fn: (
|
|
1630
|
+
context: DeeplinePlayRuntimeContext,
|
|
1631
|
+
input: unknown,
|
|
1632
|
+
) => Promise<TOutput>,
|
|
1633
|
+
bindings: PlayBindings<never> & {
|
|
1634
|
+
readonly sqlListeners: readonly SqlListenerDeclaration[];
|
|
1635
|
+
},
|
|
1636
|
+
): DefinedPlay<unknown, TOutput>;
|
|
1637
|
+
/* eslint-disable @typescript-eslint/no-explicit-any -- This constraint must infer a concrete contravariant handler input. */
|
|
1611
1638
|
/**
|
|
1612
1639
|
* Define a play with a name and function.
|
|
1613
1640
|
*
|
|
@@ -1616,11 +1643,17 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
1616
1643
|
* @param bindings - Play configuration, including runtime limits and triggers.
|
|
1617
1644
|
* @returns Play handle.
|
|
1618
1645
|
*/
|
|
1619
|
-
export function definePlay<
|
|
1646
|
+
export function definePlay<
|
|
1647
|
+
THandler extends (
|
|
1648
|
+
context: DeeplinePlayRuntimeContext,
|
|
1649
|
+
input: any,
|
|
1650
|
+
) => Promise<PlayReturnObject>,
|
|
1651
|
+
>(
|
|
1620
1652
|
name: string,
|
|
1621
|
-
fn:
|
|
1622
|
-
bindings?: PlayBindings
|
|
1623
|
-
): DefinedPlay<
|
|
1653
|
+
fn: THandler,
|
|
1654
|
+
bindings?: PlayBindings<NoInfer<PlayHandlerInput<THandler>>>,
|
|
1655
|
+
): DefinedPlay<PlayHandlerInput<THandler>, PlayHandlerOutput<THandler>>;
|
|
1656
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
1624
1657
|
/**
|
|
1625
1658
|
* @sdkReference runtime 010
|
|
1626
1659
|
*/
|
|
@@ -1630,7 +1663,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
1630
1663
|
ctx: DeeplinePlayRuntimeContext,
|
|
1631
1664
|
input: TInput,
|
|
1632
1665
|
) => Promise<TOutput>,
|
|
1633
|
-
maybeBindings?: PlayBindings
|
|
1666
|
+
maybeBindings?: PlayBindings<TInput>,
|
|
1634
1667
|
): DefinedPlay<TInput, TOutput> {
|
|
1635
1668
|
const config =
|
|
1636
1669
|
typeof nameOrConfig === 'string'
|
|
@@ -1690,7 +1723,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
1690
1723
|
);
|
|
1691
1724
|
}
|
|
1692
1725
|
|
|
1693
|
-
const metadata: PlayMetadata = {
|
|
1726
|
+
const metadata: PlayMetadata<TInput> = {
|
|
1694
1727
|
name,
|
|
1695
1728
|
...(description ? { description } : {}),
|
|
1696
1729
|
...(bindings ? { bindings } : {}),
|
|
@@ -199,7 +199,7 @@ export const SDK_RELEASE = {
|
|
|
199
199
|
// available at toolResponse.rawV2 while toolResponse.raw and all declared
|
|
200
200
|
// getters keep their established compatibility behavior.
|
|
201
201
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
202
|
-
version: '0.3.
|
|
202
|
+
version: '0.3.60',
|
|
203
203
|
updateSummary:
|
|
204
204
|
'Automatic CLI updates are now enabled by default. To opt out, run `deepline settings autoupdate off`; use `deepline settings autoupdate on` to re-enable updates or `deepline settings autoupdate pin <version>` to hold an exact release. This release also adds raw-v2 tool responses at toolResponse.rawV2 while preserving existing toolResponse.raw and declared getters.',
|
|
205
205
|
packageCapabilities: {
|
|
@@ -1089,6 +1089,20 @@ export interface PlayDefinitionDetail {
|
|
|
1089
1089
|
liveRevision?: PlayRevisionSummary | null;
|
|
1090
1090
|
/** `true` if the working revision differs from the live revision. */
|
|
1091
1091
|
isDraftDirty?: boolean;
|
|
1092
|
+
/** Live trigger state. This is operational state, not a source declaration. */
|
|
1093
|
+
triggerStatus?: PlayTriggerStatus;
|
|
1094
|
+
/**
|
|
1095
|
+
* Additive scheduler metadata for human/UI rendering. Values are absent for
|
|
1096
|
+
* plays with no cron binding and must never be inferred from source alone.
|
|
1097
|
+
*/
|
|
1098
|
+
triggerMetadata?: {
|
|
1099
|
+
cronSchedule?: string | null;
|
|
1100
|
+
cronTimezone?: string | null;
|
|
1101
|
+
nextScheduledAt?: number | null;
|
|
1102
|
+
lastScheduledAt?: number | null;
|
|
1103
|
+
blockedReason?: string | null;
|
|
1104
|
+
[key: string]: unknown;
|
|
1105
|
+
} | null;
|
|
1092
1106
|
/** Effective sandbox limit from the live revision, or the working draft before first publish. */
|
|
1093
1107
|
runtimeLimit?: PlayRuntimeLimit | null;
|
|
1094
1108
|
/** Present for a Play with a cron trigger. Advisory only; publish remains authoritative. */
|
|
@@ -1125,11 +1139,14 @@ export interface PlayListItem {
|
|
|
1125
1139
|
currentRevision?: PlayRevisionSummary | null;
|
|
1126
1140
|
liveRevision?: PlayRevisionSummary | null;
|
|
1127
1141
|
aliases?: string[];
|
|
1128
|
-
triggerStatus?:
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1142
|
+
triggerStatus?: PlayTriggerStatus;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/** Additive live trigger state shared by list, get, and describe. */
|
|
1146
|
+
export interface PlayTriggerStatus {
|
|
1147
|
+
cron: string | null;
|
|
1148
|
+
webhook: string | null;
|
|
1149
|
+
blockedReason: string | null;
|
|
1133
1150
|
}
|
|
1134
1151
|
|
|
1135
1152
|
export interface ProductNotificationEventDefinition {
|
|
@@ -1202,11 +1219,9 @@ export interface PlayDescription {
|
|
|
1202
1219
|
*/
|
|
1203
1220
|
liveVersion?: number | null;
|
|
1204
1221
|
/** Whether this play's cron and webhook triggers are armed. */
|
|
1205
|
-
triggerStatus?:
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
blockedReason: string | null;
|
|
1209
|
-
};
|
|
1222
|
+
triggerStatus?: PlayTriggerStatus;
|
|
1223
|
+
/** Additive cron timing metadata for an operational description. */
|
|
1224
|
+
triggerMetadata?: PlayDefinitionDetail['triggerMetadata'];
|
|
1210
1225
|
isDraftDirty?: boolean;
|
|
1211
1226
|
latestRunId?: string | null;
|
|
1212
1227
|
/** Effective sandbox limit from the revision named runs use. */
|
|
@@ -1512,7 +1527,12 @@ export interface PlayCheckSqlListenerEventSummary {
|
|
|
1512
1527
|
export interface PlayCheckTriggersSummary {
|
|
1513
1528
|
sqlListeners?: PlayCheckSqlListenerTrigger[];
|
|
1514
1529
|
sqlListenerEvent?: PlayCheckSqlListenerEventSummary;
|
|
1515
|
-
cron?: {
|
|
1530
|
+
cron?: {
|
|
1531
|
+
schedule: string;
|
|
1532
|
+
timezone?: string;
|
|
1533
|
+
/** Static input supplied to every run started by this cron binding. */
|
|
1534
|
+
input?: Record<string, unknown>;
|
|
1535
|
+
};
|
|
1516
1536
|
webhook?: true;
|
|
1517
1537
|
}
|
|
1518
1538
|
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
compileRequestsWithStrategy,
|
|
31
31
|
executeChunkedRequests,
|
|
32
32
|
} from './batch-runtime';
|
|
33
|
+
import type { CompiledRequestBatch } from './batch-runtime';
|
|
33
34
|
import type { AnyBatchOperationStrategy } from './batching-types';
|
|
34
35
|
import {
|
|
35
36
|
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
@@ -112,6 +113,7 @@ import {
|
|
|
112
113
|
import {
|
|
113
114
|
isProviderUnavailable,
|
|
114
115
|
serializeToolExecutionFailure,
|
|
116
|
+
ToolExecutionError,
|
|
115
117
|
TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
|
|
116
118
|
TOOL_EXECUTION_ERROR_SCHEMA_HEADER,
|
|
117
119
|
type ToolExecutionErrorSchemaVersion,
|
|
@@ -169,10 +171,10 @@ import {
|
|
|
169
171
|
import {
|
|
170
172
|
buildDurableCtxCallCacheKey,
|
|
171
173
|
buildDurableRunPlayInvocationScope,
|
|
172
|
-
buildDurableToolAggregateProviderIdempotencyKey,
|
|
173
174
|
buildDurableToolAggregateReceiptKey,
|
|
174
175
|
buildDurableToolCallAuthScopeDigest,
|
|
175
176
|
buildDurableToolCallCacheKey,
|
|
177
|
+
buildDurableToolPayloadProviderIdempotencyKey,
|
|
176
178
|
buildDurableToolProviderIdempotencyKey,
|
|
177
179
|
buildDurableToolReceiptPrefix,
|
|
178
180
|
resolveDurableCallCachePolicy,
|
|
@@ -2035,6 +2037,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2035
2037
|
private readonly toolDispatchQueuedAtByLane = new Map<string, number>();
|
|
2036
2038
|
private readonly toolDispatcherWakeWaiters = new Set<() => void>();
|
|
2037
2039
|
private toolDispatcherFailure: unknown | null = null;
|
|
2040
|
+
/**
|
|
2041
|
+
* Deepline's own zero-credit denial is run-fatal. Keep it independently of
|
|
2042
|
+
* customer control flow so a broad `try/catch` cannot turn it into a
|
|
2043
|
+
* successful Play result. Provider account capacity remains a normal typed
|
|
2044
|
+
* waterfall miss.
|
|
2045
|
+
*/
|
|
2046
|
+
private deeplineInsufficientCreditsFailure: ToolExecutionError | null = null;
|
|
2038
2047
|
private toolCallResolvers = new Map<
|
|
2039
2048
|
string,
|
|
2040
2049
|
{ resolve: (value: unknown) => void; reject: (reason: unknown) => void }
|
|
@@ -2217,10 +2226,30 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2217
2226
|
: {}),
|
|
2218
2227
|
}
|
|
2219
2228
|
: undefined,
|
|
2220
|
-
)
|
|
2229
|
+
).catch((error: unknown) => {
|
|
2230
|
+
this.recordDeeplineInsufficientCreditsFailure(error);
|
|
2231
|
+
throw error;
|
|
2232
|
+
}) as Promise<TOutput>;
|
|
2221
2233
|
},
|
|
2222
2234
|
};
|
|
2223
2235
|
|
|
2236
|
+
private recordDeeplineInsufficientCreditsFailure(error: unknown): void {
|
|
2237
|
+
if (
|
|
2238
|
+
error instanceof ToolExecutionError &&
|
|
2239
|
+
error.origin === 'deepline' &&
|
|
2240
|
+
error.code === 'INSUFFICIENT_CREDITS'
|
|
2241
|
+
) {
|
|
2242
|
+
this.deeplineInsufficientCreditsFailure ??= error;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
/** Called by the runner after customer code returns, before a success settles. */
|
|
2247
|
+
assertNoDeeplineInsufficientCreditsFailure(): void {
|
|
2248
|
+
if (this.deeplineInsufficientCreditsFailure) {
|
|
2249
|
+
throw this.deeplineInsufficientCreditsFailure;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2224
2253
|
async tool<TOutput = unknown>(
|
|
2225
2254
|
key: string,
|
|
2226
2255
|
toolId: string,
|
|
@@ -11273,6 +11302,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11273
11302
|
strategy,
|
|
11274
11303
|
getPayload: (request: ToolCallRequest) => request.input,
|
|
11275
11304
|
});
|
|
11305
|
+
const compileOwnedPhysicalBatch = (
|
|
11306
|
+
owned: ToolCallRequest[],
|
|
11307
|
+
): CompiledRequestBatch<ToolCallRequest> => {
|
|
11308
|
+
const compiled = compileRequestsWithStrategy({
|
|
11309
|
+
requests: owned,
|
|
11310
|
+
strategy,
|
|
11311
|
+
getPayload: (request: ToolCallRequest) => request.input,
|
|
11312
|
+
});
|
|
11313
|
+
if (compiled.length !== 1) {
|
|
11314
|
+
throw new Error(
|
|
11315
|
+
'Native batch owned members must compile into exactly one provider request.',
|
|
11316
|
+
);
|
|
11317
|
+
}
|
|
11318
|
+
return compiled[0]!;
|
|
11319
|
+
};
|
|
11276
11320
|
const batchParallelismCeiling =
|
|
11277
11321
|
this.governor.policy.pacing.workerToolBatchDefaultParallelism;
|
|
11278
11322
|
const batchSize =
|
|
@@ -11282,6 +11326,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11282
11326
|
batchParallelismCeiling,
|
|
11283
11327
|
)
|
|
11284
11328
|
: batchParallelismCeiling;
|
|
11329
|
+
const batchProviderActionVersion =
|
|
11330
|
+
(await this.#options.getToolActionCacheVersion?.(toolId))?.trim() ??
|
|
11331
|
+
'unversioned';
|
|
11285
11332
|
const successful: ToolCallRequest[] = [];
|
|
11286
11333
|
|
|
11287
11334
|
const deliver = (request: ToolCallRequest, result: ToolExecuteResult) => {
|
|
@@ -11332,6 +11379,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11332
11379
|
string,
|
|
11333
11380
|
{
|
|
11334
11381
|
members: ToolCallRequest[];
|
|
11382
|
+
compiled: CompiledRequestBatch<ToolCallRequest>;
|
|
11335
11383
|
providerOperationKey: string;
|
|
11336
11384
|
receiptLeaseExpiresAt: string | null;
|
|
11337
11385
|
timeoutMs: number | undefined;
|
|
@@ -11380,26 +11428,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11380
11428
|
detail.members[0]?.executionAuthScopeDigest ?? null,
|
|
11381
11429
|
providerIdempotencyReceiptKey:
|
|
11382
11430
|
detail.providerOperationKey,
|
|
11383
|
-
providerIdempotencyKey:
|
|
11384
|
-
buildDurableToolAggregateProviderIdempotencyKey({
|
|
11385
|
-
aggregateReceiptKey: detail.providerOperationKey,
|
|
11386
|
-
receiptKeys: detail.members.map(
|
|
11387
|
-
(member) =>
|
|
11388
|
-
member.providerIdempotencyKeyBase ??
|
|
11389
|
-
member.receiptKey!,
|
|
11390
|
-
),
|
|
11391
|
-
providerIdempotencyKeys: detail.members.map(
|
|
11392
|
-
(member) =>
|
|
11393
|
-
this.providerIdempotencyKeyForToolCall({
|
|
11394
|
-
cacheKey:
|
|
11395
|
-
member.providerIdempotencyKeyBase ??
|
|
11396
|
-
member.receiptKey!,
|
|
11397
|
-
force: member.force === true,
|
|
11398
|
-
leaseId: member.receiptLeaseId,
|
|
11399
|
-
logicalCallId: member.logicalCallId,
|
|
11400
|
-
}),
|
|
11401
|
-
),
|
|
11402
|
-
}),
|
|
11431
|
+
providerIdempotencyKey: detail.providerOperationKey,
|
|
11403
11432
|
receiptLeaseExpiresAt: detail.receiptLeaseExpiresAt,
|
|
11404
11433
|
timeoutMs: detail.timeoutMs,
|
|
11405
11434
|
},
|
|
@@ -11477,10 +11506,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11477
11506
|
(member) => batchRequestByCallId.get(member.memberId)!,
|
|
11478
11507
|
);
|
|
11479
11508
|
const receiptKeys = owned.map((request) => request.receiptKey!);
|
|
11480
|
-
const providerReceiptKeys = owned.map(
|
|
11481
|
-
(request) =>
|
|
11482
|
-
request.providerIdempotencyKeyBase ?? request.receiptKey!,
|
|
11483
|
-
);
|
|
11484
11509
|
const aggregateReceiptKey = buildDurableToolAggregateReceiptKey({
|
|
11485
11510
|
receiptKeys,
|
|
11486
11511
|
prefix: 'batch',
|
|
@@ -11489,16 +11514,36 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11489
11514
|
toolId: batch.batchOperation,
|
|
11490
11515
|
}),
|
|
11491
11516
|
});
|
|
11492
|
-
const
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11517
|
+
const compiled = compileOwnedPhysicalBatch(owned);
|
|
11518
|
+
if (compiled.batchOperation !== batch.batchOperation) {
|
|
11519
|
+
throw new Error(
|
|
11520
|
+
'Native batch owned members changed provider operation during compilation.',
|
|
11521
|
+
);
|
|
11522
|
+
}
|
|
11523
|
+
const providerOperationKey =
|
|
11524
|
+
buildDurableToolPayloadProviderIdempotencyKey({
|
|
11496
11525
|
orgId: this.#options.orgId,
|
|
11497
11526
|
toolId: batch.batchOperation,
|
|
11498
|
-
|
|
11499
|
-
|
|
11527
|
+
executionAuthScopeDigest:
|
|
11528
|
+
owned[0]?.executionAuthScopeDigest ?? null,
|
|
11529
|
+
providerActionVersion: batchProviderActionVersion,
|
|
11530
|
+
payload: compiled.batchPayload,
|
|
11531
|
+
forceRefreshIdentities: owned
|
|
11532
|
+
.filter((request) => request.force === true)
|
|
11533
|
+
.map((request) =>
|
|
11534
|
+
this.providerIdempotencyKeyForToolCall({
|
|
11535
|
+
cacheKey:
|
|
11536
|
+
request.providerIdempotencyKeyBase ??
|
|
11537
|
+
request.receiptKey!,
|
|
11538
|
+
force: true,
|
|
11539
|
+
leaseId: request.receiptLeaseId,
|
|
11540
|
+
logicalCallId: request.logicalCallId,
|
|
11541
|
+
}),
|
|
11542
|
+
),
|
|
11543
|
+
});
|
|
11500
11544
|
dispatchDetails.set(aggregateReceiptKey, {
|
|
11501
11545
|
members: owned,
|
|
11546
|
+
compiled,
|
|
11502
11547
|
providerOperationKey,
|
|
11503
11548
|
receiptLeaseExpiresAt: owned.reduce<string | null>(
|
|
11504
11549
|
(earliest, request) => {
|
|
@@ -11523,7 +11568,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11523
11568
|
return {
|
|
11524
11569
|
...members[0]!.call,
|
|
11525
11570
|
tool: { id: batch.batchOperation, providerActionVersion: '' },
|
|
11526
|
-
payload:
|
|
11571
|
+
payload: compiled.batchPayload,
|
|
11527
11572
|
compatibility: {
|
|
11528
11573
|
resultReceiptKey: aggregateReceiptKey,
|
|
11529
11574
|
providerOperationKey,
|
|
@@ -11531,7 +11576,40 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11531
11576
|
};
|
|
11532
11577
|
},
|
|
11533
11578
|
splitResult: async ({ result: aggregate, members }) => {
|
|
11534
|
-
const
|
|
11579
|
+
const receiptKeys = members.map((member) => {
|
|
11580
|
+
const request = batchRequestByCallId.get(member.memberId);
|
|
11581
|
+
if (!request?.receiptKey) {
|
|
11582
|
+
throw new Error(
|
|
11583
|
+
`Native batch Tool Call lost receipt for ${member.memberId}.`,
|
|
11584
|
+
);
|
|
11585
|
+
}
|
|
11586
|
+
return request.receiptKey;
|
|
11587
|
+
});
|
|
11588
|
+
const aggregateReceiptKey = buildDurableToolAggregateReceiptKey({
|
|
11589
|
+
receiptKeys,
|
|
11590
|
+
prefix: 'batch',
|
|
11591
|
+
aggregateReceiptPrefix: buildDurableToolReceiptPrefix({
|
|
11592
|
+
orgId: this.#options.orgId,
|
|
11593
|
+
toolId: batch.batchOperation,
|
|
11594
|
+
}),
|
|
11595
|
+
});
|
|
11596
|
+
const detail = dispatchDetails.get(aggregateReceiptKey);
|
|
11597
|
+
if (!detail) {
|
|
11598
|
+
throw new Error(
|
|
11599
|
+
'Native batch Tool Call lost its split compilation details.',
|
|
11600
|
+
);
|
|
11601
|
+
}
|
|
11602
|
+
if (
|
|
11603
|
+
detail.members.length !== members.length ||
|
|
11604
|
+
!detail.members.every(
|
|
11605
|
+
(request, index) => request.callId === members[index]?.memberId,
|
|
11606
|
+
)
|
|
11607
|
+
) {
|
|
11608
|
+
throw new Error(
|
|
11609
|
+
'Native batch Tool Call split members changed in transit.',
|
|
11610
|
+
);
|
|
11611
|
+
}
|
|
11612
|
+
const split = detail.compiled.splitResults(
|
|
11535
11613
|
legacyResultForBatchSplitter(aggregate),
|
|
11536
11614
|
);
|
|
11537
11615
|
return await Promise.all(
|
|
@@ -12573,11 +12651,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12573
12651
|
const receiptKeys = batch.memberRequests.map(
|
|
12574
12652
|
(request) => request.cacheKey,
|
|
12575
12653
|
);
|
|
12576
|
-
const providerIdempotencyReceiptKeys =
|
|
12577
|
-
batch.memberRequests.map(
|
|
12578
|
-
(request) =>
|
|
12579
|
-
request.providerIdempotencyKeyBase ?? request.cacheKey,
|
|
12580
|
-
);
|
|
12581
12654
|
const aggregateReceiptKey =
|
|
12582
12655
|
buildDurableToolAggregateReceiptKey({
|
|
12583
12656
|
receiptKeys,
|
|
@@ -12588,13 +12661,31 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12588
12661
|
}),
|
|
12589
12662
|
});
|
|
12590
12663
|
const aggregateProviderIdempotencyReceiptKey =
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
|
|
12597
|
-
|
|
12664
|
+
buildDurableToolPayloadProviderIdempotencyKey({
|
|
12665
|
+
orgId: this.#options.orgId,
|
|
12666
|
+
toolId: batch.batchOperation,
|
|
12667
|
+
executionAuthScopeDigest:
|
|
12668
|
+
batch.memberRequests[0]?.executionAuthScopeDigest ??
|
|
12669
|
+
null,
|
|
12670
|
+
providerActionVersion:
|
|
12671
|
+
(
|
|
12672
|
+
await this.#options.getToolActionCacheVersion?.(
|
|
12673
|
+
batch.batchOperation,
|
|
12674
|
+
)
|
|
12675
|
+
)?.trim() ?? 'unversioned',
|
|
12676
|
+
payload: batch.batchPayload,
|
|
12677
|
+
forceRefreshIdentities: batch.memberRequests
|
|
12678
|
+
.filter((request) => request.force === true)
|
|
12679
|
+
.map((request) =>
|
|
12680
|
+
this.providerIdempotencyKeyForToolCall({
|
|
12681
|
+
cacheKey:
|
|
12682
|
+
request.providerIdempotencyKeyBase ??
|
|
12683
|
+
request.cacheKey,
|
|
12684
|
+
force: true,
|
|
12685
|
+
leaseId: request.receiptLeaseId,
|
|
12686
|
+
logicalCallId: request.logicalCallId,
|
|
12687
|
+
}),
|
|
12688
|
+
),
|
|
12598
12689
|
});
|
|
12599
12690
|
let releaseToolSlot: () => void = () => undefined;
|
|
12600
12691
|
try {
|
|
@@ -12621,22 +12712,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12621
12712
|
providerIdempotencyReceiptKey:
|
|
12622
12713
|
aggregateProviderIdempotencyReceiptKey,
|
|
12623
12714
|
providerIdempotencyKey:
|
|
12624
|
-
|
|
12625
|
-
aggregateReceiptKey:
|
|
12626
|
-
aggregateProviderIdempotencyReceiptKey,
|
|
12627
|
-
receiptKeys: providerIdempotencyReceiptKeys,
|
|
12628
|
-
providerIdempotencyKeys: batch.memberRequests.map(
|
|
12629
|
-
(request) =>
|
|
12630
|
-
this.providerIdempotencyKeyForToolCall({
|
|
12631
|
-
cacheKey:
|
|
12632
|
-
request.providerIdempotencyKeyBase ??
|
|
12633
|
-
request.cacheKey,
|
|
12634
|
-
force: request.force === true,
|
|
12635
|
-
leaseId: request.receiptLeaseId,
|
|
12636
|
-
logicalCallId: request.logicalCallId,
|
|
12637
|
-
}),
|
|
12638
|
-
),
|
|
12639
|
-
}),
|
|
12715
|
+
aggregateProviderIdempotencyReceiptKey,
|
|
12640
12716
|
receiptLeaseExpiresAt: batch.memberRequests.reduce<
|
|
12641
12717
|
string | null
|
|
12642
12718
|
>((earliest, request) => {
|
|
@@ -241,3 +241,42 @@ export function buildDurableToolAggregateProviderIdempotencyKey(input: {
|
|
|
241
241
|
stableStringify({ receiptKeys, providerIdempotencyKeys }),
|
|
242
242
|
)}`;
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Provider-native batch caching is keyed by the exact compiled provider
|
|
247
|
+
* payload. Receipt identities coordinate ownership and terminal results; they
|
|
248
|
+
* cannot stand in for request identity because cache hits and foreign leases
|
|
249
|
+
* can remove members after the original batch was planned.
|
|
250
|
+
*
|
|
251
|
+
* An explicit force refresh is deliberately a separate namespace: it asks for
|
|
252
|
+
* a fresh provider operation even when the payload itself is unchanged.
|
|
253
|
+
*/
|
|
254
|
+
export function buildDurableToolPayloadProviderIdempotencyKey(input: {
|
|
255
|
+
orgId?: string | null;
|
|
256
|
+
toolId: string;
|
|
257
|
+
executionAuthScopeDigest?: string | null;
|
|
258
|
+
providerActionVersion?: string | null;
|
|
259
|
+
payload: Record<string, unknown>;
|
|
260
|
+
forceRefreshIdentities?: readonly string[];
|
|
261
|
+
}): string {
|
|
262
|
+
const toolId = input.toolId.trim();
|
|
263
|
+
if (!toolId) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
'Provider payload idempotency key needs a non-empty toolId.',
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
const forceRefreshIdentities = input.forceRefreshIdentities
|
|
269
|
+
?.map((identity) => identity.trim())
|
|
270
|
+
.filter(Boolean);
|
|
271
|
+
return `${buildDurableToolReceiptPrefix({
|
|
272
|
+
orgId: input.orgId,
|
|
273
|
+
toolId,
|
|
274
|
+
})}payload-v1:${sha256Hex(
|
|
275
|
+
stableStringify({
|
|
276
|
+
executionAuthScopeDigest: input.executionAuthScopeDigest?.trim() || null,
|
|
277
|
+
providerActionVersion: input.providerActionVersion?.trim() || null,
|
|
278
|
+
payload: input.payload,
|
|
279
|
+
...(forceRefreshIdentities?.length ? { forceRefreshIdentities } : {}),
|
|
280
|
+
}),
|
|
281
|
+
)}`;
|
|
282
|
+
}
|