deepline 0.1.181 → 0.1.183

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.
Files changed (70) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/child-play-await.ts +203 -6
  2. package/dist/bundling-sources/apps/play-runner-workers/src/child-play-submit.ts +8 -1
  3. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +633 -107
  4. package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +3 -2
  5. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +1932 -1838
  6. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +116 -0
  7. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +542 -78
  8. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +124 -12
  9. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +292 -11
  10. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +519 -0
  11. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +2381 -0
  12. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +18 -5
  13. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +130 -0
  14. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +39 -0
  15. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +2 -0
  16. package/dist/bundling-sources/sdk/src/client.ts +48 -5
  17. package/dist/bundling-sources/sdk/src/http.ts +23 -8
  18. package/dist/bundling-sources/sdk/src/play.ts +200 -26
  19. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +56 -3
  20. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  21. package/dist/bundling-sources/sdk/src/types.ts +2 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +132 -10
  23. package/dist/bundling-sources/shared_libs/play-runtime/auth-scope-resolver.ts +92 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +4 -4
  25. package/dist/bundling-sources/shared_libs/play-runtime/batching-types.ts +8 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +101 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1857 -314
  28. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +106 -6
  29. package/dist/bundling-sources/shared_libs/play-runtime/dedup-backend.ts +0 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +1 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +4 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +116 -16
  33. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +186 -20
  34. package/dist/bundling-sources/shared_libs/play-runtime/dynamic-worker-version.ts +2 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +133 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +245 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -5
  38. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +14 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +52 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +41 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +3 -2
  42. package/dist/bundling-sources/shared_libs/play-runtime/query-result-dataset.ts +120 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +124 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +6 -2
  45. package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +48 -2
  46. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +158 -15
  47. package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +45 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +9 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2037 -262
  50. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +42 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +183 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +88 -0
  53. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +8 -1
  54. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +103 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +19 -1
  56. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +343 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +43 -0
  58. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
  59. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +139 -17
  60. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +437 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +83 -6
  62. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +24 -17
  63. package/dist/cli/index.js +85 -23
  64. package/dist/cli/index.mjs +85 -23
  65. package/dist/index.d.mts +4 -1
  66. package/dist/index.d.ts +4 -1
  67. package/dist/index.js +274 -35
  68. package/dist/index.mjs +274 -35
  69. package/dist/plays/bundle-play-file.mjs +2 -2
  70. package/package.json +1 -1
@@ -6,11 +6,13 @@ import {
6
6
  export type WorkerToolReceiptSelection = {
7
7
  claimableReceiptKey: string | null;
8
8
  forceDurableRefresh: boolean;
9
+ forceFailedDurableRefresh: boolean;
9
10
  };
10
11
 
11
12
  export type WorkerToolReceiptGroup<TRequest> = {
12
13
  claimableReceiptKey: string;
13
14
  forceDurableRefresh: boolean;
15
+ forceFailedDurableRefresh: boolean;
14
16
  requests: TRequest[];
15
17
  };
16
18
 
@@ -155,6 +157,7 @@ export function selectWorkerToolReceipt(input: {
155
157
  durableReceiptKey?: string | null;
156
158
  localRetryCacheKey: string;
157
159
  force: boolean;
160
+ forceFailed?: boolean;
158
161
  allowLocalRetryReceipts: boolean;
159
162
  }): WorkerToolReceiptSelection {
160
163
  const durableReceiptKey = input.durableReceiptKey?.trim() || null;
@@ -165,6 +168,10 @@ export function selectWorkerToolReceipt(input: {
165
168
  return {
166
169
  claimableReceiptKey: durableReceiptKey ?? localRetryReceiptKey,
167
170
  forceDurableRefresh: input.force === true && durableReceiptKey !== null,
171
+ forceFailedDurableRefresh:
172
+ input.force !== true &&
173
+ input.forceFailed === true &&
174
+ durableReceiptKey !== null,
168
175
  };
169
176
  }
170
177
 
@@ -176,17 +183,21 @@ export function planWorkerToolReceiptGroups<TRequest>(
176
183
  durableReceiptKey?: string | null;
177
184
  localRetryCacheKey: string;
178
185
  force: boolean;
186
+ forceFailed?: boolean;
179
187
  };
180
188
  },
181
189
  ): WorkerToolReceiptGroupPlan<TRequest> {
182
190
  const localRequests: TRequest[] = [];
183
191
  const groupsByKey = new Map<string, WorkerToolReceiptGroup<TRequest>>();
184
192
  for (const request of requests) {
185
- const { claimableReceiptKey, forceDurableRefresh } =
186
- selectWorkerToolReceipt({
187
- ...input.getReceiptInput(request),
188
- allowLocalRetryReceipts: input.allowLocalRetryReceipts,
189
- });
193
+ const {
194
+ claimableReceiptKey,
195
+ forceDurableRefresh,
196
+ forceFailedDurableRefresh,
197
+ } = selectWorkerToolReceipt({
198
+ ...input.getReceiptInput(request),
199
+ allowLocalRetryReceipts: input.allowLocalRetryReceipts,
200
+ });
190
201
  if (!claimableReceiptKey) {
191
202
  localRequests.push(request);
192
203
  continue;
@@ -194,10 +205,12 @@ export function planWorkerToolReceiptGroups<TRequest>(
194
205
  const group = groupsByKey.get(claimableReceiptKey) ?? {
195
206
  claimableReceiptKey,
196
207
  forceDurableRefresh: false,
208
+ forceFailedDurableRefresh: false,
197
209
  requests: [],
198
210
  };
199
211
  group.requests.push(request);
200
212
  group.forceDurableRefresh ||= forceDurableRefresh;
213
+ group.forceFailedDurableRefresh ||= forceFailedDurableRefresh;
201
214
  groupsByKey.set(claimableReceiptKey, group);
202
215
  }
203
216
  return {
@@ -0,0 +1,130 @@
1
+ import { WORKER_PLATFORM_YIELD_LIMITS } from './worker-platform-budget';
2
+
3
+ export type WorkerWorkBudgetEventKind =
4
+ | 'tool'
5
+ | 'provider'
6
+ | 'receipt'
7
+ | 'heartbeat'
8
+ | 'sheet'
9
+ | 'log'
10
+ | 'egress';
11
+
12
+ export type WorkerWorkBudgetSnapshot = {
13
+ startedAt: number;
14
+ updatedAt: number;
15
+ elapsedMs: number;
16
+ counts: Record<WorkerWorkBudgetEventKind, number>;
17
+ elapsedMsByKind: Record<WorkerWorkBudgetEventKind, number>;
18
+ };
19
+
20
+ export type WorkerWorkBudgetYieldReason =
21
+ | 'elapsed'
22
+ | 'subrequest'
23
+ | 'provider'
24
+ | 'tool'
25
+ | 'receipt'
26
+ | 'heartbeat'
27
+ | 'sheet'
28
+ | 'log'
29
+ | 'egress';
30
+
31
+ export type WorkerWorkBudgetYieldDecision = {
32
+ shouldYield: boolean;
33
+ reason: WorkerWorkBudgetYieldReason | null;
34
+ snapshot: WorkerWorkBudgetSnapshot;
35
+ };
36
+
37
+ export type WorkerWorkBudgetYieldLimits = Partial<
38
+ Record<WorkerWorkBudgetYieldReason, number>
39
+ >;
40
+
41
+ export type WorkerWorkBudgetMeter = {
42
+ count(kind: WorkerWorkBudgetEventKind, amount?: number): void;
43
+ snapshot(): WorkerWorkBudgetSnapshot;
44
+ resetWindow(): void;
45
+ };
46
+
47
+ const WORK_BUDGET_EVENT_KINDS: WorkerWorkBudgetEventKind[] = [
48
+ 'tool',
49
+ 'provider',
50
+ 'receipt',
51
+ 'heartbeat',
52
+ 'sheet',
53
+ 'log',
54
+ 'egress',
55
+ ];
56
+
57
+ function zeroCounters(): Record<WorkerWorkBudgetEventKind, number> {
58
+ return Object.fromEntries(
59
+ WORK_BUDGET_EVENT_KINDS.map((kind) => [kind, 0]),
60
+ ) as Record<WorkerWorkBudgetEventKind, number>;
61
+ }
62
+
63
+ export function createWorkerWorkBudgetMeter(input?: {
64
+ nowMs?: () => number;
65
+ }): WorkerWorkBudgetMeter {
66
+ const nowMs = input?.nowMs ?? Date.now;
67
+ let startedAt = nowMs();
68
+ let updatedAt = startedAt;
69
+ let counts = zeroCounters();
70
+ let elapsedMsByKind = zeroCounters();
71
+
72
+ return {
73
+ count(kind, amount = 1) {
74
+ counts[kind] += amount;
75
+ updatedAt = nowMs();
76
+ },
77
+ snapshot() {
78
+ const now = nowMs();
79
+ return {
80
+ startedAt,
81
+ updatedAt,
82
+ elapsedMs: Math.max(0, now - startedAt),
83
+ counts: { ...counts },
84
+ elapsedMsByKind: { ...elapsedMsByKind },
85
+ };
86
+ },
87
+ resetWindow() {
88
+ startedAt = nowMs();
89
+ updatedAt = startedAt;
90
+ counts = zeroCounters();
91
+ elapsedMsByKind = zeroCounters();
92
+ },
93
+ };
94
+ }
95
+
96
+ export const WORKER_WORK_BUDGET_YIELD_LIMITS = WORKER_PLATFORM_YIELD_LIMITS;
97
+
98
+ export function shouldYieldWorkerWorkBudget(input: {
99
+ meter: WorkerWorkBudgetMeter;
100
+ limits?: WorkerWorkBudgetYieldLimits;
101
+ projectedSubrequests?: number;
102
+ }): WorkerWorkBudgetYieldDecision {
103
+ const snapshot = input.meter.snapshot();
104
+ const limits = {
105
+ ...WORKER_WORK_BUDGET_YIELD_LIMITS,
106
+ ...(input.limits ?? {}),
107
+ };
108
+ if (snapshot.elapsedMs >= limits.elapsed) {
109
+ return { shouldYield: true, reason: 'elapsed', snapshot };
110
+ }
111
+ const subrequests =
112
+ snapshot.counts.provider +
113
+ snapshot.counts.receipt +
114
+ snapshot.counts.heartbeat +
115
+ snapshot.counts.sheet +
116
+ snapshot.counts.egress;
117
+ const projectedSubrequests = Math.max(
118
+ 0,
119
+ Math.floor(input.projectedSubrequests ?? 0),
120
+ );
121
+ if (subrequests + projectedSubrequests >= limits.subrequest) {
122
+ return { shouldYield: true, reason: 'subrequest', snapshot };
123
+ }
124
+ for (const kind of WORK_BUDGET_EVENT_KINDS) {
125
+ if (snapshot.counts[kind] >= limits[kind]) {
126
+ return { shouldYield: true, reason: kind, snapshot };
127
+ }
128
+ }
129
+ return { shouldYield: false, reason: null, snapshot };
130
+ }
@@ -0,0 +1,39 @@
1
+ import type { WorkerWorkBudgetYieldLimits } from './work-budget';
2
+
3
+ // Preview accounts can still enforce the free/internal-service subrequest
4
+ // ceiling even when wrangler config asks for more. This is a planner budget,
5
+ // not the advertised platform ceiling: unbatched tool chunks also spend
6
+ // subrequests on sheet writes, progress/ledger flushes, service-binding hops,
7
+ // and workflow replay around the visible provider calls. Individual log lines
8
+ // are buffered and batched into those flushes; they are not one subrequest each.
9
+ // Keep real headroom here;
10
+ // paid deployments may raise it only once the active account limit is proven by
11
+ // an E2E run, not by config alone.
12
+ export const WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT = 700;
13
+
14
+ // Fresh unbatched tool calls spend more than the visible provider RPC:
15
+ // durable receipt claim/complete, provider execution, route/service-binding
16
+ // hops, and row progress/sheet writes all share the Worker subrequest pool.
17
+ export const WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL = 8;
18
+
19
+ export const WORKER_PLATFORM_YIELD_LIMITS: Required<WorkerWorkBudgetYieldLimits> =
20
+ {
21
+ elapsed: 45_000,
22
+ subrequest: WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT,
23
+ provider: WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT,
24
+ tool: 1_500,
25
+ receipt: 2_000,
26
+ heartbeat: 1_000,
27
+ sheet: 800,
28
+ log: 1_500,
29
+ egress: 1_000,
30
+ };
31
+
32
+ export function estimateUnbatchedToolCallSubrequests(input: {
33
+ toolCalls: number;
34
+ }): number {
35
+ return (
36
+ Math.max(0, Math.floor(input.toolCalls)) *
37
+ WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL
38
+ );
39
+ }
@@ -27,6 +27,7 @@ export type WorkflowRetryParamsRef = {
27
27
 
28
28
  export type WorkflowRetryPlayParams = {
29
29
  runId: string;
30
+ runAttempt?: number;
30
31
  playId: string;
31
32
  playName: string;
32
33
  artifactStorageKey: string;
@@ -72,6 +73,7 @@ export type WorkflowRetryPlayParams = {
72
73
  totalRows?: number;
73
74
  coordinatorUrl?: string | null;
74
75
  coordinatorInternalToken?: string | null;
76
+ runtimeTestFaultHeader?: string | null;
75
77
  };
76
78
 
77
79
  export type WorkflowRetryStatePayload<TParams = WorkflowRetryPlayParams> =
@@ -109,6 +109,41 @@ function resolvePlayRunIntegrationMode(
109
109
  );
110
110
  }
111
111
 
112
+ function normalizeTestPolicyOverrides(
113
+ value: unknown,
114
+ source: string,
115
+ ): Record<string, unknown> {
116
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
117
+ return value as Record<string, unknown>;
118
+ }
119
+ throw new DeeplineError(
120
+ `${source} must be a JSON object.`,
121
+ undefined,
122
+ 'INVALID_TEST_POLICY_OVERRIDES',
123
+ );
124
+ }
125
+
126
+ function parseEnvTestPolicyOverrides(): Record<string, unknown> | undefined {
127
+ const raw =
128
+ typeof process !== 'undefined'
129
+ ? process.env?.DEEPLINE_TEST_POLICY_OVERRIDES
130
+ : undefined;
131
+ const trimmed = raw?.trim();
132
+ if (!trimmed) return undefined;
133
+ let parsed: unknown;
134
+ try {
135
+ parsed = JSON.parse(trimmed);
136
+ } catch (error) {
137
+ const detail = error instanceof Error ? ` ${error.message}` : '';
138
+ throw new DeeplineError(
139
+ `DEEPLINE_TEST_POLICY_OVERRIDES must be valid JSON.${detail}`,
140
+ undefined,
141
+ 'INVALID_TEST_POLICY_OVERRIDES',
142
+ );
143
+ }
144
+ return normalizeTestPolicyOverrides(parsed, 'DEEPLINE_TEST_POLICY_OVERRIDES');
145
+ }
146
+
112
147
  function sleep(ms: number): Promise<void> {
113
148
  return new Promise((resolve) => setTimeout(resolve, ms));
114
149
  }
@@ -202,7 +237,8 @@ function chunkRegisterPlayArtifacts<T>(artifacts: T[]): T[][] {
202
237
 
203
238
  type ExecuteToolRawOptions = {
204
239
  includeToolMetadata?: boolean;
205
- responseIntent?: 'raw' | 'row_artifact';
240
+ responseIntent?: 'dataset' | 'raw' | 'row_artifact';
241
+ metadata?: Record<string, unknown>;
206
242
  timeout?: number;
207
243
  maxRetries?: number;
208
244
  };
@@ -1310,13 +1346,14 @@ export class DeeplineClient {
1310
1346
  ...(options?.includeToolMetadata
1311
1347
  ? { [INCLUDE_TOOL_METADATA_HEADER]: 'true' }
1312
1348
  : {}),
1313
- ...(options?.responseIntent
1314
- ? { [EXECUTE_RESPONSE_INTENT_HEADER]: options.responseIntent }
1315
- : {}),
1349
+ [EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? 'raw',
1316
1350
  };
1317
1351
  return this.http.post<ToolExecution<TData, TMeta>>(
1318
1352
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
1319
- { payload: input },
1353
+ {
1354
+ payload: input,
1355
+ ...(options?.metadata ? { metadata: options.metadata } : {}),
1356
+ },
1320
1357
  headers,
1321
1358
  {
1322
1359
  forbiddenAsApiError: true,
@@ -1424,6 +1461,8 @@ export class DeeplineClient {
1424
1461
  */
1425
1462
  async startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart> {
1426
1463
  const integrationMode = resolvePlayRunIntegrationMode(request);
1464
+ const testPolicyOverrides =
1465
+ request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
1427
1466
  const forceToolRefresh = request.forceToolRefresh === true;
1428
1467
  const response = await this.http.post<Record<string, unknown>>(
1429
1468
  '/api/v2/plays/run',
@@ -1468,6 +1507,7 @@ export class DeeplineClient {
1468
1507
  // different profile pass `request.profile` explicitly.
1469
1508
  ...(request.profile ? { profile: request.profile } : {}),
1470
1509
  ...(integrationMode ? { integrationMode } : {}),
1510
+ ...(testPolicyOverrides ? { testPolicyOverrides } : {}),
1471
1511
  },
1472
1512
  );
1473
1513
  return normalizePlayRunStart(response);
@@ -1489,6 +1529,8 @@ export class DeeplineClient {
1489
1529
  options?: { signal?: AbortSignal },
1490
1530
  ): AsyncGenerator<PlayLiveEvent> {
1491
1531
  const integrationMode = resolvePlayRunIntegrationMode(request);
1532
+ const testPolicyOverrides =
1533
+ request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
1492
1534
  const forceToolRefresh = request.forceToolRefresh === true;
1493
1535
  const body = {
1494
1536
  ...(request.name ? { name: request.name } : {}),
@@ -1528,6 +1570,7 @@ export class DeeplineClient {
1528
1570
  : {}),
1529
1571
  ...(request.profile ? { profile: request.profile } : {}),
1530
1572
  ...(integrationMode ? { integrationMode } : {}),
1573
+ ...(testPolicyOverrides ? { testPolicyOverrides } : {}),
1531
1574
  };
1532
1575
  for await (const event of this.http.streamSse<PlayLiveEvent>(
1533
1576
  '/api/v2/plays/run?stream=true',
@@ -34,6 +34,7 @@ import {
34
34
  SYNTHETIC_RUN_HEADER,
35
35
  WORKER_CALLBACK_URL_OVERRIDE_HEADER,
36
36
  } from '../../shared_libs/play-runtime/coordinator-headers.js';
37
+ import { PLAY_RUNTIME_TEST_FAULT_HEADER } from '../../shared_libs/play-runtime/test-runtime-seams.js';
37
38
 
38
39
  const MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
39
40
  const COWORK_NETWORK_HINT =
@@ -240,16 +241,12 @@ export class HttpClient {
240
241
  typeof process !== 'undefined'
241
242
  ? process.env?.DEEPLINE_COORDINATOR_URL
242
243
  : undefined;
244
+ const coordinatorInternalToken =
245
+ typeof process !== 'undefined'
246
+ ? process.env?.DEEPLINE_INTERNAL_TOKEN
247
+ : undefined;
243
248
  if (coordinatorUrl?.trim()) {
244
249
  headers[COORDINATOR_URL_OVERRIDE_HEADER] = coordinatorUrl.trim();
245
- const coordinatorInternalToken =
246
- typeof process !== 'undefined'
247
- ? process.env?.DEEPLINE_INTERNAL_TOKEN
248
- : undefined;
249
- if (coordinatorInternalToken?.trim()) {
250
- headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
251
- coordinatorInternalToken.trim();
252
- }
253
250
  }
254
251
  const workerCallbackUrl =
255
252
  typeof process !== 'undefined'
@@ -276,6 +273,24 @@ export class HttpClient {
276
273
  if (syntheticRun && syntheticRun.trim() && syntheticRun.trim() !== '0') {
277
274
  headers[SYNTHETIC_RUN_HEADER] = '1';
278
275
  }
276
+ // Test-only runtime seam. The server validates both environment and value
277
+ // before honoring this header.
278
+ const runtimeTestFault =
279
+ typeof process !== 'undefined'
280
+ ? process.env?.DEEPLINE_TEST_RUNTIME_FAULT
281
+ : undefined;
282
+ if (runtimeTestFault?.trim()) {
283
+ headers[PLAY_RUNTIME_TEST_FAULT_HEADER] = runtimeTestFault.trim();
284
+ }
285
+ if (
286
+ coordinatorInternalToken?.trim() &&
287
+ (coordinatorUrl?.trim() ||
288
+ workerCallbackUrl?.trim() ||
289
+ runtimeTestFault?.trim())
290
+ ) {
291
+ headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
292
+ coordinatorInternalToken.trim();
293
+ }
279
294
  return headers;
280
295
  }
281
296
 
@@ -74,11 +74,20 @@
74
74
  */
75
75
  import { DeeplineClient } from './client.js';
76
76
  import { DeeplineError } from './errors.js';
77
- import { createToolExecuteResult } from '../../shared_libs/play-runtime/tool-result.js';
77
+ import {
78
+ attachToolResultListDataset,
79
+ createToolExecuteResult,
80
+ } from '../../shared_libs/play-runtime/tool-result.js';
78
81
  export {
79
82
  readValue,
80
83
  readList,
81
84
  } from '../../shared_libs/play-runtime/tool-result.js';
85
+ import { createDeferredPlayDataset } from '../../shared_libs/plays/dataset.js';
86
+ import {
87
+ QUERY_RESULT_DATASET_PAGE_SIZE,
88
+ isCustomerDbDatasetTool,
89
+ isQueryResultDatasetTool,
90
+ } from '../../shared_libs/play-runtime/query-result-dataset.js';
82
91
  import type {
83
92
  PlayDataset,
84
93
  PlayDatasetInput,
@@ -1473,8 +1482,12 @@ export class DeeplineContext {
1473
1482
  ): Promise<ToolExecuteResult> => {
1474
1483
  const response = await this.client.executeTool(toolId, input, {
1475
1484
  includeToolMetadata: true,
1485
+ responseIntent: 'dataset',
1486
+ });
1487
+ return toolExecutionEnvelopeToResult(toolId, response, {
1488
+ client: this.client,
1489
+ input,
1476
1490
  });
1477
- return toolExecutionEnvelopeToResult(toolId, response);
1478
1491
  },
1479
1492
  };
1480
1493
  }
@@ -1724,9 +1737,163 @@ function extractorDescriptorRecord(
1724
1737
  );
1725
1738
  }
1726
1739
 
1740
+ function rowsFromUnknown(value: unknown): Record<string, unknown>[] {
1741
+ if (!Array.isArray(value)) return [];
1742
+ return value.map((row) => (isRecord(row) ? row : { value: row }));
1743
+ }
1744
+
1745
+ function finiteNonNegativeInteger(value: unknown): number | null {
1746
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
1747
+ return null;
1748
+ }
1749
+ return Math.floor(value);
1750
+ }
1751
+
1752
+ function finitePositiveInteger(value: unknown): number | null {
1753
+ const integer = finiteNonNegativeInteger(value);
1754
+ return integer !== null && integer > 0 ? integer : null;
1755
+ }
1756
+
1757
+ function stableHash(value: string): string {
1758
+ let hash = 0;
1759
+ for (let index = 0; index < value.length; index += 1) {
1760
+ hash = Math.imul(31, hash) + value.charCodeAt(index);
1761
+ hash |= 0;
1762
+ }
1763
+ return Math.abs(hash).toString(36);
1764
+ }
1765
+
1766
+ function attachSdkQueryResultDatasetResult(
1767
+ toolId: string,
1768
+ result: ToolExecuteResult,
1769
+ options?: {
1770
+ client: DeeplineClient;
1771
+ input: Record<string, unknown>;
1772
+ },
1773
+ ): ToolExecuteResult {
1774
+ if (!options || !isQueryResultDatasetTool(toolId)) return result;
1775
+ const raw = isRecord(result.toolResponse.raw)
1776
+ ? result.toolResponse.raw
1777
+ : null;
1778
+ const dataset = isRecord(raw?.dataset) ? raw.dataset : null;
1779
+ const totalRows = finiteNonNegativeInteger(dataset?.total_rows);
1780
+ const sql =
1781
+ typeof options.input.sql === 'string'
1782
+ ? options.input.sql
1783
+ : typeof options.input.query === 'string'
1784
+ ? options.input.query
1785
+ : typeof raw?.sql === 'string'
1786
+ ? raw.sql
1787
+ : null;
1788
+ if (!dataset || totalRows === null || !sql) {
1789
+ return result;
1790
+ }
1791
+ const datasetLimit =
1792
+ finitePositiveInteger(dataset.returned_limit) ?? totalRows;
1793
+ const previewRows = rowsFromUnknown(raw?.rows).slice(0, 25);
1794
+ const fetchPage = async (
1795
+ offset: number,
1796
+ limit: number,
1797
+ ): Promise<Record<string, unknown>[]> => {
1798
+ if (limit <= 0 || offset >= totalRows) return [];
1799
+ const response = await options.client.executeTool(toolId, options.input, {
1800
+ includeToolMetadata: true,
1801
+ responseIntent: 'dataset',
1802
+ metadata: {
1803
+ query_result_dataset: {
1804
+ limit: datasetLimit,
1805
+ offset,
1806
+ page_size: Math.min(limit, QUERY_RESULT_DATASET_PAGE_SIZE),
1807
+ total_rows: totalRows,
1808
+ },
1809
+ ...(isCustomerDbDatasetTool(toolId)
1810
+ ? {
1811
+ customer_db_dataset: {
1812
+ limit: datasetLimit,
1813
+ offset,
1814
+ page_size: Math.min(limit, QUERY_RESULT_DATASET_PAGE_SIZE),
1815
+ total_rows: totalRows,
1816
+ },
1817
+ }
1818
+ : {}),
1819
+ },
1820
+ });
1821
+ const pageRaw = isRecord(response.toolResponse?.raw)
1822
+ ? response.toolResponse.raw
1823
+ : null;
1824
+ return rowsFromUnknown(pageRaw?.rows);
1825
+ };
1826
+ const collectRows = async (
1827
+ limit: number | undefined,
1828
+ ): Promise<Record<string, unknown>[]> => {
1829
+ const target = Math.min(limit ?? totalRows, totalRows, datasetLimit);
1830
+ const collected: Record<string, unknown>[] = [];
1831
+ for (
1832
+ let offset = 0;
1833
+ offset < target;
1834
+ offset += QUERY_RESULT_DATASET_PAGE_SIZE
1835
+ ) {
1836
+ collected.push(
1837
+ ...(await fetchPage(
1838
+ offset,
1839
+ Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, target - offset),
1840
+ )),
1841
+ );
1842
+ }
1843
+ return collected.slice(0, target);
1844
+ };
1845
+ const executionNonce =
1846
+ typeof result.job_id === 'string' && result.job_id.trim()
1847
+ ? result.job_id.trim()
1848
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
1849
+ const safeNonce = executionNonce
1850
+ .replace(/[^A-Za-z0-9_.:-]/g, '_')
1851
+ .slice(0, 64);
1852
+ const datasetScope = `${options.client.baseUrl}:${toolId}:${sql}:${datasetLimit}`;
1853
+ const playDataset = createDeferredPlayDataset({
1854
+ datasetKind: 'csv',
1855
+ datasetId: `sdk-tool-list:${toolId}:${stableHash(datasetScope)}:${datasetLimit}:${safeNonce}`,
1856
+ count: Math.min(totalRows, datasetLimit),
1857
+ previewRows,
1858
+ sourceLabel: 'query result rows',
1859
+ tableNamespace: null,
1860
+ resolvers: {
1861
+ count: async () => Math.min(totalRows, datasetLimit),
1862
+ peek: async (limit) => collectRows(limit),
1863
+ materialize: async (limit) => collectRows(limit),
1864
+ iterate: () =>
1865
+ ({
1866
+ async *[Symbol.asyncIterator]() {
1867
+ const count = Math.min(totalRows, datasetLimit);
1868
+ for (
1869
+ let offset = 0;
1870
+ offset < count;
1871
+ offset += QUERY_RESULT_DATASET_PAGE_SIZE
1872
+ ) {
1873
+ yield* await fetchPage(
1874
+ offset,
1875
+ Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, count - offset),
1876
+ );
1877
+ }
1878
+ },
1879
+ }) as AsyncIterable<Record<string, unknown>>,
1880
+ },
1881
+ });
1882
+ return attachToolResultListDataset(result, {
1883
+ name: 'rows',
1884
+ path: 'toolResponse.raw.rows',
1885
+ dataset: playDataset,
1886
+ count: Math.min(totalRows, datasetLimit),
1887
+ });
1888
+ }
1889
+
1727
1890
  function toolExecutionEnvelopeToResult(
1728
1891
  fallbackToolId: string,
1729
1892
  response: ToolExecution,
1893
+ options?: {
1894
+ client: DeeplineClient;
1895
+ input: Record<string, unknown>;
1896
+ },
1730
1897
  ): ToolExecuteResult {
1731
1898
  const raw = response.toolResponse?.raw ?? null;
1732
1899
  const meta = response.toolResponse?.meta;
@@ -1735,30 +1902,37 @@ function toolExecutionEnvelopeToResult(
1735
1902
  : null;
1736
1903
  const toolMetadata = isRecord(metadata) ? metadata : {};
1737
1904
 
1738
- return createToolExecuteResult({
1739
- status: typeof response.status === 'string' ? response.status : 'completed',
1740
- jobId: typeof response.job_id === 'string' ? response.job_id : undefined,
1741
- result: {
1742
- data: raw,
1743
- ...(isRecord(meta) ? { meta } : {}),
1744
- },
1745
- metadata: {
1746
- toolId:
1747
- typeof toolMetadata.toolId === 'string'
1748
- ? toolMetadata.toolId
1749
- : fallbackToolId,
1750
- extractors: extractorDescriptorRecord(toolMetadata.extractors),
1751
- targetGetters: stringArrayRecord(toolMetadata.targetGetters),
1752
- listExtractorPaths: stringArray(toolMetadata.listExtractorPaths),
1753
- listIdentityGetters: stringArrayRecord(toolMetadata.listIdentityGetters),
1754
- },
1755
- execution: {
1756
- idempotent: true,
1757
- cached: false,
1758
- source: 'live',
1759
- },
1760
- meta: isRecord(response.meta) ? response.meta : undefined,
1761
- });
1905
+ return attachSdkQueryResultDatasetResult(
1906
+ fallbackToolId,
1907
+ createToolExecuteResult({
1908
+ status:
1909
+ typeof response.status === 'string' ? response.status : 'completed',
1910
+ jobId: typeof response.job_id === 'string' ? response.job_id : undefined,
1911
+ result: {
1912
+ data: raw,
1913
+ ...(isRecord(meta) ? { meta } : {}),
1914
+ },
1915
+ metadata: {
1916
+ toolId:
1917
+ typeof toolMetadata.toolId === 'string'
1918
+ ? toolMetadata.toolId
1919
+ : fallbackToolId,
1920
+ extractors: extractorDescriptorRecord(toolMetadata.extractors),
1921
+ targetGetters: stringArrayRecord(toolMetadata.targetGetters),
1922
+ listExtractorPaths: stringArray(toolMetadata.listExtractorPaths),
1923
+ listIdentityGetters: stringArrayRecord(
1924
+ toolMetadata.listIdentityGetters,
1925
+ ),
1926
+ },
1927
+ execution: {
1928
+ idempotent: true,
1929
+ cached: false,
1930
+ source: 'live',
1931
+ },
1932
+ meta: isRecord(response.meta) ? response.meta : undefined,
1933
+ }),
1934
+ options,
1935
+ );
1762
1936
  }
1763
1937
 
1764
1938
  export function defineInput<TInput>(