deepline 0.3.44 → 0.3.45
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/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1540 -265
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +22 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +16 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +418 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/index.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +304 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/testkit.ts +83 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/always-fresh-adapter.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/contract.ts +135 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +291 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +335 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +904 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +522 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/testkit.ts +165 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/install-integrity.json +11 -0
- package/package.json +1 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PlayRunnerBackend,
|
|
3
|
+
PlayRunnerCallbacks,
|
|
4
|
+
PlayRunnerRuntimeLifecycleEvent,
|
|
5
|
+
PlayRunnerRuntimeResource,
|
|
6
|
+
} from '../runner-backends/types';
|
|
7
|
+
import { SandboxRunnerPreCodeDeferralError } from '../runner-backends/types';
|
|
8
|
+
import type {
|
|
9
|
+
PlayCheckpoint,
|
|
10
|
+
PlayExecutionEvent,
|
|
11
|
+
PlayRowUpdate,
|
|
12
|
+
} from '../ctx-types';
|
|
13
|
+
import type {
|
|
14
|
+
PlayRunnerExecutionConfig,
|
|
15
|
+
PlayRunnerLogEvent,
|
|
16
|
+
PlayRunnerResult,
|
|
17
|
+
} from '../protocol';
|
|
18
|
+
import type {
|
|
19
|
+
SandboxExecutionLifecycle,
|
|
20
|
+
SandboxExecutionPort,
|
|
21
|
+
SandboxExecutionPortOutcome,
|
|
22
|
+
SandboxExecutionResource,
|
|
23
|
+
} from './contract';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The named scheduler read needed by an already-running runner. It is not a
|
|
27
|
+
* generic callback bag: it is the one scheduler observation the backend needs
|
|
28
|
+
* to turn accepted detached work into a safe suspension.
|
|
29
|
+
*/
|
|
30
|
+
export type SandboxRunnerReadiness = (input: {
|
|
31
|
+
baselineHeartbeatAt?: string | null;
|
|
32
|
+
}) => Promise<{ ready: boolean; heartbeatAt: string | null } | null>;
|
|
33
|
+
|
|
34
|
+
/** Progress is orthogonal to sandbox lifecycle and remains a separate port. */
|
|
35
|
+
export type SandboxRunnerProgress = {
|
|
36
|
+
onLog?: (event: PlayRunnerLogEvent) => void;
|
|
37
|
+
onCheckpoint?: (checkpoint: PlayCheckpoint) => void;
|
|
38
|
+
onRowUpdate?: (update: PlayRowUpdate) => void;
|
|
39
|
+
onExecutionEvent?: (event: PlayExecutionEvent) => void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Existing runner results have a broader status vocabulary than Sandbox Runs.
|
|
44
|
+
* The worker owns retry/terminal policy, so it must make this conversion
|
|
45
|
+
* explicit rather than letting the adapter guess whether a failed result is
|
|
46
|
+
* safe to retry.
|
|
47
|
+
*/
|
|
48
|
+
export type SandboxRunnerResultInterpreter = (input: {
|
|
49
|
+
result: PlayRunnerResult;
|
|
50
|
+
}) =>
|
|
51
|
+
| SandboxExecutionPortOutcome<PlayRunnerResult>
|
|
52
|
+
| Promise<SandboxExecutionPortOutcome<PlayRunnerResult>>;
|
|
53
|
+
|
|
54
|
+
export type SandboxRunnerBackendAdapterDependencies = {
|
|
55
|
+
backend: PlayRunnerBackend;
|
|
56
|
+
readiness: SandboxRunnerReadiness;
|
|
57
|
+
interpretResult: SandboxRunnerResultInterpreter;
|
|
58
|
+
cancellationSignal?: AbortSignal;
|
|
59
|
+
progress?: SandboxRunnerProgress;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Concrete Adapter from the long-lived PlayRunnerBackend callback protocol to
|
|
64
|
+
* the Sandbox Runs Interface.
|
|
65
|
+
*
|
|
66
|
+
* This is intentionally the only place that builds a PlayRunnerCallbacks bag.
|
|
67
|
+
* Callers supply named scheduler ports and receive the deep Sandbox Runs
|
|
68
|
+
* lifecycle; runner-backend compatibility remains local to this Adapter.
|
|
69
|
+
*/
|
|
70
|
+
export function createPlayRunnerBackendSandboxExecutionAdapter(
|
|
71
|
+
dependencies: SandboxRunnerBackendAdapterDependencies,
|
|
72
|
+
): SandboxExecutionPort<
|
|
73
|
+
PlayRunnerExecutionConfig,
|
|
74
|
+
PlayCheckpoint,
|
|
75
|
+
unknown,
|
|
76
|
+
PlayRunnerResult
|
|
77
|
+
> {
|
|
78
|
+
return {
|
|
79
|
+
async execute({ execution, lifecycle }) {
|
|
80
|
+
if (execution.mode.kind === 'detached_observation') {
|
|
81
|
+
throw new Error(
|
|
82
|
+
'PlayRunnerBackend Sandbox Runs Adapter cannot observe a detached runner; the scheduler must resolve persisted runner state without invoking a backend.',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (execution.mode.kind === 'fresh') {
|
|
86
|
+
const admission = await lifecycle.beforeFirstProviderCreate();
|
|
87
|
+
if (admission.kind === 'pre_code_deferred') return admission;
|
|
88
|
+
}
|
|
89
|
+
const resources = new Map<string, SandboxExecutionResource>();
|
|
90
|
+
const callbacks = createRunnerCallbacks({
|
|
91
|
+
lifecycle,
|
|
92
|
+
resources,
|
|
93
|
+
readiness: dependencies.readiness,
|
|
94
|
+
cancellationSignal: dependencies.cancellationSignal,
|
|
95
|
+
progress: dependencies.progress,
|
|
96
|
+
});
|
|
97
|
+
let result: PlayRunnerResult;
|
|
98
|
+
try {
|
|
99
|
+
result = await dependencies.backend.execute(
|
|
100
|
+
execution.launch,
|
|
101
|
+
callbacks,
|
|
102
|
+
);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof SandboxRunnerPreCodeDeferralError) {
|
|
105
|
+
return { kind: 'pre_code_deferred', reason: error.reason };
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
const outcome = await dependencies.interpretResult({ result });
|
|
110
|
+
return outcome.kind === 'suspended' &&
|
|
111
|
+
result.status === 'suspended' &&
|
|
112
|
+
result.suspension.kind === 'detached_runner'
|
|
113
|
+
? { ...outcome, requiresDetachedRunnerReadiness: true }
|
|
114
|
+
: outcome;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createRunnerCallbacks(input: {
|
|
120
|
+
lifecycle: SandboxExecutionLifecycle;
|
|
121
|
+
resources: Map<string, SandboxExecutionResource>;
|
|
122
|
+
readiness: SandboxRunnerReadiness;
|
|
123
|
+
cancellationSignal?: AbortSignal;
|
|
124
|
+
progress?: SandboxRunnerProgress;
|
|
125
|
+
}): PlayRunnerCallbacks {
|
|
126
|
+
const remember = (resource: PlayRunnerRuntimeResource) => {
|
|
127
|
+
const converted = toSandboxResource(resource);
|
|
128
|
+
input.resources.set(resourceKey(converted), converted);
|
|
129
|
+
return converted;
|
|
130
|
+
};
|
|
131
|
+
const requireResource = (
|
|
132
|
+
provider: 'daytona' | 'modal',
|
|
133
|
+
sandboxId: string,
|
|
134
|
+
) => {
|
|
135
|
+
const resource = input.resources.get(`${provider}:${sandboxId}`);
|
|
136
|
+
if (!resource) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`PlayRunnerBackend reported ${provider} command acceptance for ${sandboxId} before recording its Sandbox Runs resource fact.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return resource;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
cancellationSignal: input.cancellationSignal,
|
|
146
|
+
onLog: input.progress?.onLog,
|
|
147
|
+
onCheckpoint: input.progress?.onCheckpoint,
|
|
148
|
+
onRowUpdate: input.progress?.onRowUpdate,
|
|
149
|
+
onExecutionEvent: input.progress?.onExecutionEvent,
|
|
150
|
+
readDetachedRunnerReadiness: async (baselineHeartbeatAt) =>
|
|
151
|
+
await input.readiness({ baselineHeartbeatAt }),
|
|
152
|
+
onRuntimeResourceAcquired: async (resource) => {
|
|
153
|
+
await input.lifecycle.recordResource(remember(resource));
|
|
154
|
+
},
|
|
155
|
+
onLateRuntimeResourceAcquired: async (resource) => {
|
|
156
|
+
await input.lifecycle.recordLateResource(remember(resource));
|
|
157
|
+
},
|
|
158
|
+
onAmbiguousModalCreateIntent: async (intent) => {
|
|
159
|
+
await input.lifecycle.recordCreateIntent({
|
|
160
|
+
provider: 'modal',
|
|
161
|
+
routingDomain: intent.modalAppId,
|
|
162
|
+
runtimeEnvironment: intent.runtimeEnvironment,
|
|
163
|
+
correlation: intent.tags,
|
|
164
|
+
...(intent.sandboxCapacityLeaseId
|
|
165
|
+
? { capacityLeaseId: intent.sandboxCapacityLeaseId }
|
|
166
|
+
: {}),
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
onAmbiguousModalCreateResolved: async () => {
|
|
170
|
+
await input.lifecycle.resolveCreateIntent();
|
|
171
|
+
},
|
|
172
|
+
reserveSandboxCapacity: async (provider, providerAttempt) => {
|
|
173
|
+
const outcome = await input.lifecycle.reserveCapacity({
|
|
174
|
+
provider,
|
|
175
|
+
providerAttempt,
|
|
176
|
+
});
|
|
177
|
+
if (outcome.kind !== 'reserved') {
|
|
178
|
+
throw new SandboxRunnerPreCodeDeferralError(outcome.reason);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
leaseId: outcome.leaseId,
|
|
182
|
+
providerOperationToken: outcome.providerOperationToken ?? null,
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
releaseSandboxCapacity: async (leaseId) => {
|
|
186
|
+
await input.lifecycle.releaseCapacity({ leaseId });
|
|
187
|
+
},
|
|
188
|
+
shouldAttemptSandboxProvider: async (provider) =>
|
|
189
|
+
await input.lifecycle.claimProviderCircuit({ provider }),
|
|
190
|
+
markSandboxProviderHealthy: async (provider, probeToken) => {
|
|
191
|
+
await input.lifecycle.markProviderHealthy({ provider, probeToken });
|
|
192
|
+
},
|
|
193
|
+
onRuntimeLifecycleEvent: async (event) => {
|
|
194
|
+
await input.lifecycle.recordProviderLifecycleEvent(
|
|
195
|
+
toProviderLifecycleEvent(event),
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
onRunnerCommandAccepted: ({ provider, sandboxId }) => {
|
|
199
|
+
const resource = requireResource(provider, sandboxId);
|
|
200
|
+
input.lifecycle.commandAccepted({ provider, resource });
|
|
201
|
+
},
|
|
202
|
+
onDetachedRunnerReady: ({
|
|
203
|
+
provider,
|
|
204
|
+
sandboxId,
|
|
205
|
+
boundaryId,
|
|
206
|
+
baselineHeartbeatAt,
|
|
207
|
+
}) => {
|
|
208
|
+
const resource = requireResource(provider, sandboxId);
|
|
209
|
+
input.lifecycle.runnerReady({
|
|
210
|
+
provider,
|
|
211
|
+
resource,
|
|
212
|
+
observation: { boundaryId, baselineHeartbeatAt },
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function toSandboxResource(
|
|
219
|
+
resource: PlayRunnerRuntimeResource,
|
|
220
|
+
): SandboxExecutionResource {
|
|
221
|
+
const isModal = resource.kind === 'modal_sandbox';
|
|
222
|
+
return {
|
|
223
|
+
kind: resource.kind,
|
|
224
|
+
provider: isModal ? 'modal' : 'daytona',
|
|
225
|
+
resourceId: resource.sandboxId,
|
|
226
|
+
routingDomain: isModal
|
|
227
|
+
? (resource.modalAppId ?? null)
|
|
228
|
+
: (resource.daytonaOrganizationId ?? null),
|
|
229
|
+
billingStartedAtMs: resource.billingStartedAt,
|
|
230
|
+
billingEndedAtMs: resource.billingEndedAt ?? null,
|
|
231
|
+
lateAcquired: resource.lateAcquired,
|
|
232
|
+
maxBillingDurationSeconds: resource.maxBillingDurationSeconds ?? null,
|
|
233
|
+
cpu: resource.cpu ?? null,
|
|
234
|
+
memoryGiB: resource.memoryGiB ?? null,
|
|
235
|
+
diskGiB: resource.diskGiB ?? null,
|
|
236
|
+
// Historical Daytona facts use `daytonaEnvironment`; normalize it at the
|
|
237
|
+
// portable boundary so the cleanup job stays in the resource's actual
|
|
238
|
+
// runtime lane rather than falling back to production.
|
|
239
|
+
runtimeEnvironment:
|
|
240
|
+
resource.runtimeEnvironment ??
|
|
241
|
+
resource.daytonaEnvironment ??
|
|
242
|
+
'production',
|
|
243
|
+
...(resource.sandboxCapacityLeaseId
|
|
244
|
+
? { capacityLeaseId: resource.sandboxCapacityLeaseId }
|
|
245
|
+
: {}),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Rehydrate the established scheduler storage representation at the concrete
|
|
251
|
+
* Postgres Adapter seam. The portable Sandbox Runs contract remains free of
|
|
252
|
+
* runner-backend compatibility fields such as `sandboxId` and
|
|
253
|
+
* `daytonaOrganizationId`.
|
|
254
|
+
*/
|
|
255
|
+
export function playRunnerRuntimeResourceFromSandboxExecutionResource(
|
|
256
|
+
resource: SandboxExecutionResource,
|
|
257
|
+
): PlayRunnerRuntimeResource {
|
|
258
|
+
const isModal = resource.provider === 'modal';
|
|
259
|
+
if (resource.kind === 'local_process' || resource.provider === 'local') {
|
|
260
|
+
throw new Error(
|
|
261
|
+
'The PlayRunnerBackend Sandbox Runs Adapter cannot persist a local process as a physical runtime resource.',
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
kind: isModal ? 'modal_sandbox' : 'daytona_sandbox',
|
|
266
|
+
sandboxId: resource.resourceId,
|
|
267
|
+
runtimeEnvironment: resource.runtimeEnvironment,
|
|
268
|
+
...(isModal
|
|
269
|
+
? { modalAppId: resource.routingDomain ?? undefined }
|
|
270
|
+
: {
|
|
271
|
+
daytonaEnvironment: resource.runtimeEnvironment,
|
|
272
|
+
daytonaOrganizationId: resource.routingDomain ?? undefined,
|
|
273
|
+
}),
|
|
274
|
+
billingStartedAt: resource.billingStartedAtMs,
|
|
275
|
+
billingEndedAt: resource.billingEndedAtMs ?? null,
|
|
276
|
+
lateAcquired: resource.lateAcquired,
|
|
277
|
+
maxBillingDurationSeconds: resource.maxBillingDurationSeconds ?? null,
|
|
278
|
+
cpu: resource.cpu ?? null,
|
|
279
|
+
memoryGiB: resource.memoryGiB ?? null,
|
|
280
|
+
diskGiB: resource.diskGiB ?? null,
|
|
281
|
+
...(resource.capacityLeaseId
|
|
282
|
+
? { sandboxCapacityLeaseId: resource.capacityLeaseId }
|
|
283
|
+
: {}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function resourceKey(resource: SandboxExecutionResource): string {
|
|
288
|
+
return `${resource.provider}:${resource.resourceId}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function toProviderLifecycleEvent(event: PlayRunnerRuntimeLifecycleEvent) {
|
|
292
|
+
const type = event.type.replace('daytona_', '') as
|
|
293
|
+
| 'create_call_started'
|
|
294
|
+
| 'create_call_succeeded'
|
|
295
|
+
| 'create_call_failed';
|
|
296
|
+
return {
|
|
297
|
+
provider: 'daytona' as const,
|
|
298
|
+
type,
|
|
299
|
+
providerAttempt: event.providerAttempt,
|
|
300
|
+
occurredAtMs: event.occurredAtMs,
|
|
301
|
+
...(event.sandboxId ? { resourceId: event.sandboxId } : {}),
|
|
302
|
+
...(event.errorClass ? { errorClass: event.errorClass } : {}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SandboxAcquisition,
|
|
3
|
+
SandboxRunDependencies,
|
|
4
|
+
SandboxRunInput,
|
|
5
|
+
SandboxRunResource,
|
|
6
|
+
SandboxRunnerObservation,
|
|
7
|
+
} from './contract';
|
|
8
|
+
|
|
9
|
+
type RecordingEvent =
|
|
10
|
+
| { kind: 'acquire'; input: SandboxRunInput }
|
|
11
|
+
| { kind: 'record'; resource: SandboxRunResource }
|
|
12
|
+
| { kind: 'start'; resource: SandboxRunResource }
|
|
13
|
+
| { kind: 'observe'; resource: SandboxRunResource };
|
|
14
|
+
|
|
15
|
+
type RecordingPlan<Handle> = {
|
|
16
|
+
acquisition: SandboxAcquisition<Handle>;
|
|
17
|
+
record?: 'recorded' | 'fence_lost';
|
|
18
|
+
start?:
|
|
19
|
+
| { kind: 'command_accepted'; observation: SandboxRunnerObservation }
|
|
20
|
+
| { kind: 'not_started'; reason: string }
|
|
21
|
+
| { kind: 'start_unknown'; reason: string };
|
|
22
|
+
observe?:
|
|
23
|
+
| { kind: 'heartbeat_observed'; heartbeatAt: string }
|
|
24
|
+
| {
|
|
25
|
+
kind: 'terminal_observed';
|
|
26
|
+
terminal: 'completed' | 'failed' | 'cancelled';
|
|
27
|
+
}
|
|
28
|
+
| { kind: 'unconfirmed'; reason: string };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Test-only real recording ports. They model the Module Interface rather than
|
|
33
|
+
* spying on a runner-backend file or duplicating its implementation.
|
|
34
|
+
*/
|
|
35
|
+
export function createRecordingSandboxRunPorts<Handle>(
|
|
36
|
+
plan: RecordingPlan<Handle>,
|
|
37
|
+
): SandboxRunDependencies<Handle> & {
|
|
38
|
+
events(): readonly RecordingEvent[];
|
|
39
|
+
} {
|
|
40
|
+
const events: RecordingEvent[] = [];
|
|
41
|
+
return {
|
|
42
|
+
acquire: {
|
|
43
|
+
async acquire(input) {
|
|
44
|
+
events.push({ kind: 'acquire', input });
|
|
45
|
+
return plan.acquisition;
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
resourceFacts: {
|
|
49
|
+
async record(input) {
|
|
50
|
+
events.push({ kind: 'record', resource: input.resource });
|
|
51
|
+
return { kind: plan.record ?? 'recorded' };
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
runner: {
|
|
55
|
+
async start(input) {
|
|
56
|
+
events.push({ kind: 'start', resource: input.resource });
|
|
57
|
+
return (
|
|
58
|
+
plan.start ?? {
|
|
59
|
+
kind: 'command_accepted',
|
|
60
|
+
observation: {
|
|
61
|
+
boundaryId: `detached-runner:${input.attempt.runId}:${input.attempt.number}`,
|
|
62
|
+
baselineHeartbeatAt: null,
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
observer: {
|
|
69
|
+
async observe(input) {
|
|
70
|
+
events.push({ kind: 'observe', resource: input.resource });
|
|
71
|
+
return (
|
|
72
|
+
plan.observe ?? {
|
|
73
|
+
kind: 'heartbeat_observed',
|
|
74
|
+
heartbeatAt: '2026-08-26T00:00:01.000Z',
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
events() {
|
|
80
|
+
return events;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ToolExecuteResult } from '../tool-result';
|
|
2
|
+
import {
|
|
3
|
+
createToolCallJob,
|
|
4
|
+
type ToolCallDispatcher,
|
|
5
|
+
type ToolCallInput,
|
|
6
|
+
} from './index';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The authored facts for an operation that is deliberately not durable.
|
|
10
|
+
*
|
|
11
|
+
* There is no compatibility identity here by design. A provider adapter
|
|
12
|
+
* receives `providerOperationKey: null` and must therefore omit the
|
|
13
|
+
* provider-idempotency header. Repeating this call means repeating the
|
|
14
|
+
* physical provider operation, even when the two calls are otherwise equal.
|
|
15
|
+
*/
|
|
16
|
+
export type AlwaysFreshToolCallInput = Omit<
|
|
17
|
+
ToolCallInput,
|
|
18
|
+
'execution' | 'compatibility'
|
|
19
|
+
> & {
|
|
20
|
+
execution: Omit<ToolCallInput['execution'], 'persistence'>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type AlwaysFreshToolCall = {
|
|
24
|
+
call(input: AlwaysFreshToolCallInput): Promise<ToolExecuteResult>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Adapts the receiptless direct/map runtime paths onto ToolCall.
|
|
29
|
+
*
|
|
30
|
+
* This is not an in-memory receipt implementation. It selects ToolCall's
|
|
31
|
+
* explicit `always-fresh` protocol, which validates the same authored facts
|
|
32
|
+
* as a receipt-backed call while making no durable claim, recovery, heartbeat,
|
|
33
|
+
* completion, or provider-operation assertion.
|
|
34
|
+
*/
|
|
35
|
+
export function createAlwaysFreshToolCallAdapter(input: {
|
|
36
|
+
dispatcher: ToolCallDispatcher;
|
|
37
|
+
}): AlwaysFreshToolCall {
|
|
38
|
+
const toolCall = createToolCallJob({ dispatcher: input.dispatcher });
|
|
39
|
+
return {
|
|
40
|
+
async call(call) {
|
|
41
|
+
return await toolCall.call({
|
|
42
|
+
...call,
|
|
43
|
+
execution: {
|
|
44
|
+
...call.execution,
|
|
45
|
+
persistence: 'always-fresh',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ToolExecuteResult } from '../tool-result';
|
|
2
|
+
|
|
3
|
+
/** The response transformation requested by the authored Play artifact. */
|
|
4
|
+
export type ToolCallResultFormat = 'legacy' | 'v2' | 'raw-v2';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transitional identities supplied by the existing runtime.
|
|
8
|
+
*
|
|
9
|
+
* The result receipt and provider-operation identities deliberately remain
|
|
10
|
+
* caller-owned until the new Integration Operation store has replaced the
|
|
11
|
+
* historical cache/fence records. ToolCall must pass them through unchanged:
|
|
12
|
+
* deriving replacement keys here would strand completed work or redispatch a
|
|
13
|
+
* provider operation during the rolling migration.
|
|
14
|
+
*/
|
|
15
|
+
export type ToolCallCompatibilityIdentity = {
|
|
16
|
+
resultReceiptKey: string;
|
|
17
|
+
providerOperationKey: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Whether this call has a durable Tool Result Receipt. An always-fresh call
|
|
22
|
+
* is intentionally not a forced variant of a cached call: it has neither a
|
|
23
|
+
* receipt to replay nor a provider-operation identity to send downstream.
|
|
24
|
+
*/
|
|
25
|
+
export type ToolCallPersistence = 'receipt' | 'always-fresh';
|
|
26
|
+
|
|
27
|
+
/** Facts from the authored call; neither headers nor credentials cross here. */
|
|
28
|
+
export type ToolCallInput = {
|
|
29
|
+
scope: {
|
|
30
|
+
organizationId: string;
|
|
31
|
+
/** The play-local execution/cache boundary selected by the program. */
|
|
32
|
+
playScope: string;
|
|
33
|
+
};
|
|
34
|
+
tool: {
|
|
35
|
+
id: string;
|
|
36
|
+
providerActionVersion: string;
|
|
37
|
+
};
|
|
38
|
+
payload: Record<string, unknown>;
|
|
39
|
+
authorization?: {
|
|
40
|
+
/** Opaque digest; credential material never enters this Module. */
|
|
41
|
+
scopeDigest?: string | null;
|
|
42
|
+
};
|
|
43
|
+
output: {
|
|
44
|
+
/** Dataset/raw/row-artifact distinguish provider-operation intent. */
|
|
45
|
+
intent: string;
|
|
46
|
+
format: ToolCallResultFormat;
|
|
47
|
+
revision?: string | null;
|
|
48
|
+
};
|
|
49
|
+
execution: {
|
|
50
|
+
/** The dispatcher Adapter owns the direct/map implementation difference. */
|
|
51
|
+
mode: 'direct' | 'map';
|
|
52
|
+
ownerRunId: string;
|
|
53
|
+
force?: boolean;
|
|
54
|
+
/** Defaults to a durable result receipt for compatibility callers. */
|
|
55
|
+
persistence?: ToolCallPersistence;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Existing durable identities. They must be absent for always-fresh calls:
|
|
59
|
+
* inventing a local key would silently turn a receiptless operation into a
|
|
60
|
+
* provider-idempotent one.
|
|
61
|
+
*/
|
|
62
|
+
compatibility?: ToolCallCompatibilityIdentity;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type ToolCallReceiptLease = {
|
|
66
|
+
leaseId: string;
|
|
67
|
+
ownerRunId: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Lets an Adapter preserve the runtime's cached versus in-flight result ABI. */
|
|
71
|
+
export type ToolCallReceiptRecoverySource = 'cache' | 'in_flight';
|
|
72
|
+
|
|
73
|
+
/** Durable Tool Result Receipt lifecycle, independent of a storage engine. */
|
|
74
|
+
export type ToolResultReceipts = {
|
|
75
|
+
claim(input: {
|
|
76
|
+
resultReceiptKey: string;
|
|
77
|
+
ownerRunId: string;
|
|
78
|
+
force: boolean;
|
|
79
|
+
}): Promise<
|
|
80
|
+
| { kind: 'completed'; result: ToolExecuteResult }
|
|
81
|
+
| { kind: 'owned'; lease: ToolCallReceiptLease }
|
|
82
|
+
| { kind: 'following' }
|
|
83
|
+
>;
|
|
84
|
+
recover(input: {
|
|
85
|
+
resultReceiptKey: string;
|
|
86
|
+
ownerRunId: string;
|
|
87
|
+
source: ToolCallReceiptRecoverySource;
|
|
88
|
+
}): Promise<ToolExecuteResult>;
|
|
89
|
+
heartbeat(input: {
|
|
90
|
+
resultReceiptKey: string;
|
|
91
|
+
lease: ToolCallReceiptLease;
|
|
92
|
+
}): Promise<'active' | 'completed' | 'lost'>;
|
|
93
|
+
complete(input: {
|
|
94
|
+
resultReceiptKey: string;
|
|
95
|
+
lease: ToolCallReceiptLease;
|
|
96
|
+
result: ToolExecuteResult;
|
|
97
|
+
}): Promise<
|
|
98
|
+
| { kind: 'stored'; result: ToolExecuteResult }
|
|
99
|
+
| { kind: 'existing'; result: ToolExecuteResult }
|
|
100
|
+
>;
|
|
101
|
+
fail(input: {
|
|
102
|
+
resultReceiptKey: string;
|
|
103
|
+
lease: ToolCallReceiptLease;
|
|
104
|
+
error: unknown;
|
|
105
|
+
}): Promise<void>;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Direct and map execution are Adapter choices, never separate public jobs. */
|
|
109
|
+
export type ToolCallDispatcher = {
|
|
110
|
+
dispatch(input: {
|
|
111
|
+
call: ToolCallInput;
|
|
112
|
+
/** Null means the adapter must not emit a provider-idempotency identity. */
|
|
113
|
+
providerOperationKey: string | null;
|
|
114
|
+
/** Null means this is a receiptless, always-fresh physical call. */
|
|
115
|
+
receipt: ToolCallReceiptLease | null;
|
|
116
|
+
}): Promise<ToolExecuteResult>;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type ToolCallDependencies = {
|
|
120
|
+
/** Required only by the durable-receipt branch. */
|
|
121
|
+
resultReceipts?: ToolResultReceipts;
|
|
122
|
+
dispatcher: ToolCallDispatcher;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** The intentionally small public Interface of the Tool Call Module. */
|
|
126
|
+
export type ToolCallJob = {
|
|
127
|
+
call(input: ToolCallInput): Promise<ToolExecuteResult>;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export class ToolCallReceiptLeaseLostError extends Error {
|
|
131
|
+
constructor(resultReceiptKey: string) {
|
|
132
|
+
super(`Lost Tool Result Receipt lease for ${resultReceiptKey}.`);
|
|
133
|
+
this.name = 'ToolCallReceiptLeaseLostError';
|
|
134
|
+
}
|
|
135
|
+
}
|