deepline 0.1.180 → 0.1.182

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.
@@ -73,6 +73,7 @@ import {
73
73
  import { submitChildPlayThroughCoordinator } from './child-play-submit';
74
74
  import type { AnyBatchOperationStrategy } from '../../../shared_libs/play-runtime/batching-types';
75
75
  import {
76
+ attachToolResultListDataset,
76
77
  parseToolExecuteResponse,
77
78
  createToolExecuteResult,
78
79
  deserializeToolExecuteResult,
@@ -97,12 +98,20 @@ import {
97
98
  deriveToolRequestIdentity,
98
99
  derivePlayRowIdentity,
99
100
  derivePlayRowIdentityFromKey,
101
+ sha256Hex,
100
102
  } from '../../../shared_libs/plays/row-identity';
103
+ import { createDeferredPlayDataset } from '../../../shared_libs/plays/dataset';
101
104
  import {
102
105
  buildDurableCtxCallCacheKey,
103
106
  buildDurableToolCallAuthScopeDigest,
104
107
  buildDurableToolCallCacheKey,
105
108
  } from '../../../shared_libs/play-runtime/durable-call-cache';
109
+ import {
110
+ QUERY_RESULT_DATASET_PAGE_SIZE,
111
+ isCustomerDbDatasetTool,
112
+ isQueryResultDatasetReadRequest,
113
+ isQueryResultDatasetTool,
114
+ } from '../../../shared_libs/play-runtime/query-result-dataset';
106
115
  import { buildScopedWorkReceiptKey } from '../../../shared_libs/play-runtime/work-receipts';
107
116
  import { DEDUPE_DUPLICATE_KEY_SAMPLE_CAP } from '../../../shared_libs/play-runtime/map-row-identity';
108
117
  import {
@@ -341,7 +350,40 @@ type WorkerFileRef = {
341
350
 
342
351
  const EXECUTE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
343
352
  const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
353
+ const EXECUTE_RESPONSE_INTENT_HEADER = 'x-deepline-execute-response-intent';
344
354
  const V2_EXECUTE_RESPONSE_CONTRACT = 'v2-tool-execution-result';
355
+ type CustomerDbDatasetRequest = {
356
+ limit?: number;
357
+ offset?: number;
358
+ pageSize?: number;
359
+ totalRows?: number;
360
+ };
361
+
362
+ type WorkerToolDirectOptions = {
363
+ customerDbDataset?: CustomerDbDatasetRequest;
364
+ attachCustomerDbDataset?: boolean;
365
+ };
366
+
367
+ function rowsFromUnknown(value: unknown): Record<string, unknown>[] {
368
+ if (!Array.isArray(value)) return [];
369
+ return value.map((row) =>
370
+ row && typeof row === 'object' && !Array.isArray(row)
371
+ ? (row as Record<string, unknown>)
372
+ : { value: row },
373
+ );
374
+ }
375
+
376
+ function finiteNonNegativeInteger(value: unknown): number | null {
377
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
378
+ return null;
379
+ }
380
+ return Math.floor(value);
381
+ }
382
+
383
+ function finitePositiveInteger(value: unknown): number | null {
384
+ const integer = finiteNonNegativeInteger(value);
385
+ return integer !== null && integer > 0 ? integer : null;
386
+ }
345
387
 
346
388
  function getStringField(value: unknown, key: string): string | null {
347
389
  if (!isRecord(value)) return null;
@@ -706,6 +748,8 @@ async function callRuntimeApiRpcBinding(
706
748
  if (metadata) headers[EXECUTE_TOOL_METADATA_HEADER] = metadata;
707
749
  const contract = h.get(EXECUTE_RESPONSE_CONTRACT_HEADER);
708
750
  if (contract) headers[EXECUTE_RESPONSE_CONTRACT_HEADER] = contract;
751
+ const responseIntent = h.get(EXECUTE_RESPONSE_INTENT_HEADER);
752
+ if (responseIntent) headers[EXECUTE_RESPONSE_INTENT_HEADER] = responseIntent;
709
753
  const rawBody = typeof init.body === 'string' ? init.body : '';
710
754
  const result = await binding.runtimeApiCall({
711
755
  executorToken: authorization.replace(/^Bearer\s+/i, '').trim(),
@@ -1291,6 +1335,7 @@ async function executeTool(
1291
1335
  abortSignal?: AbortSignal,
1292
1336
  runtimeDeadlineMs?: number,
1293
1337
  runtimeTimeoutMs?: number,
1338
+ directOptions?: WorkerToolDirectOptions,
1294
1339
  ): Promise<ToolExecuteResult> {
1295
1340
  if (args.toolId === 'test_wait_for_event' && workflowStep) {
1296
1341
  const result = await waitForSyntheticIntegrationEvent(
@@ -1314,6 +1359,7 @@ async function executeTool(
1314
1359
  abortSignal,
1315
1360
  runtimeDeadlineMs,
1316
1361
  runtimeTimeoutMs,
1362
+ directOptions,
1317
1363
  );
1318
1364
  }
1319
1365
 
@@ -1328,6 +1374,7 @@ async function executeToolWithLifecycle(
1328
1374
  abortSignal?: AbortSignal,
1329
1375
  runtimeDeadlineMs?: number,
1330
1376
  runtimeTimeoutMs?: number,
1377
+ directOptions?: WorkerToolDirectOptions,
1331
1378
  ): Promise<ToolExecuteResult> {
1332
1379
  callbacks?.onToolCalled?.(args.toolId, nowMs());
1333
1380
  try {
@@ -1341,6 +1388,7 @@ async function executeToolWithLifecycle(
1341
1388
  abortSignal,
1342
1389
  runtimeDeadlineMs,
1343
1390
  runtimeTimeoutMs,
1391
+ directOptions,
1344
1392
  );
1345
1393
  } catch (error) {
1346
1394
  callbacks?.onToolFailed?.(args.toolId, nowMs());
@@ -1490,6 +1538,7 @@ async function callToolDirect(
1490
1538
  abortSignal?: AbortSignal,
1491
1539
  runtimeDeadlineMs?: number,
1492
1540
  runtimeTimeoutMs?: number,
1541
+ directOptions?: WorkerToolDirectOptions,
1493
1542
  ): Promise<ToolExecuteResult> {
1494
1543
  const { id, toolId, input } = args;
1495
1544
  const path = `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
@@ -1517,11 +1566,46 @@ async function callToolDirect(
1517
1566
  authorization: `Bearer ${req.executorToken}`,
1518
1567
  'x-deepline-request-id': `${req.runId}:${toolId}:${id}:attempt:${requestAttempt}`,
1519
1568
  [EXECUTE_RESPONSE_CONTRACT_HEADER]: V2_EXECUTE_RESPONSE_CONTRACT,
1569
+ [EXECUTE_RESPONSE_INTENT_HEADER]: 'dataset',
1520
1570
  [EXECUTE_TOOL_METADATA_HEADER]: 'true',
1521
1571
  },
1522
1572
  body: JSON.stringify({
1523
1573
  payload: input,
1524
- metadata: { parent_run_id: req.runId },
1574
+ metadata: {
1575
+ parent_run_id: req.runId,
1576
+ ...(directOptions?.customerDbDataset
1577
+ ? {
1578
+ query_result_dataset: {
1579
+ limit: directOptions.customerDbDataset.limit,
1580
+ offset: directOptions.customerDbDataset.offset,
1581
+ page_size: directOptions.customerDbDataset.pageSize,
1582
+ ...(directOptions.customerDbDataset.totalRows !==
1583
+ undefined
1584
+ ? {
1585
+ total_rows:
1586
+ directOptions.customerDbDataset.totalRows,
1587
+ }
1588
+ : {}),
1589
+ },
1590
+ ...(isCustomerDbDatasetTool(toolId)
1591
+ ? {
1592
+ customer_db_dataset: {
1593
+ limit: directOptions.customerDbDataset.limit,
1594
+ offset: directOptions.customerDbDataset.offset,
1595
+ page_size: directOptions.customerDbDataset.pageSize,
1596
+ ...(directOptions.customerDbDataset.totalRows !==
1597
+ undefined
1598
+ ? {
1599
+ total_rows:
1600
+ directOptions.customerDbDataset.totalRows,
1601
+ }
1602
+ : {}),
1603
+ },
1604
+ }
1605
+ : {}),
1606
+ }
1607
+ : {}),
1608
+ },
1525
1609
  ...(req.integrationMode
1526
1610
  ? { integration_mode: req.integrationMode }
1527
1611
  : {}),
@@ -1561,6 +1645,9 @@ async function callToolDirect(
1561
1645
  parsed.result,
1562
1646
  parsed.metadata,
1563
1647
  parsed.status,
1648
+ directOptions?.attachCustomerDbDataset === false
1649
+ ? undefined
1650
+ : { req, input },
1564
1651
  );
1565
1652
  }
1566
1653
 
@@ -1645,13 +1732,141 @@ function wrapWorkerToolResult(
1645
1732
  result: unknown,
1646
1733
  metadata: ToolResultMetadataInput | null,
1647
1734
  status = result == null ? 'no_result' : 'completed',
1735
+ customerDbDatasetOptions?: {
1736
+ req: RunRequest;
1737
+ input: Record<string, unknown>;
1738
+ },
1648
1739
  ): ToolExecuteResult {
1649
- return createToolExecuteResult({
1740
+ const wrapped = createToolExecuteResult({
1650
1741
  status,
1651
1742
  result,
1652
1743
  metadata: metadata ?? { toolId },
1653
1744
  execution: toolExecutionMetadataForOutcome({ kind: 'live' }),
1654
1745
  });
1746
+ return attachWorkerCustomerDbDatasetResult(
1747
+ toolId,
1748
+ wrapped,
1749
+ customerDbDatasetOptions,
1750
+ );
1751
+ }
1752
+
1753
+ function attachWorkerCustomerDbDatasetResult(
1754
+ toolId: string,
1755
+ result: ToolExecuteResult,
1756
+ options?: {
1757
+ req: RunRequest;
1758
+ input: Record<string, unknown>;
1759
+ },
1760
+ ): ToolExecuteResult {
1761
+ if (!options || !isQueryResultDatasetTool(toolId)) return result;
1762
+ const raw = isRecord(result.toolOutput.raw) ? result.toolOutput.raw : null;
1763
+ const dataset = isRecord(raw?.dataset) ? raw.dataset : null;
1764
+ const rows = rowsFromUnknown(raw?.rows);
1765
+ const totalRows = finiteNonNegativeInteger(dataset?.total_rows);
1766
+ const sql =
1767
+ typeof options.input.sql === 'string'
1768
+ ? options.input.sql
1769
+ : typeof options.input.query === 'string'
1770
+ ? options.input.query
1771
+ : typeof raw?.sql === 'string'
1772
+ ? raw.sql
1773
+ : null;
1774
+ if (!dataset || totalRows === null || !sql) {
1775
+ return result;
1776
+ }
1777
+
1778
+ const datasetLimit =
1779
+ finitePositiveInteger(dataset.returned_limit) ?? totalRows;
1780
+ const previewRows = rows.slice(0, Math.min(rows.length, 25));
1781
+ const jobId = typeof result.job_id === 'string' ? result.job_id.trim() : '';
1782
+ const datasetId = `worker-tool-list:${sha256Hex(
1783
+ `${options.req.runId}:${jobId || Math.random()}`,
1784
+ )}`;
1785
+ const fetchPage = async (
1786
+ offset: number,
1787
+ limit: number,
1788
+ ): Promise<Record<string, unknown>[]> => {
1789
+ if (limit <= 0 || offset >= totalRows) return [];
1790
+ const page = await callToolDirect(
1791
+ options.req,
1792
+ {
1793
+ id: 'customer-db-dataset-page',
1794
+ toolId,
1795
+ input: options.input,
1796
+ },
1797
+ undefined,
1798
+ undefined,
1799
+ false,
1800
+ undefined,
1801
+ undefined,
1802
+ undefined,
1803
+ {
1804
+ attachCustomerDbDataset: false,
1805
+ customerDbDataset: {
1806
+ limit: datasetLimit,
1807
+ offset,
1808
+ pageSize: Math.min(limit, QUERY_RESULT_DATASET_PAGE_SIZE),
1809
+ totalRows,
1810
+ },
1811
+ },
1812
+ );
1813
+ const pageRaw = isRecord(page.toolOutput.raw) ? page.toolOutput.raw : null;
1814
+ return rowsFromUnknown(pageRaw?.rows);
1815
+ };
1816
+ const collectRows = async (
1817
+ limit: number | undefined,
1818
+ ): Promise<Record<string, unknown>[]> => {
1819
+ const target = Math.min(limit ?? totalRows, totalRows, datasetLimit);
1820
+ const collected: Record<string, unknown>[] = [];
1821
+ for (
1822
+ let offset = 0;
1823
+ offset < target;
1824
+ offset += QUERY_RESULT_DATASET_PAGE_SIZE
1825
+ ) {
1826
+ collected.push(
1827
+ ...(await fetchPage(
1828
+ offset,
1829
+ Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, target - offset),
1830
+ )),
1831
+ );
1832
+ }
1833
+ return collected.slice(0, target);
1834
+ };
1835
+ const playDataset = createDeferredPlayDataset({
1836
+ datasetKind: 'csv',
1837
+ datasetId,
1838
+ count: Math.min(totalRows, datasetLimit),
1839
+ previewRows,
1840
+ sourceLabel: 'toolResponse.raw.rows',
1841
+ tableNamespace: null,
1842
+ resolvers: {
1843
+ count: async () => Math.min(totalRows, datasetLimit),
1844
+ peek: async (limit) => collectRows(limit),
1845
+ materialize: async (limit) => collectRows(limit),
1846
+ iterate: () =>
1847
+ ({
1848
+ async *[Symbol.asyncIterator]() {
1849
+ const count = Math.min(totalRows, datasetLimit);
1850
+ for (
1851
+ let offset = 0;
1852
+ offset < count;
1853
+ offset += QUERY_RESULT_DATASET_PAGE_SIZE
1854
+ ) {
1855
+ yield* await fetchPage(
1856
+ offset,
1857
+ Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, count - offset),
1858
+ );
1859
+ }
1860
+ },
1861
+ }) as AsyncIterable<Record<string, unknown>>,
1862
+ },
1863
+ });
1864
+ return attachToolResultListDataset(result, {
1865
+ name: 'rows',
1866
+ path: 'toolResponse.raw.rows',
1867
+ dataset: playDataset,
1868
+ count: Math.min(totalRows, datasetLimit),
1869
+ });
1655
1870
  }
1656
1871
 
1657
1872
  type RecordedStepProgramOutput = {
@@ -1685,6 +1900,7 @@ type WorkerToolBatchRequest = {
1685
1900
  toolId: string;
1686
1901
  input: Record<string, unknown>;
1687
1902
  workflowStep?: WorkflowStep;
1903
+ directOptions?: WorkerToolDirectOptions;
1688
1904
  resolve: (value: unknown) => void;
1689
1905
  reject: (error: unknown) => void;
1690
1906
  };
@@ -1975,10 +2191,15 @@ class WorkerToolBatchScheduler {
1975
2191
  providerActionVersion,
1976
2192
  staleAfterSeconds: options?.staleAfterSeconds,
1977
2193
  });
2194
+ const queryResultDatasetRead = isQueryResultDatasetReadRequest(
2195
+ toolId,
2196
+ input,
2197
+ );
2198
+ const receiptEligible = !queryResultDatasetRead;
1978
2199
  this.queue.push({
1979
2200
  id,
1980
- cacheKey: receiptKey,
1981
- receiptKey,
2201
+ cacheKey: receiptEligible ? receiptKey : '',
2202
+ receiptKey: receiptEligible ? receiptKey : null,
1982
2203
  force: options?.force === true || !!this.req.force,
1983
2204
  receiptWaitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(
1984
2205
  typeof runtimeOptions?.receiptWaitMs === 'number'
@@ -1991,6 +2212,16 @@ class WorkerToolBatchScheduler {
1991
2212
  toolId,
1992
2213
  input,
1993
2214
  workflowStep,
2215
+ ...(queryResultDatasetRead
2216
+ ? {
2217
+ directOptions: {
2218
+ customerDbDataset: {
2219
+ offset: 0,
2220
+ pageSize: QUERY_RESULT_DATASET_PAGE_SIZE,
2221
+ },
2222
+ },
2223
+ }
2224
+ : {}),
1994
2225
  resolve: (value) => {
1995
2226
  recordRunnerPerfTrace({
1996
2227
  req: this.req,
@@ -2794,6 +3025,7 @@ class WorkerToolBatchScheduler {
2794
3025
  resolveClaimedWorkerToolRuntimeTimeoutMs([claimed], {
2795
3026
  runtimeDeadlineMs: this.runtimeDeadlineMs,
2796
3027
  }),
3028
+ request.directOptions,
2797
3029
  );
2798
3030
  } catch (error) {
2799
3031
  this.rejectRequests(
@@ -3056,6 +3288,11 @@ async function executeBatchedWorkerToolGroup(input: {
3056
3288
  claimed.request.toolId,
3057
3289
  splitResults[index] ?? null,
3058
3290
  toolMetadataFallback(claimed.request.toolId),
3291
+ splitResults[index] == null ? 'no_result' : 'completed',
3292
+ {
3293
+ req: input.req,
3294
+ input: claimed.request.input,
3295
+ },
3059
3296
  ),
3060
3297
  })),
3061
3298
  );
@@ -5,6 +5,8 @@ import {
5
5
  sha256Hex,
6
6
  } from '../../../../shared_libs/plays/row-identity';
7
7
  import {
8
+ createDeferredPlayDataset,
9
+ isPlayDataset,
8
10
  isSerializedPlayDataset,
9
11
  type SerializedPlayDataset,
10
12
  } from '../../../../shared_libs/plays/dataset';
@@ -60,6 +62,22 @@ function datasetMetadata(value: unknown): Record<string, unknown> | null {
60
62
  }
61
63
  }
62
64
 
65
+ function datasetCountFromMetadata(
66
+ metadata: Record<string, unknown> | null,
67
+ ): number {
68
+ return typeof metadata?.count === 'number' && Number.isFinite(metadata.count)
69
+ ? Math.max(0, Math.floor(metadata.count))
70
+ : 0;
71
+ }
72
+
73
+ function datasetPreviewFromMetadata(
74
+ metadata: Record<string, unknown> | null,
75
+ ): OutputDatasetRow[] {
76
+ return Array.isArray(metadata?.preview)
77
+ ? metadata.preview.filter(isRecord).map((row) => ({ ...row }))
78
+ : [];
79
+ }
80
+
63
81
  function isRowArray(
64
82
  value: unknown,
65
83
  path: readonly string[],
@@ -226,6 +244,47 @@ function disambiguateTableNamespace(
226
244
  return candidate;
227
245
  }
228
246
 
247
+ function collectExistingDatasetTableNamespaces(value: unknown): Set<string> {
248
+ const namespaces = new Set<string>();
249
+ const seen = new WeakSet<object>();
250
+
251
+ const addNamespace = (namespace: unknown) => {
252
+ if (typeof namespace !== 'string' || !namespace) return;
253
+ try {
254
+ namespaces.add(normalizeTableNamespace(namespace));
255
+ } catch {
256
+ // Historical malformed namespaces should not block generated result
257
+ // names. The original handle/envelope remains unchanged.
258
+ }
259
+ };
260
+
261
+ const visit = (candidate: unknown, depth: number): void => {
262
+ if (depth > 20 || candidate == null || typeof candidate !== 'object') {
263
+ return;
264
+ }
265
+ if (seen.has(candidate)) return;
266
+ seen.add(candidate);
267
+ if (isSerializedPlayDataset(candidate)) {
268
+ addNamespace(candidate.tableNamespace);
269
+ return;
270
+ }
271
+ if (isDatasetLike(candidate)) {
272
+ addNamespace(datasetMetadata(candidate)?.tableNamespace);
273
+ return;
274
+ }
275
+ if (Array.isArray(candidate)) {
276
+ for (const entry of candidate) visit(entry, depth + 1);
277
+ return;
278
+ }
279
+ for (const child of Object.values(candidate as Record<string, unknown>)) {
280
+ visit(child, depth + 1);
281
+ }
282
+ };
283
+
284
+ visit(value, 0);
285
+ return namespaces;
286
+ }
287
+
229
288
  export function projectTerminalResultDatasets(
230
289
  value: unknown,
231
290
  createDataset: DatasetFactory,
@@ -239,30 +298,84 @@ export function projectTerminalResultDatasets(
239
298
  const handles: ProjectedResultDatasetHandle[] = [];
240
299
  const seen = new WeakMap<object, unknown>();
241
300
  const seenHandles = new WeakSet<object>();
301
+ const projectedHandles = new WeakMap<object, unknown>();
242
302
  const usedNamespaces = seedUsedNamespaces(options.reservedTableNamespaces);
303
+ for (const namespace of collectExistingDatasetTableNamespaces(value)) {
304
+ usedNamespaces.add(namespace);
305
+ }
243
306
  const maxTableNamespaceLength = maxTableNamespaceLengthForPlay(
244
307
  options.playName,
245
308
  );
246
309
 
247
- const rememberHandle = (handle: unknown, path: readonly string[]): void => {
248
- if (!isRecord(handle) || seenHandles.has(handle)) return;
310
+ const projectHandle = (handle: unknown, path: readonly string[]): unknown => {
311
+ if (!isRecord(handle)) return handle;
312
+ const existingProjection = projectedHandles.get(handle);
313
+ if (existingProjection) return existingProjection;
249
314
  const metadata = datasetMetadata(handle);
250
- const tableNamespace =
251
- typeof metadata?.tableNamespace === 'string'
315
+ let projectedHandle: unknown = handle;
316
+ let tableNamespace =
317
+ typeof metadata?.tableNamespace === 'string' && metadata.tableNamespace
252
318
  ? metadata.tableNamespace
253
319
  : null;
254
- if (!tableNamespace) return;
255
- seenHandles.add(handle);
320
+ if (!tableNamespace && isPlayDataset<OutputDatasetRow>(handle)) {
321
+ const resolvedPath = path.length > 0 ? path : ['rows'];
322
+ tableNamespace = disambiguateTableNamespace(
323
+ resultTableNamespaceForPath(resolvedPath, maxTableNamespaceLength),
324
+ resolvedPath,
325
+ usedNamespaces,
326
+ maxTableNamespaceLength,
327
+ );
328
+ projectedHandle = createDeferredPlayDataset({
329
+ datasetKind: 'csv',
330
+ datasetId:
331
+ typeof metadata?.datasetId === 'string'
332
+ ? metadata.datasetId
333
+ : `csv:${tableNamespace}`,
334
+ count: datasetCountFromMetadata(metadata),
335
+ previewRows: datasetPreviewFromMetadata(metadata),
336
+ sourceLabel:
337
+ typeof metadata?.sourceLabel === 'string'
338
+ ? metadata.sourceLabel
339
+ : null,
340
+ tableNamespace,
341
+ resolvers: {
342
+ count: () => handle.count(),
343
+ peek: (limit) => handle.peek(limit),
344
+ materialize: (limit) => handle.materialize(limit),
345
+ iterate: () =>
346
+ ({
347
+ async *[Symbol.asyncIterator]() {
348
+ yield* handle;
349
+ },
350
+ }) as AsyncIterable<OutputDatasetRow>,
351
+ },
352
+ });
353
+ }
354
+ projectedHandles.set(handle, projectedHandle);
355
+ if (!tableNamespace || seenHandles.has(projectedHandle as object)) {
356
+ return projectedHandle;
357
+ }
358
+ try {
359
+ usedNamespaces.add(normalizeTableNamespace(tableNamespace));
360
+ } catch {
361
+ // Existing dataset handles can carry historical namespaces. Keep the
362
+ // handle, but do not let a malformed namespace poison new result names.
363
+ }
364
+ seenHandles.add(projectedHandle as object);
365
+ const projectedMetadata =
366
+ projectedHandle === handle ? metadata : datasetMetadata(projectedHandle);
256
367
  const datasetKind =
257
- metadata?.datasetKind === 'csv' || metadata?.datasetKind === 'map'
258
- ? metadata.datasetKind
368
+ projectedMetadata?.datasetKind === 'csv' ||
369
+ projectedMetadata?.datasetKind === 'map'
370
+ ? projectedMetadata.datasetKind
259
371
  : null;
260
372
  handles.push({
261
373
  path: path.length > 0 ? path.join('.') : 'rows',
262
374
  tableNamespace,
263
375
  datasetKind,
264
- handle,
376
+ handle: projectedHandle,
265
377
  });
378
+ return projectedHandle;
266
379
  };
267
380
 
268
381
  const promoteRows = (
@@ -287,7 +400,7 @@ export function projectTerminalResultDatasets(
287
400
  rows,
288
401
  handle,
289
402
  });
290
- rememberHandle(handle, path);
403
+ projectHandle(handle, path);
291
404
  return handle;
292
405
  };
293
406
 
@@ -298,8 +411,7 @@ export function projectTerminalResultDatasets(
298
411
  ): unknown => {
299
412
  if (depth > 20 || candidate == null) return candidate;
300
413
  if (isDatasetLike(candidate)) {
301
- rememberHandle(candidate, path);
302
- return candidate;
414
+ return projectHandle(candidate, path);
303
415
  }
304
416
  if (isSerializedPlayDataset(candidate)) {
305
417
  return candidate;
@@ -202,7 +202,8 @@ function chunkRegisterPlayArtifacts<T>(artifacts: T[]): T[][] {
202
202
 
203
203
  type ExecuteToolRawOptions = {
204
204
  includeToolMetadata?: boolean;
205
- responseIntent?: 'raw' | 'row_artifact';
205
+ responseIntent?: 'dataset' | 'raw' | 'row_artifact';
206
+ metadata?: Record<string, unknown>;
206
207
  timeout?: number;
207
208
  maxRetries?: number;
208
209
  };
@@ -1310,13 +1311,14 @@ export class DeeplineClient {
1310
1311
  ...(options?.includeToolMetadata
1311
1312
  ? { [INCLUDE_TOOL_METADATA_HEADER]: 'true' }
1312
1313
  : {}),
1313
- ...(options?.responseIntent
1314
- ? { [EXECUTE_RESPONSE_INTENT_HEADER]: options.responseIntent }
1315
- : {}),
1314
+ [EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? 'raw',
1316
1315
  };
1317
1316
  return this.http.post<ToolExecution<TData, TMeta>>(
1318
1317
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
1319
- { payload: input },
1318
+ {
1319
+ payload: input,
1320
+ ...(options?.metadata ? { metadata: options.metadata } : {}),
1321
+ },
1320
1322
  headers,
1321
1323
  {
1322
1324
  forbiddenAsApiError: true,