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.
@@ -41,6 +41,7 @@ import { InMemoryRateStateBackend } from './governor/in-memory-rate-state-backen
41
41
  import { pacingPolicyForTool } from './pacing';
42
42
  import {
43
43
  cloneToolExecuteResultWithExecution,
44
+ attachToolResultListDataset,
44
45
  createToolExecuteResult,
45
46
  parseToolExecuteResponse,
46
47
  isToolExecuteResult,
@@ -69,6 +70,12 @@ import {
69
70
  buildDurableToolCallAuthScopeDigest,
70
71
  buildDurableToolCallCacheKey,
71
72
  } from './durable-call-cache';
73
+ import {
74
+ QUERY_RESULT_DATASET_PAGE_SIZE,
75
+ isCustomerDbDatasetTool,
76
+ isQueryResultDatasetReadRequest,
77
+ isQueryResultDatasetTool,
78
+ } from './query-result-dataset';
72
79
  import {
73
80
  RuntimeReceiptWaitTimeoutError,
74
81
  executeWithDurableRuntimeReceipt,
@@ -266,7 +273,44 @@ function publicToolResponseEnvelope(value: unknown): {
266
273
 
267
274
  const EXECUTE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
268
275
  const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
276
+ const EXECUTE_RESPONSE_INTENT_HEADER = 'x-deepline-execute-response-intent';
269
277
  const V2_EXECUTE_RESPONSE_CONTRACT = 'v2-tool-response';
278
+ function recordOrNull(value: unknown): Record<string, unknown> | null {
279
+ return value && typeof value === 'object' && !Array.isArray(value)
280
+ ? (value as Record<string, unknown>)
281
+ : null;
282
+ }
283
+
284
+ function rowsFromUnknown(value: unknown): Record<string, unknown>[] {
285
+ if (!Array.isArray(value)) return [];
286
+ return value.map((row) =>
287
+ row && typeof row === 'object' && !Array.isArray(row)
288
+ ? (row as Record<string, unknown>)
289
+ : { value: row },
290
+ );
291
+ }
292
+
293
+ function finiteNonNegativeInteger(value: unknown): number | null {
294
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
295
+ return null;
296
+ }
297
+ return Math.floor(value);
298
+ }
299
+
300
+ function finitePositiveInteger(value: unknown): number | null {
301
+ const integer = finiteNonNegativeInteger(value);
302
+ return integer !== null && integer > 0 ? integer : null;
303
+ }
304
+
305
+ type ToolExecutionApiOptions = {
306
+ timeoutMs?: number;
307
+ customerDbDataset?: {
308
+ limit: number;
309
+ offset: number;
310
+ pageSize: number;
311
+ totalRows: number;
312
+ };
313
+ };
270
314
  const IN_MEMORY_STEP_RESULT_PREVIEW_LIMIT = 25;
271
315
  const BATCH_SIZE_LOG_SAMPLE_LIMIT = 10;
272
316
  const STEP_PROGRAM_MAP_DEFINITION = Symbol('deepline.stepProgramMapDefinition');
@@ -1695,24 +1739,146 @@ export class PlayContextImpl {
1695
1739
  metadata?: ToolResultMetadataInput | null;
1696
1740
  meta?: Record<string, unknown>;
1697
1741
  execution: ToolResultExecutionMetadata;
1742
+ requestInput?: Record<string, unknown>;
1698
1743
  }): Promise<ToolExecuteResult> {
1699
1744
  if (isToolExecuteResult(input.result)) {
1700
- return cloneToolExecuteResultWithExecution(input.result, input.execution);
1745
+ return this.attachCustomerDbDatasetResult(
1746
+ input.toolId,
1747
+ input.requestInput,
1748
+ cloneToolExecuteResultWithExecution(input.result, input.execution),
1749
+ );
1701
1750
  }
1702
1751
  const publicToolResult = publicToolResponseEnvelope(input.result);
1703
- return createToolExecuteResult({
1704
- status: publicToolResult?.status ?? input.status,
1705
- jobId: input.jobId,
1706
- result: publicToolResult
1707
- ? {
1708
- data: publicToolResult.raw,
1709
- ...(publicToolResult.meta ? { meta: publicToolResult.meta } : {}),
1710
- }
1711
- : input.result,
1712
- metadata:
1713
- input.metadata ?? (await this.resolveToolResultMetadata(input.toolId)),
1714
- execution: input.execution,
1715
- meta: input.meta,
1752
+ return this.attachCustomerDbDatasetResult(
1753
+ input.toolId,
1754
+ input.requestInput,
1755
+ createToolExecuteResult({
1756
+ status: publicToolResult?.status ?? input.status,
1757
+ jobId: input.jobId,
1758
+ result: publicToolResult
1759
+ ? {
1760
+ data: publicToolResult.raw,
1761
+ ...(publicToolResult.meta ? { meta: publicToolResult.meta } : {}),
1762
+ }
1763
+ : input.result,
1764
+ metadata:
1765
+ input.metadata ??
1766
+ (await this.resolveToolResultMetadata(input.toolId)),
1767
+ execution: input.execution,
1768
+ meta: input.meta,
1769
+ }),
1770
+ );
1771
+ }
1772
+
1773
+ private attachCustomerDbDatasetResult(
1774
+ toolId: string,
1775
+ requestInput: Record<string, unknown> | undefined,
1776
+ wrapped: ToolExecuteResult,
1777
+ ): ToolExecuteResult {
1778
+ if (!isQueryResultDatasetTool(toolId)) return wrapped;
1779
+ const raw = recordOrNull(wrapped.toolResponse.raw);
1780
+ const dataset = recordOrNull(raw?.dataset);
1781
+ const rows = rowsFromUnknown(raw?.rows);
1782
+ const totalRows = finiteNonNegativeInteger(dataset?.total_rows);
1783
+ const sql =
1784
+ typeof requestInput?.sql === 'string'
1785
+ ? requestInput.sql
1786
+ : typeof requestInput?.query === 'string'
1787
+ ? requestInput.query
1788
+ : typeof raw?.sql === 'string'
1789
+ ? raw.sql
1790
+ : null;
1791
+ if (!dataset || totalRows === null || !sql) {
1792
+ return wrapped;
1793
+ }
1794
+ const originalRequestInput = requestInput as Record<string, unknown>;
1795
+ const datasetLimit =
1796
+ finitePositiveInteger(dataset.returned_limit) ?? totalRows;
1797
+ const previewRows = rows.slice(0, Math.min(rows.length, 25));
1798
+ const executionNonce =
1799
+ typeof wrapped.job_id === 'string' && wrapped.job_id.trim()
1800
+ ? wrapped.job_id.trim()
1801
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
1802
+ const datasetId = `tool-list:${sha256Hex(
1803
+ `${toolId}:${this.currentRunId}:${sql}:${datasetLimit}:${executionNonce}`,
1804
+ )}`;
1805
+ const fetchPage = async (
1806
+ offset: number,
1807
+ limit: number,
1808
+ ): Promise<Record<string, unknown>[]> => {
1809
+ if (limit <= 0 || offset >= totalRows) return [];
1810
+ const execution = await this.callToolExecutionAPI(
1811
+ toolId,
1812
+ originalRequestInput,
1813
+ {
1814
+ timeoutMs: resolveToolRuntimeTimeoutMs(toolId),
1815
+ customerDbDataset: {
1816
+ limit: datasetLimit,
1817
+ offset,
1818
+ pageSize: Math.min(limit, QUERY_RESULT_DATASET_PAGE_SIZE),
1819
+ totalRows,
1820
+ },
1821
+ },
1822
+ );
1823
+ const pageRaw =
1824
+ recordOrNull(execution.toolResponse)?.raw ??
1825
+ recordOrNull(execution.result)?.data ??
1826
+ execution.result;
1827
+ return rowsFromUnknown(recordOrNull(pageRaw)?.rows);
1828
+ };
1829
+ const collectRows = async (
1830
+ limit: number | undefined,
1831
+ ): Promise<Record<string, unknown>[]> => {
1832
+ const target = Math.min(limit ?? totalRows, totalRows, datasetLimit);
1833
+ const collected: Record<string, unknown>[] = [];
1834
+ for (
1835
+ let offset = 0;
1836
+ offset < target;
1837
+ offset += QUERY_RESULT_DATASET_PAGE_SIZE
1838
+ ) {
1839
+ collected.push(
1840
+ ...(await fetchPage(
1841
+ offset,
1842
+ Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, target - offset),
1843
+ )),
1844
+ );
1845
+ }
1846
+ return collected.slice(0, target);
1847
+ };
1848
+ const playDataset = createDeferredPlayDataset({
1849
+ datasetKind: 'csv',
1850
+ datasetId,
1851
+ count: Math.min(totalRows, datasetLimit),
1852
+ previewRows,
1853
+ sourceLabel: 'query result rows',
1854
+ tableNamespace: null,
1855
+ resolvers: {
1856
+ count: async () => Math.min(totalRows, datasetLimit),
1857
+ peek: async (limit) => collectRows(limit),
1858
+ materialize: async (limit) => collectRows(limit),
1859
+ iterate: () =>
1860
+ ({
1861
+ async *[Symbol.asyncIterator]() {
1862
+ const count = Math.min(totalRows, datasetLimit);
1863
+ for (
1864
+ let offset = 0;
1865
+ offset < count;
1866
+ offset += QUERY_RESULT_DATASET_PAGE_SIZE
1867
+ ) {
1868
+ yield* await fetchPage(
1869
+ offset,
1870
+ Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, count - offset),
1871
+ );
1872
+ }
1873
+ },
1874
+ }) as AsyncIterable<Record<string, unknown>>,
1875
+ },
1876
+ });
1877
+ return attachToolResultListDataset(wrapped, {
1878
+ name: 'rows',
1879
+ path: 'toolResponse.raw.rows',
1880
+ dataset: playDataset,
1881
+ count: Math.min(totalRows, datasetLimit),
1716
1882
  });
1717
1883
  }
1718
1884
 
@@ -1738,6 +1904,7 @@ export class PlayContextImpl {
1738
1904
  cacheKey,
1739
1905
  receiptKey,
1740
1906
  }),
1907
+ requestInput: request.input,
1741
1908
  });
1742
1909
  let completed: RuntimeStepReceipt | null | undefined = null;
1743
1910
  if (receiptKey) {
@@ -1791,6 +1958,7 @@ export class PlayContextImpl {
1791
1958
  cacheKey: entry.request.cacheKey,
1792
1959
  receiptKey: entry.request.receiptKey,
1793
1960
  }),
1961
+ requestInput: entry.request.input,
1794
1962
  }),
1795
1963
  })),
1796
1964
  );
@@ -1851,6 +2019,7 @@ export class PlayContextImpl {
1851
2019
  ? 'no_result'
1852
2020
  : 'completed',
1853
2021
  result: this.runtimeReceiptOutput(completed),
2022
+ requestInput: request.input,
1854
2023
  execution: toolExecutionMetadataForOutcome(
1855
2024
  completed.runId === this.currentRunId
1856
2025
  ? {
@@ -1867,7 +2036,9 @@ export class PlayContextImpl {
1867
2036
  ),
1868
2037
  })
1869
2038
  : wrapped;
1870
- this.cacheToolResult(toolId, cacheKey, finalWrapped);
2039
+ if (request.cacheable !== false) {
2040
+ this.cacheToolResult(toolId, cacheKey, finalWrapped);
2041
+ }
1871
2042
 
1872
2043
  const resolver = this.toolCallResolvers.get(request.callId);
1873
2044
  if (resolver) {
@@ -2211,8 +2382,7 @@ export class PlayContextImpl {
2211
2382
  playId: this.#options.playId,
2212
2383
  runId: this.#options.runId,
2213
2384
  staticPipeline: this.#options.staticPipeline,
2214
- forceRefresh:
2215
- this.#options.cachePolicy?.forceToolRefresh === true,
2385
+ forceRefresh: this.#options.cachePolicy?.forceToolRefresh === true,
2216
2386
  },
2217
2387
  );
2218
2388
  resolvedTableNamespace = normalizeTableNamespace(
@@ -3486,11 +3656,13 @@ export class PlayContextImpl {
3486
3656
  toolId,
3487
3657
  requestInput: input,
3488
3658
  });
3659
+ const cacheableToolResult = !isQueryResultDatasetReadRequest(toolId, input);
3489
3660
  const durableCacheKey = await this.durableToolCallCacheKey({
3490
3661
  toolId,
3491
3662
  requestInput: input,
3492
3663
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
3493
3664
  });
3665
+ const durableReceiptKey = cacheableToolResult ? durableCacheKey : null;
3494
3666
  const eventWaitHandler =
3495
3667
  (await this.#options.getIntegrationEventWaitHandler?.(toolId)) ?? null;
3496
3668
  const store = rowContext.getStore();
@@ -3518,15 +3690,18 @@ export class PlayContextImpl {
3518
3690
 
3519
3691
  if (!store) {
3520
3692
  const directCacheKey = durableCacheKey;
3521
- const cached = toolCachePolicy.force
3693
+ const cached = !cacheableToolResult
3522
3694
  ? null
3523
- : this.getCachedToolResult(toolId, directCacheKey);
3695
+ : toolCachePolicy.force
3696
+ ? null
3697
+ : this.getCachedToolResult(toolId, directCacheKey);
3524
3698
  if (cached?.done) {
3525
3699
  this.log(`Tool cache hit: ${toolId} exact payload`);
3526
3700
  return await this.wrapToolExecutionResult({
3527
3701
  toolId,
3528
3702
  status: cached.result == null ? 'no_result' : 'completed',
3529
3703
  result: cached.result,
3704
+ requestInput: input,
3530
3705
  execution: toolExecutionMetadataForOutcome({
3531
3706
  kind: 'checkpoint',
3532
3707
  cacheKey: directCacheKey,
@@ -3548,19 +3723,22 @@ export class PlayContextImpl {
3548
3723
  result: execution.result,
3549
3724
  metadata: execution.metadata,
3550
3725
  meta: execution.meta,
3726
+ requestInput: input,
3551
3727
  execution: toolExecutionMetadataForOutcome({
3552
3728
  kind: 'live',
3553
3729
  cacheKey: directCacheKey,
3554
3730
  }),
3555
3731
  });
3556
- this.checkpoint.completedToolBatches[toolId] = {
3557
- ...(this.checkpoint.completedToolBatches[toolId] ?? {}),
3558
- [directCacheKey]: {
3559
- done: true,
3560
- result: wrapped,
3561
- },
3562
- };
3563
- this.#options.onBatchComplete?.(this.checkpoint);
3732
+ if (cacheableToolResult) {
3733
+ this.checkpoint.completedToolBatches[toolId] = {
3734
+ ...(this.checkpoint.completedToolBatches[toolId] ?? {}),
3735
+ [directCacheKey]: {
3736
+ done: true,
3737
+ result: wrapped,
3738
+ },
3739
+ };
3740
+ this.#options.onBatchComplete?.(this.checkpoint);
3741
+ }
3564
3742
  return wrapped;
3565
3743
  }
3566
3744
 
@@ -3581,15 +3759,18 @@ export class PlayContextImpl {
3581
3759
  }
3582
3760
 
3583
3761
  const toolResultCacheKey = durableCacheKey;
3584
- const cached = toolCachePolicy.force
3762
+ const cached = !cacheableToolResult
3585
3763
  ? null
3586
- : this.getCachedToolResult(toolId, toolResultCacheKey);
3764
+ : toolCachePolicy.force
3765
+ ? null
3766
+ : this.getCachedToolResult(toolId, toolResultCacheKey);
3587
3767
  if (cached?.done) {
3588
3768
  this.log(` Row ${rowId} ${toolId}: cache hit exact payload`);
3589
3769
  return await this.wrapToolExecutionResult({
3590
3770
  toolId,
3591
3771
  status: cached.result == null ? 'no_result' : 'completed',
3592
3772
  result: cached.result,
3773
+ requestInput: input,
3593
3774
  execution: toolExecutionMetadataForOutcome({
3594
3775
  kind: 'checkpoint',
3595
3776
  cacheKey: toolResultCacheKey,
@@ -3624,7 +3805,8 @@ export class PlayContextImpl {
3624
3805
  this.toolCallQueue.push({
3625
3806
  callId,
3626
3807
  cacheKey: toolResultCacheKey,
3627
- receiptKey: durableCacheKey,
3808
+ cacheable: cacheableToolResult,
3809
+ receiptKey: durableReceiptKey,
3628
3810
  force: toolCachePolicy.force,
3629
3811
  rowId,
3630
3812
  fieldName,
@@ -3641,7 +3823,7 @@ export class PlayContextImpl {
3641
3823
  });
3642
3824
  };
3643
3825
 
3644
- if (store) {
3826
+ if (store || !cacheableToolResult) {
3645
3827
  return await executeTool();
3646
3828
  }
3647
3829
 
@@ -3650,7 +3832,7 @@ export class PlayContextImpl {
3650
3832
  normalizedKey,
3651
3833
  this.currentRunId,
3652
3834
  {
3653
- receiptKey: durableCacheKey,
3835
+ receiptKey: durableReceiptKey,
3654
3836
  semanticKey: toolRequestIdentity,
3655
3837
  force: toolCachePolicy.force,
3656
3838
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
@@ -4347,17 +4529,21 @@ export class PlayContextImpl {
4347
4529
  await Promise.all(
4348
4530
  [...byTool.entries()].map(async ([toolId, requests]) => {
4349
4531
  this.log(`Executing tool batch ${toolId}: ${requests.length} calls`);
4532
+ const liveStepResults = new Map<string, unknown>();
4350
4533
 
4351
4534
  const recordToolStep = (stepRequests: ToolCallRequest[]): void => {
4352
4535
  if (stepRequests.length === 0) return;
4353
4536
  const stepResults: PlayStepRowResult[] = stepRequests.map((req) => {
4354
- const cachedResult = this.getCachedToolResult(toolId, req.cacheKey);
4537
+ const hasLiveResult = liveStepResults.has(req.callId);
4538
+ const result = hasLiveResult
4539
+ ? liveStepResults.get(req.callId)
4540
+ : this.getCachedToolResult(toolId, req.cacheKey)?.result;
4355
4541
  return {
4356
4542
  rowId: req.rowId,
4357
- status: cachedResult?.result != null ? 'completed' : 'failed',
4358
- success: cachedResult?.result != null,
4359
- value: cachedResult?.result,
4360
- error: cachedResult?.result != null ? null : 'Tool call failed',
4543
+ status: result != null ? 'completed' : 'failed',
4544
+ success: result != null,
4545
+ value: result,
4546
+ error: result != null ? null : 'Tool call failed',
4361
4547
  };
4362
4548
  });
4363
4549
  const toolStep = {
@@ -4377,9 +4563,12 @@ export class PlayContextImpl {
4377
4563
  const pendingRequests: ToolCallRequest[] = [];
4378
4564
  const recoveredRequests: ToolCallRequest[] = [];
4379
4565
  for (const req of requests) {
4380
- const cached = req.force
4381
- ? undefined
4382
- : this.getCachedToolResult(toolId, req.cacheKey);
4566
+ const cached =
4567
+ req.cacheable === false
4568
+ ? undefined
4569
+ : req.force
4570
+ ? undefined
4571
+ : this.getCachedToolResult(toolId, req.cacheKey);
4383
4572
  if (cached?.done) {
4384
4573
  this.log(` Row ${req.rowId} ${toolId}: recovered from checkpoint`);
4385
4574
  const resolver = this.toolCallResolvers.get(req.callId);
@@ -4411,7 +4600,9 @@ export class PlayContextImpl {
4411
4600
  attachedToReceiptKey: followerReceiptKey,
4412
4601
  })
4413
4602
  : result;
4414
- this.cacheToolResult(toolId, follower.cacheKey, followerResult);
4603
+ if (follower.cacheable !== false) {
4604
+ this.cacheToolResult(toolId, follower.cacheKey, followerResult);
4605
+ }
4415
4606
  const resolver = this.toolCallResolvers.get(follower.callId);
4416
4607
  if (resolver) {
4417
4608
  resolver.resolve(followerResult);
@@ -4558,6 +4749,7 @@ export class PlayContextImpl {
4558
4749
  ? 'no_result'
4559
4750
  : 'completed',
4560
4751
  result: this.runtimeReceiptOutput(receipt),
4752
+ requestInput: request.input,
4561
4753
  execution: toolExecutionMetadataForOutcome({
4562
4754
  kind: source,
4563
4755
  cacheKey: request.cacheKey,
@@ -4689,6 +4881,7 @@ export class PlayContextImpl {
4689
4881
  execution?.jobId,
4690
4882
  execution?.meta,
4691
4883
  );
4884
+ liveStepResults.set(owner.callId, result);
4692
4885
  resolveLiveFollowers(owner, result);
4693
4886
  recordToolStep([owner]);
4694
4887
  this.#options.onBatchComplete?.(this.checkpoint);
@@ -4837,6 +5030,7 @@ export class PlayContextImpl {
4837
5030
  index += 1
4838
5031
  ) {
4839
5032
  const request = entry.request.memberRequests[index]!;
5033
+ liveStepResults.set(request.callId, resolvedResults[index]);
4840
5034
  resolveLiveFollowers(request, resolvedResults[index]);
4841
5035
  }
4842
5036
  }
@@ -4894,6 +5088,7 @@ export class PlayContextImpl {
4894
5088
  execution?.jobId,
4895
5089
  execution?.meta,
4896
5090
  );
5091
+ liveStepResults.set(request.callId, result);
4897
5092
  resolveLiveFollowers(request, result);
4898
5093
  } catch (error) {
4899
5094
  await rejectWithLiveFollowers(request, error);
@@ -4989,7 +5184,7 @@ export class PlayContextImpl {
4989
5184
  private async callToolAPI(
4990
5185
  toolId: string,
4991
5186
  input: Record<string, unknown>,
4992
- options?: { timeoutMs?: number },
5187
+ options?: ToolExecutionApiOptions,
4993
5188
  ): Promise<unknown> {
4994
5189
  const execution = await this.callToolExecutionAPI(toolId, input, options);
4995
5190
  if (execution.toolResponse && 'raw' in execution.toolResponse) {
@@ -5006,7 +5201,7 @@ export class PlayContextImpl {
5006
5201
  private async callToolExecutionAPI(
5007
5202
  toolId: string,
5008
5203
  input: Record<string, unknown>,
5009
- options?: { timeoutMs?: number },
5204
+ options?: ToolExecutionApiOptions,
5010
5205
  ): Promise<ParsedToolExecuteResponse> {
5011
5206
  if (!this.#options.executorToken || !this.#options.baseUrl) {
5012
5207
  throw new Error(
@@ -5064,6 +5259,7 @@ export class PlayContextImpl {
5064
5259
  Authorization: `Bearer ${this.#options.executorToken}`,
5065
5260
  [EXECUTE_RESPONSE_CONTRACT_HEADER]:
5066
5261
  V2_EXECUTE_RESPONSE_CONTRACT,
5262
+ [EXECUTE_RESPONSE_INTENT_HEADER]: 'dataset',
5067
5263
  [EXECUTE_TOOL_METADATA_HEADER]: 'true',
5068
5264
  ...(this.#options.vercelProtectionBypassToken?.trim()
5069
5265
  ? {
@@ -5074,7 +5270,30 @@ export class PlayContextImpl {
5074
5270
  },
5075
5271
  body: JSON.stringify({
5076
5272
  payload: input,
5077
- metadata: { parent_run_id: this.#options.runId },
5273
+ metadata: {
5274
+ parent_run_id: this.#options.runId,
5275
+ ...(options?.customerDbDataset
5276
+ ? {
5277
+ query_result_dataset: {
5278
+ limit: options.customerDbDataset.limit,
5279
+ offset: options.customerDbDataset.offset,
5280
+ page_size: options.customerDbDataset.pageSize,
5281
+ total_rows: options.customerDbDataset.totalRows,
5282
+ },
5283
+ ...(isCustomerDbDatasetTool(toolId)
5284
+ ? {
5285
+ customer_db_dataset: {
5286
+ limit: options.customerDbDataset.limit,
5287
+ offset: options.customerDbDataset.offset,
5288
+ page_size: options.customerDbDataset.pageSize,
5289
+ total_rows:
5290
+ options.customerDbDataset.totalRows,
5291
+ },
5292
+ }
5293
+ : {}),
5294
+ }
5295
+ : {}),
5296
+ },
5078
5297
  ...(this.#options.integrationMode
5079
5298
  ? { integration_mode: this.#options.integrationMode }
5080
5299
  : {}),
@@ -22,6 +22,7 @@ export interface RowState {
22
22
  export interface ToolCallRequest {
23
23
  callId: string;
24
24
  cacheKey: string;
25
+ cacheable?: boolean;
25
26
  receiptKey?: string | null;
26
27
  force?: boolean;
27
28
  rowId: number;
@@ -0,0 +1,120 @@
1
+ export type QueryResultDatasetRequest = {
2
+ limit?: number;
3
+ offset?: number;
4
+ pageSize?: number;
5
+ totalRows?: number;
6
+ };
7
+
8
+ export const QUERY_RESULT_DATASET_PAGE_SIZE = 1_000;
9
+
10
+ function maskSqlForDatasetClassifier(sql: string): string {
11
+ let output = '';
12
+ let index = 0;
13
+ while (index < sql.length) {
14
+ const char = sql[index];
15
+ const next = sql[index + 1];
16
+ if (char === '-' && next === '-') {
17
+ const newline = sql.indexOf('\n', index + 2);
18
+ output += ' ';
19
+ index = newline === -1 ? sql.length : newline + 1;
20
+ continue;
21
+ }
22
+ if (char === '/' && next === '*') {
23
+ index += 2;
24
+ let depth = 1;
25
+ while (index < sql.length && depth > 0) {
26
+ if (sql[index] === '/' && sql[index + 1] === '*') {
27
+ depth += 1;
28
+ index += 2;
29
+ continue;
30
+ }
31
+ if (sql[index] === '*' && sql[index + 1] === '/') {
32
+ depth -= 1;
33
+ index += 2;
34
+ continue;
35
+ }
36
+ index += 1;
37
+ }
38
+ output += ' ';
39
+ continue;
40
+ }
41
+ if (char === "'" || char === '"') {
42
+ const quote = char;
43
+ output += ' ';
44
+ index += 1;
45
+ while (index < sql.length) {
46
+ const quoted = sql[index];
47
+ if (quoted === quote && sql[index + 1] === quote) {
48
+ index += 2;
49
+ continue;
50
+ }
51
+ index += 1;
52
+ if (quoted === quote) break;
53
+ }
54
+ continue;
55
+ }
56
+ output += char;
57
+ index += 1;
58
+ }
59
+ return output;
60
+ }
61
+
62
+ export function isCustomerDbDatasetTool(toolId: string): boolean {
63
+ return /^(?:customer_db_)?query_customer_db$/.test(toolId);
64
+ }
65
+
66
+ export function isSnowflakeQueryDatasetTool(toolId: string): boolean {
67
+ return /^snowflake_(?:snowflake_)?run_(?:semantic_)?query$/.test(toolId);
68
+ }
69
+
70
+ function isSnowflakeSemanticQueryDatasetTool(toolId: string): boolean {
71
+ return /^snowflake_(?:snowflake_)?run_semantic_query$/.test(toolId);
72
+ }
73
+
74
+ export function isQueryResultDatasetTool(toolId: string): boolean {
75
+ return isCustomerDbDatasetTool(toolId) || isSnowflakeQueryDatasetTool(toolId);
76
+ }
77
+
78
+ export function isQueryResultDatasetOperation(input: {
79
+ provider?: string | null;
80
+ operation?: string | null;
81
+ }): boolean {
82
+ return (
83
+ (input.provider === 'customer_db' &&
84
+ input.operation === 'query_customer_db') ||
85
+ (input.provider === 'snowflake' &&
86
+ (input.operation === 'snowflake_run_query' ||
87
+ input.operation === 'snowflake_run_semantic_query'))
88
+ );
89
+ }
90
+
91
+ function isReadOnlySelectOrWith(sql: string): boolean {
92
+ const normalized = maskSqlForDatasetClassifier(sql).trimStart().toLowerCase();
93
+ return (
94
+ (normalized.startsWith('select') &&
95
+ !/\bselect\b[\s\S]*?\binto\b/i.test(normalized)) ||
96
+ (normalized.startsWith('with') &&
97
+ !/\b(insert\s+into|update|delete\s+from|merge\s+into|select\b[\s\S]*?\binto|create|alter|drop|truncate)\b/i.test(
98
+ normalized,
99
+ ))
100
+ );
101
+ }
102
+
103
+ export function isQueryResultDatasetReadRequest(
104
+ toolId: string,
105
+ input: Record<string, unknown>,
106
+ ): boolean {
107
+ if (!isQueryResultDatasetTool(toolId)) return false;
108
+ if (isSnowflakeQueryDatasetTool(toolId) && input.write === true) return false;
109
+ if (isSnowflakeSemanticQueryDatasetTool(toolId)) return true;
110
+ const sql =
111
+ typeof input.sql === 'string'
112
+ ? input.sql
113
+ : typeof input.query === 'string'
114
+ ? input.query
115
+ : '';
116
+ return isReadOnlySelectOrWith(sql);
117
+ }
118
+
119
+ export const isCustomerDbDatasetReadRequest =
120
+ isQueryResultDatasetReadRequest;