deepline 0.3.228 → 0.3.229

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 (31) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/play-data-plane/contracts.ts +12 -0
  3. package/dist/bundling-sources/shared_libs/play-data-plane/index.ts +1 -0
  4. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-column-storage.ts +252 -0
  5. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +29 -8
  6. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +250 -37
  7. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +6 -1
  9. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +380 -183
  10. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -0
  11. package/dist/bundling-sources/shared_libs/plays/dataset.ts +16 -2
  12. package/dist/bundling-sources/shared_libs/plays/execution-pipeline.ts +72 -0
  13. package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +1 -3
  14. package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +9 -13
  15. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +28 -31
  16. package/dist/cli/index.js +1 -1
  17. package/dist/cli/index.mjs +1 -1
  18. package/dist/{compiler-manifest-Dpd2hEet.d.mts → compiler-manifest-n8ZtPv2h.d.mts} +31 -21
  19. package/dist/{compiler-manifest-Dpd2hEet.d.ts → compiler-manifest-n8ZtPv2h.d.ts} +31 -21
  20. package/dist/index.d.mts +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +6 -1
  23. package/dist/index.mjs +6 -1
  24. package/dist/install-integrity.json +4 -2
  25. package/dist/plays/bundle-play-file.d.mts +2 -2
  26. package/dist/plays/bundle-play-file.d.ts +2 -2
  27. package/dist/release.d.mts +1 -1
  28. package/dist/release.d.ts +1 -1
  29. package/dist/release.js +1 -1
  30. package/dist/release.mjs +1 -1
  31. package/package.json +1 -1
@@ -26,6 +26,7 @@ import {
26
26
  serializePlayDatasetCell,
27
27
  } from '../plays/dataset';
28
28
  import type { PlayDataset, PlayDatasetInput } from '../plays/dataset';
29
+ import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
29
30
  import {
30
31
  compileRequestsWithStrategy,
31
32
  executeChunkedRequests,
@@ -203,6 +204,10 @@ import {
203
204
  TOOL_EXECUTE_PROVIDER_RATE_RETRY_SNAPSHOT_HEADER,
204
205
  classifyToolExecuteHttpFailure,
205
206
  createToolExecuteHttpFailureAttemptTracker,
207
+ TOOL_EXECUTE_BARE_RATE_LIMIT_MAX_ATTEMPTS,
208
+ TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS,
209
+ TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS,
210
+ TOOL_EXECUTE_RATE_LIMIT_RETRY_DELAY_MAX_MS,
206
211
  parseToolExecuteRetryAfterMs,
207
212
  parseToolExecuteRetryAtMs,
208
213
  parseToolExecuteAuthScopeChangedError,
@@ -680,7 +685,7 @@ async function runCtxFetchPhase<T>(input: {
680
685
  callerSignal?: AbortSignal | null;
681
686
  controller: AbortController;
682
687
  deadline: CtxFetchDeadline;
683
- phase: 'headers' | 'body';
688
+ phase: 'headers' | 'body' | 'total';
684
689
  run: () => Promise<T>;
685
690
  }): Promise<T> {
686
691
  const remainingMs = remainingCtxFetchMs(input.deadline);
@@ -695,7 +700,9 @@ async function runCtxFetchPhase<T>(input: {
695
700
  const phaseLimitMs =
696
701
  input.phase === 'headers'
697
702
  ? input.deadline.headersMs
698
- : input.deadline.bodyMs;
703
+ : input.phase === 'body'
704
+ ? input.deadline.bodyMs
705
+ : remainingMs;
699
706
  const timeoutMs = Math.min(phaseLimitMs, remainingMs);
700
707
  const timeoutPhase = timeoutMs >= remainingMs ? 'total' : input.phase;
701
708
  let timeout: ReturnType<typeof setTimeout> | null = null;
@@ -744,19 +751,42 @@ async function sleepWithinCtxFetchDeadline(input: {
744
751
  delayMs: number;
745
752
  }): Promise<void> {
746
753
  const controller = new AbortController();
747
- await runCtxFetchPhase({
748
- callerSignal: input.callerSignal,
749
- controller,
750
- deadline: input.deadline,
751
- phase: 'headers',
752
- run: () =>
753
- new Promise<void>((resolve) => {
754
- setTimeout(
755
- resolve,
756
- Math.min(input.delayMs, remainingCtxFetchMs(input.deadline)),
757
- );
758
- }),
759
- });
754
+ let timer: ReturnType<typeof setTimeout> | undefined;
755
+ try {
756
+ await runCtxFetchPhase({
757
+ callerSignal: input.callerSignal,
758
+ controller,
759
+ deadline: input.deadline,
760
+ phase: 'total',
761
+ run: () =>
762
+ new Promise<void>((resolve) => {
763
+ timer = setTimeout(resolve, input.delayMs);
764
+ }),
765
+ });
766
+ } finally {
767
+ if (timer !== undefined) clearTimeout(timer);
768
+ }
769
+ }
770
+
771
+ function isCtxFetchBackpressure(response: Response, bodyText: string): boolean {
772
+ if (response.status === 429) return true;
773
+ if (response.status !== 503) return false;
774
+ // This Salesforce endpoint reports a rejected burst as 503. Match the final
775
+ // destination and exact response contract; arbitrary 503s remain failures.
776
+ try {
777
+ const destination = new URL(response.url);
778
+ const body: unknown = JSON.parse(bodyText);
779
+ return (
780
+ destination.protocol === 'https:' &&
781
+ destination.hostname.endsWith('.salesforce-sites.com') &&
782
+ body !== null &&
783
+ typeof body === 'object' &&
784
+ !Array.isArray(body) &&
785
+ (body as Record<string, unknown>).status === 'burst_circuit_open'
786
+ );
787
+ } catch {
788
+ return false;
789
+ }
760
790
  }
761
791
 
762
792
  async function readCtxFetchBody(input: {
@@ -1673,6 +1703,69 @@ function rowsFromUnknown(value: unknown): Record<string, unknown>[] {
1673
1703
  );
1674
1704
  }
1675
1705
 
1706
+ function customerDbColumnTypes(
1707
+ columns: unknown,
1708
+ rows: readonly Record<string, unknown>[],
1709
+ ): PlayDatasetColumnTypes | undefined {
1710
+ if (!Array.isArray(columns)) return undefined;
1711
+ // Core PostgreSQL OIDs only. Extension/domain identities are database-local,
1712
+ // and source SQL types never authorize coercing the values returned by a tool.
1713
+ const coreValueTypes: Record<
1714
+ number,
1715
+ PlayDatasetColumnTypes[string]['valueType']
1716
+ > = {
1717
+ 16: 'boolean',
1718
+ 20: 'string',
1719
+ 21: 'number',
1720
+ 23: 'number',
1721
+ 25: 'string',
1722
+ 114: 'json',
1723
+ 700: 'number',
1724
+ 701: 'number',
1725
+ 1042: 'string',
1726
+ 1043: 'string',
1727
+ 1082: 'string',
1728
+ 1083: 'string',
1729
+ 1114: 'string',
1730
+ 1184: 'string',
1731
+ 1266: 'string',
1732
+ 1700: 'string',
1733
+ 2950: 'string',
1734
+ 3802: 'json',
1735
+ };
1736
+ const entries: Array<[string, PlayDatasetColumnTypes[string]]> = [];
1737
+ for (const value of columns) {
1738
+ const column = recordOrNull(value);
1739
+ const postgresTypeId = column?.data_type_id;
1740
+ if (
1741
+ typeof column?.name !== 'string' ||
1742
+ typeof postgresTypeId !== 'number' ||
1743
+ !Object.hasOwn(coreValueTypes, postgresTypeId)
1744
+ )
1745
+ continue;
1746
+ const observed = new Set<PlayDatasetColumnTypes[string]['valueType']>();
1747
+ for (const row of rows) {
1748
+ const cell = row[column.name];
1749
+ if (cell === null || cell === undefined) continue;
1750
+ observed.add(
1751
+ typeof cell === 'boolean'
1752
+ ? 'boolean'
1753
+ : typeof cell === 'string'
1754
+ ? 'string'
1755
+ : typeof cell === 'number' && Number.isFinite(cell)
1756
+ ? 'number'
1757
+ : 'json',
1758
+ );
1759
+ }
1760
+ const valueType =
1761
+ postgresTypeId === 114 || postgresTypeId === 3802 || observed.size > 1
1762
+ ? 'json'
1763
+ : ([...observed][0] ?? coreValueTypes[postgresTypeId]!);
1764
+ entries.push([column.name, { postgresTypeId, valueType }]);
1765
+ }
1766
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
1767
+ }
1768
+
1676
1769
  function finiteNonNegativeInteger(value: unknown): number | null {
1677
1770
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
1678
1771
  return null;
@@ -7914,6 +8007,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
7914
8007
  rows.length >= effectiveCount ? rows.slice(0, effectiveCount) : null,
7915
8008
  sourceLabel: 'query result rows',
7916
8009
  tableNamespace: null,
8010
+ columnTypes: isCustomerDbDatasetTool(toolId)
8011
+ ? customerDbColumnTypes(raw?.columns, rows)
8012
+ : undefined,
7917
8013
  resolvers: {
7918
8014
  count: async () => Math.min(totalRows, datasetLimit),
7919
8015
  at: async (index) => (await fetchPage(index, 1))[0],
@@ -8472,6 +8568,18 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8472
8568
  let itemsToProcess: Array<Record<string, unknown>> = [];
8473
8569
  let itemOriginalIndexes: number[] = [];
8474
8570
  const datasetColumnNames = Object.keys(input);
8571
+ // Generated fields no longer describe the source column, even when they
8572
+ // retain its name. Every writer still validates the actual incoming values.
8573
+ const inputColumnTypes = isPlayDataset(items)
8574
+ ? items.columnTypes
8575
+ : undefined;
8576
+ const unchangedColumnTypes = inputColumnTypes
8577
+ ? Object.fromEntries(
8578
+ Object.entries(inputColumnTypes).filter(
8579
+ ([field]) => !datasetColumnNames.includes(field),
8580
+ ),
8581
+ )
8582
+ : undefined;
8475
8583
  const stripFieldOutputs = (row: Record<string, unknown>) => {
8476
8584
  const stripped = cloneCsvAliasedRow(row);
8477
8585
  for (const fieldName of datasetColumnNames) {
@@ -8863,6 +8971,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8863
8971
  forceRefresh: this.#options.cachePolicy?.forceToolRefresh === true,
8864
8972
  inputOffset: page.offset,
8865
8973
  mode: options?.mode,
8974
+ columnTypes: unchangedColumnTypes,
8866
8975
  },
8867
8976
  );
8868
8977
  resolvedTableNamespace = normalizeTableNamespace(
@@ -9424,6 +9533,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9424
9533
  ? immediateMaterializedRows
9425
9534
  : null,
9426
9535
  tableNamespace: resolvedTableNamespace,
9536
+ columnTypes: unchangedColumnTypes,
9427
9537
  workProgress: {
9428
9538
  total: totalInputCount,
9429
9539
  executed: executedCount,
@@ -9666,6 +9776,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9666
9776
  staticPipeline: this.currentStaticPipeline,
9667
9777
  forceRefresh: this.#options.cachePolicy?.forceToolRefresh === true,
9668
9778
  mode: options?.mode,
9779
+ columnTypes: unchangedColumnTypes,
9669
9780
  },
9670
9781
  );
9671
9782
  resolvedTableNamespace = normalizeTableNamespace(
@@ -10175,6 +10286,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10175
10286
  .map((row) => this.toMaterializedOutputRow(row)),
10176
10287
  residentRows: terminalRows,
10177
10288
  tableNamespace: resolvedTableNamespace,
10289
+ columnTypes: unchangedColumnTypes,
10178
10290
  workProgress: {
10179
10291
  total: totalInputCount,
10180
10292
  executed: executedCount,
@@ -14991,16 +15103,30 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14991
15103
  let response: Response | null = null;
14992
15104
  let bodyText: string | null = null;
14993
15105
  try {
14994
- const canRetryTransport = ['GET', 'HEAD', 'OPTIONS'].includes(
14995
- method,
14996
- );
15106
+ // Only replay a body whose bytes are stable. In particular, a
15107
+ // consumed ReadableStream cannot be retried even with a write key.
15108
+ const replayableBody =
15109
+ init.body == null || typeof init.body === 'string';
15110
+ const canRetryTransport =
15111
+ replayableBody && ['GET', 'HEAD', 'OPTIONS'].includes(method);
14997
15112
  const { safePublicFetch } = await loadSafeFetch();
14998
- for (
14999
- let attempt = 1;
15000
- attempt <= FETCH_TRANSPORT_MAX_ATTEMPTS;
15001
- attempt += 1
15002
- ) {
15113
+ let transportAttempt = 1;
15114
+ let rateLimitAttempt = 0;
15115
+ while (true) {
15003
15116
  const attemptController = new AbortController();
15117
+ let permit: Awaited<
15118
+ ReturnType<RuntimeResourceGovernor['acquireProviderPermit']>
15119
+ > | null = null;
15120
+ let dispatched = false;
15121
+ const releasePhysicalPermit = async () => {
15122
+ const current = permit;
15123
+ permit = null;
15124
+ if (!current) return;
15125
+ if (dispatched) current.release();
15126
+ else await current.cancel();
15127
+ dispatched = false;
15128
+ };
15129
+ let transportRetry = false;
15004
15130
  try {
15005
15131
  const attemptInit = {
15006
15132
  ...fetchInit,
@@ -15012,10 +15138,53 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15012
15138
  callerSignal: init.signal,
15013
15139
  controller: attemptController,
15014
15140
  deadline: fetchDeadline,
15015
- phase: 'headers',
15141
+ phase: 'total',
15016
15142
  run: () =>
15017
15143
  safePublicFetch(url, attemptInit, {
15018
- fetchImpl: this.#options.fetchImpl,
15144
+ // safePublicFetch validates each URL before invoking this
15145
+ // wrapper. Redirect hops are physical dispatches too.
15146
+ fetchImpl: async (physicalUrl, physicalInit) => {
15147
+ await releasePhysicalPermit();
15148
+ const signal = attemptInit.signal;
15149
+ permit = await runCtxFetchPhase({
15150
+ callerSignal: init.signal,
15151
+ controller: attemptController,
15152
+ deadline: fetchDeadline,
15153
+ phase: 'total',
15154
+ run: async () => {
15155
+ for (;;) {
15156
+ const acquired =
15157
+ await this.resourceGovernor.acquireProviderPermit(
15158
+ { toolId: CTX_FETCH_EGRESS_TOOL_ID, signal },
15159
+ );
15160
+ try {
15161
+ signal.throwIfAborted();
15162
+ const valid =
15163
+ !acquired.validate ||
15164
+ (await acquired.validate());
15165
+ signal.throwIfAborted();
15166
+ if (valid) return acquired;
15167
+ } catch (error) {
15168
+ await acquired.cancel();
15169
+ throw error;
15170
+ }
15171
+ await acquired.cancel();
15172
+ }
15173
+ },
15174
+ });
15175
+ return await runCtxFetchPhase({
15176
+ callerSignal: init.signal,
15177
+ controller: attemptController,
15178
+ deadline: fetchDeadline,
15179
+ phase: 'headers',
15180
+ run: () => {
15181
+ dispatched = true;
15182
+ return (
15183
+ this.#options.fetchImpl ?? globalThis.fetch
15184
+ )(physicalUrl, physicalInit);
15185
+ },
15186
+ });
15187
+ },
15019
15188
  sensitiveHeaders: Object.keys(secretHeaderMarkers),
15020
15189
  stripHeadersOnCrossOriginRedirect: true,
15021
15190
  headersOnCrossOriginRedirect: {
@@ -15029,7 +15198,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15029
15198
  deadline: fetchDeadline,
15030
15199
  response,
15031
15200
  });
15032
- break;
15033
15201
  } catch (error) {
15034
15202
  void response?.body?.cancel(error).catch(() => undefined);
15035
15203
  response = null;
@@ -15045,22 +15213,67 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15045
15213
  error instanceof Error ? error.message : String(error);
15046
15214
  if (
15047
15215
  canRetryTransport &&
15048
- attempt < FETCH_TRANSPORT_MAX_ATTEMPTS
15216
+ transportAttempt < FETCH_TRANSPORT_MAX_ATTEMPTS
15049
15217
  ) {
15050
15218
  this.runtimeLog(
15051
- `ctx.fetch(${method} ${url}) transport failed on attempt ${attempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}; retrying: ${message}`,
15219
+ `ctx.fetch(${method} ${url}) transport failed on attempt ${transportAttempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}; retrying: ${message}`,
15220
+ );
15221
+ transportRetry = true;
15222
+ } else {
15223
+ throw new Error(
15224
+ `ctx.fetch(${method} ${url}) failed on attempt ${transportAttempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
15052
15225
  );
15053
- await sleepWithinCtxFetchDeadline({
15054
- callerSignal: init.signal,
15055
- deadline: fetchDeadline,
15056
- delayMs: FETCH_TRANSPORT_RETRY_DELAY_MS * attempt,
15057
- });
15058
- continue;
15059
15226
  }
15060
- throw new Error(
15061
- `ctx.fetch(${method} ${url}) failed on attempt ${attempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
15062
- );
15227
+ } finally {
15228
+ await releasePhysicalPermit();
15063
15229
  }
15230
+ if (transportRetry) {
15231
+ await sleepWithinCtxFetchDeadline({
15232
+ callerSignal: init.signal,
15233
+ deadline: fetchDeadline,
15234
+ delayMs: FETCH_TRANSPORT_RETRY_DELAY_MS * transportAttempt,
15235
+ });
15236
+ transportAttempt += 1;
15237
+ continue;
15238
+ }
15239
+ if (
15240
+ !response ||
15241
+ bodyText === null ||
15242
+ !replayableBody ||
15243
+ !isCtxFetchBackpressure(response, bodyText)
15244
+ )
15245
+ break;
15246
+ rateLimitAttempt += 1;
15247
+ const retryAfter = response.headers.get('retry-after');
15248
+ const attemptCap = retryAfter?.trim()
15249
+ ? TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS
15250
+ : TOOL_EXECUTE_BARE_RATE_LIMIT_MAX_ATTEMPTS;
15251
+ if (rateLimitAttempt >= attemptCap) break;
15252
+ const delayMs = Math.max(
15253
+ parseToolExecuteRetryAfterMs(retryAfter),
15254
+ Math.min(
15255
+ TOOL_EXECUTE_RATE_LIMIT_RETRY_DELAY_MAX_MS,
15256
+ TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS * rateLimitAttempt,
15257
+ ),
15258
+ );
15259
+ this.runtimeLog(
15260
+ `[runtime.ctx_fetch_backpressure] ${JSON.stringify({
15261
+ key: normalizedKey,
15262
+ method,
15263
+ origin: new URL(response.url || url).origin,
15264
+ http_status: response.status,
15265
+ attempt: rateLimitAttempt,
15266
+ max_attempts: attemptCap,
15267
+ retry_delay_ms: delayMs,
15268
+ })}`,
15269
+ );
15270
+ // Destination feedback must not cool the shared generic_http
15271
+ // bucket used by unrelated websites. Pace this logical request.
15272
+ await sleepWithinCtxFetchDeadline({
15273
+ callerSignal: init.signal,
15274
+ deadline: fetchDeadline,
15275
+ delayMs,
15276
+ });
15064
15277
  }
15065
15278
  if (!response) {
15066
15279
  throw new Error(
@@ -7,6 +7,7 @@ import type {
7
7
  } from './cell-provenance';
8
8
  import type { PlayBundleArtifact } from '../plays/artifact-types';
9
9
  import type { PlayRunContractSnapshot } from '../plays/contracts';
10
+ import type { PlayDatasetColumnTypes } from '../play-data-plane/contracts';
10
11
  import type { PlayStructuredDefinition } from '../plays/definition';
11
12
  import type { PlayStaticPipeline } from '../plays/static-pipeline';
12
13
  import type {
@@ -891,6 +892,7 @@ export interface ContextOptions {
891
892
  forceRefresh?: boolean;
892
893
  inputOffset?: number;
893
894
  mode?: RuntimeDatasetRunOptions['mode'];
895
+ columnTypes?: PlayDatasetColumnTypes;
894
896
  },
895
897
  ) => Promise<MapStartResult>;
896
898
  /**
@@ -41,6 +41,7 @@ import {
41
41
  runtimeLimitExceededFailure,
42
42
  } from '../run-failure';
43
43
  import { createPlayContext } from '../context';
44
+ import { augmentSheetContractWithDatasetFields } from '../../play-data-plane/sheet-contract';
44
45
  import type { RuntimeApiResponseMeta } from '../app-runtime-api';
45
46
  import { onMemoryPressure } from '../memory-pressure-signal';
46
47
  import {
@@ -2452,7 +2453,11 @@ async function run(
2452
2453
  playName,
2453
2454
  tableNamespace,
2454
2455
  playInput: runtimeInputObject,
2455
- sheetContract,
2456
+ sheetContract: augmentSheetContractWithDatasetFields({
2457
+ contract: sheetContract,
2458
+ rows: items,
2459
+ columnTypes: mapContext.columnTypes,
2460
+ }),
2456
2461
  rows: items,
2457
2462
  inputOffset: mapContext.inputOffset,
2458
2463
  mode: mapContext.mode,