deepline 0.1.286 → 0.1.288

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.
@@ -83,6 +83,10 @@ import type { PlayStagedFileRef } from './plays/local-file-discovery.js';
83
83
  import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest.js';
84
84
  import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
85
85
  import { RUNTIME_ENVIRONMENT_TOKEN_HEADER } from '../../shared_libs/play-runtime/coordinator-headers.js';
86
+ import {
87
+ THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS,
88
+ usesExtendedTheirstackJobSearchBudget,
89
+ } from '../../shared_libs/integrations/theirstack-execution-policy.js';
86
90
  import {
87
91
  normalizePlayRuntimeEnvironment,
88
92
  normalizePlayRuntimeNamespace,
@@ -398,6 +402,12 @@ function resolveToolExecuteTimeoutMs(
398
402
  return Math.floor(requestedTimeoutMs) + APIFY_SYNC_RESPONSE_GRACE_MS;
399
403
  }
400
404
  }
405
+ // Provider-specific slow-request policies live in shared modules so the SDK
406
+ // and server classify the same payloads. If more providers need this, replace
407
+ // these individual checks with a shared policy registry, not a wider default.
408
+ if (usesExtendedTheirstackJobSearchBudget(normalized, input)) {
409
+ return THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS;
410
+ }
401
411
  return normalized === 'deeplineagent' ||
402
412
  normalized === 'deeplineagent_deeplineagent' ||
403
413
  normalized === 'ai_inference' ||
@@ -3913,9 +3923,7 @@ export class DeeplineClient {
3913
3923
  * id for the compact inventory. Prefer `client.monitors.available(...)`.
3914
3924
  */
3915
3925
  async getMonitorsAvailable(
3916
- toolIdOrOptions?:
3917
- | string
3918
- | (MonitorsAvailableOptions & { tool?: string }),
3926
+ toolIdOrOptions?: string | (MonitorsAvailableOptions & { tool?: string }),
3919
3927
  maybeOptions?: MonitorsAvailableOptions,
3920
3928
  ): Promise<MonitorsAvailableResult> {
3921
3929
  const positionalTool =
@@ -3995,7 +4003,8 @@ export class DeeplineClient {
3995
4003
  ): Promise<MonitorsListResult> {
3996
4004
  const params = new URLSearchParams();
3997
4005
  if (options?.status) params.set('status', options.status);
3998
- if (options?.limit !== undefined) params.set('limit', String(options.limit));
4006
+ if (options?.limit !== undefined)
4007
+ params.set('limit', String(options.limit));
3999
4008
  if (options?.cursor) params.set('cursor', options.cursor);
4000
4009
  if (options?.compact) params.set('compact', 'true');
4001
4010
  const query = params.toString();
@@ -155,7 +155,7 @@ export const SDK_RELEASE = {
155
155
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
156
156
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
157
157
  // Operators use the checkout-local deepline-admin binary instead.
158
- version: '0.1.286',
158
+ version: '0.1.288',
159
159
  contracts: {
160
160
  api: {
161
161
  name: 'sdk-http-api',
@@ -0,0 +1,97 @@
1
+ /**
2
+ * TheirStack-specific thresholds backed by observed job-search latency. They
3
+ * are deliberately not global provider defaults: a larger timeout would hide
4
+ * unrelated provider failures and hold runtime permits longer for every tool.
5
+ *
6
+ * The reusable part is the deadline ordering below:
7
+ *
8
+ * client > runtime permit wait + provider request + response serialization
9
+ *
10
+ * A provider with the same measured failure mode should add its own shared
11
+ * request classifier and budgets, consumed by both its server runtime hook and
12
+ * the SDK timeout resolver. If several providers need that shape, promote the
13
+ * provider policies into a shared registry while keeping each provider's
14
+ * classifier and measured thresholds separate.
15
+ */
16
+ export const THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135_000;
17
+
18
+ /**
19
+ * The provider request may spend up to 60 seconds waiting for its runtime
20
+ * permit before the provider timeout starts. Keep another 15 seconds for the
21
+ * execute route to serialize and return the response.
22
+ */
23
+ export const THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS =
24
+ 60_000 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15_000;
25
+
26
+ const LONG_JOB_SEARCH_WINDOW_MS = 365 * 24 * 60 * 60 * 1_000;
27
+ const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
28
+
29
+ function parseDateOnly(value: unknown): number | null {
30
+ if (typeof value !== 'string' || !DATE_ONLY_PATTERN.test(value)) {
31
+ return null;
32
+ }
33
+
34
+ const timestamp = Date.parse(`${value}T00:00:00.000Z`);
35
+ if (!Number.isFinite(timestamp)) {
36
+ return null;
37
+ }
38
+
39
+ return new Date(timestamp).toISOString().slice(0, 10) === value
40
+ ? timestamp
41
+ : null;
42
+ }
43
+
44
+ function usesLongExplicitDateWindow(payload: Record<string, unknown>): boolean {
45
+ const postedAtGte = parseDateOnly(payload.posted_at_gte);
46
+ const postedAtLte = parseDateOnly(payload.posted_at_lte);
47
+
48
+ if (postedAtGte !== null && postedAtLte !== null) {
49
+ return postedAtLte - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
50
+ }
51
+
52
+ if (postedAtGte !== null) {
53
+ return Date.now() - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
54
+ }
55
+
56
+ // With no lower bound, `posted_at_lte` searches the provider's full history
57
+ // through the supplied date and needs the same slow-search budget.
58
+ return postedAtLte !== null && payload.posted_at_gte == null;
59
+ }
60
+
61
+ function parseMaxAgeDays(value: unknown): number | null {
62
+ if (typeof value === 'number') {
63
+ return Number.isFinite(value) ? value : null;
64
+ }
65
+ if (typeof value !== 'string' || !/^-?\d+$/.test(value.trim())) {
66
+ return null;
67
+ }
68
+
69
+ const parsed = Number(value.trim());
70
+ return Number.isSafeInteger(parsed) ? parsed : null;
71
+ }
72
+
73
+ /**
74
+ * Classifies the TheirStack request shapes known to perform substantially more
75
+ * work. Keeping this predicate shared prevents the server from accepting a
76
+ * slow request that the SDK abandons at its default deadline.
77
+ *
78
+ * Do not add other providers to this predicate. Give each provider an
79
+ * evidence-backed classifier so its normal requests keep the global timeout.
80
+ */
81
+ export function usesExtendedTheirstackJobSearchBudget(
82
+ endpointId: string,
83
+ payload: Record<string, unknown>,
84
+ ): boolean {
85
+ if (endpointId !== 'theirstack_job_search') {
86
+ return false;
87
+ }
88
+
89
+ if (payload.include_total_results === true) {
90
+ return true;
91
+ }
92
+
93
+ const maxAgeDays = parseMaxAgeDays(payload.posted_at_max_age_days);
94
+ return maxAgeDays !== null && maxAgeDays >= 365
95
+ ? true
96
+ : usesLongExplicitDateWindow(payload);
97
+ }
@@ -309,6 +309,10 @@ const MAP_FRAME_FLUSH_INTERVAL_MS = 250;
309
309
  const TOOL_BATCH_COALESCE_WINDOW_MS = 5;
310
310
  const TOOL_SCALAR_COALESCE_WINDOW_MS = 5;
311
311
  const TOOL_RETRY_AFTER_FALLBACK_MS = 1_000;
312
+ // The receipt gateway fully buffers the integration response before sending
313
+ // headers to the runner. Once those headers arrive, a long body stall is a
314
+ // broken gateway-to-runner connection, not slow provider work.
315
+ export const TOOL_EXECUTE_RESPONSE_BODY_TIMEOUT_MS = 30_000;
312
316
  const TOOL_RETRY_HEARTBEAT_INTERVAL_MS = 30_000;
313
317
  const DEEPLINEAGENT_TOOL_RUNTIME_TIMEOUT_MS = 15 * 60 * 1000;
314
318
  // Every runtime-API tool fetch needs a client-side deadline. Without one, a
@@ -352,6 +356,35 @@ class ToolExecuteResponseBodyTransportError extends Error {
352
356
  }
353
357
  }
354
358
 
359
+ class ToolExecuteResponseBodyTimeoutError extends Error {
360
+ constructor(toolId: string) {
361
+ super(
362
+ `Tool ${toolId} response body was not delivered within ${TOOL_EXECUTE_RESPONSE_BODY_TIMEOUT_MS}ms after response headers.`,
363
+ );
364
+ this.name = 'ToolExecuteResponseBodyTimeoutError';
365
+ }
366
+ }
367
+
368
+ async function readToolExecuteResponseBody<T>(input: {
369
+ toolId: string;
370
+ abortController: AbortController | null;
371
+ read: () => Promise<T>;
372
+ }): Promise<T> {
373
+ let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
374
+ const timeout = new Promise<never>((_resolve, reject) => {
375
+ timeoutHandle = setTimeout(() => {
376
+ const error = new ToolExecuteResponseBodyTimeoutError(input.toolId);
377
+ input.abortController?.abort(error);
378
+ reject(error);
379
+ }, TOOL_EXECUTE_RESPONSE_BODY_TIMEOUT_MS);
380
+ });
381
+ try {
382
+ return await Promise.race([input.read(), timeout]);
383
+ } finally {
384
+ if (timeoutHandle) clearTimeout(timeoutHandle);
385
+ }
386
+ }
387
+
355
388
  class ToolExecuteInvalidJsonError extends Error {
356
389
  readonly cause: unknown;
357
390
 
@@ -1289,6 +1322,7 @@ export class PlayContextImpl {
1289
1322
  createSecretRedactionContext();
1290
1323
  private mapInvocationIndex = 0;
1291
1324
  private readonly stepCallIndexByKey = new Map<string, number>();
1325
+ private readonly toolCallIndexByKey = new Map<string, number>();
1292
1326
  /**
1293
1327
  * Parent-level inline-child aggregates. Maintained only by the single-writer
1294
1328
  * progress path (never per child) so concurrent fan-out cannot contend. See
@@ -3281,7 +3315,9 @@ export class PlayContextImpl {
3281
3315
  const executionNonce =
3282
3316
  typeof wrapped.job_id === 'string' && wrapped.job_id.trim()
3283
3317
  ? wrapped.job_id.trim()
3284
- : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
3318
+ : stableDigest(
3319
+ `${this.currentRunId}:${toolId}:${stableStringify(originalRequestInput)}:${datasetLimit}`,
3320
+ );
3285
3321
  const datasetId = `tool-list:${sha256Hex(
3286
3322
  `${toolId}:${this.currentRunId}:${sql}:${datasetLimit}:${executionNonce}`,
3287
3323
  )}`;
@@ -3294,6 +3330,10 @@ export class PlayContextImpl {
3294
3330
  toolId,
3295
3331
  originalRequestInput,
3296
3332
  {
3333
+ durableCallReceiptKey: `${buildDurableToolReceiptPrefix({
3334
+ orgId: this.#options.orgId ?? 'unknown-org',
3335
+ toolId,
3336
+ })}${stableDigest(`${datasetId}:${offset}:${limit}`)}`,
3297
3337
  timeoutMs: resolveToolRuntimeTimeoutMs(toolId),
3298
3338
  customerDbDataset: {
3299
3339
  limit: datasetLimit,
@@ -6461,6 +6501,14 @@ export class PlayContextImpl {
6461
6501
  toolId,
6462
6502
  requestInput: input,
6463
6503
  });
6504
+ const store = rowContext.getStore();
6505
+ let logicalCallId: string | null = null;
6506
+ if (!store) {
6507
+ const callIndexKey = `${this.currentExecutionScope.receipt.namespace}:workflow:${normalizedKey}:${toolRequestIdentity}`;
6508
+ const callIndex = this.toolCallIndexByKey.get(callIndexKey) ?? 0;
6509
+ this.toolCallIndexByKey.set(callIndexKey, callIndex + 1);
6510
+ logicalCallId = stableDigest(`${callIndexKey}:${callIndex}`);
6511
+ }
6464
6512
  let executionAuthScopeDigest =
6465
6513
  (await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null;
6466
6514
  const eventWaitHandler =
@@ -6476,7 +6524,6 @@ export class PlayContextImpl {
6476
6524
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
6477
6525
  });
6478
6526
  const checkpointCacheKeys = [durableCacheKey];
6479
- const store = rowContext.getStore();
6480
6527
 
6481
6528
  const executeTool = async (context?: {
6482
6529
  leaseId?: string | null;
@@ -6531,18 +6578,28 @@ export class PlayContextImpl {
6531
6578
  : `Calling tool: ${toolId}`,
6532
6579
  );
6533
6580
  const directReceiptLeaseId = context?.leaseId?.trim() || null;
6581
+ if (!logicalCallId) {
6582
+ throw new Error(
6583
+ 'Direct tool execution requires a stable logical call identity.',
6584
+ );
6585
+ }
6586
+ const physicalDirectKey = cacheableToolResult
6587
+ ? directCacheKey
6588
+ : `${buildDurableToolReceiptPrefix({
6589
+ orgId: this.#options.orgId ?? 'unknown-org',
6590
+ toolId,
6591
+ })}${stableDigest(`${this.currentRunId}:${logicalCallId}:always-fresh`)}`;
6534
6592
  const execution = await this.callToolExecutionAPI(toolId, input, {
6535
- ...(cacheableToolResult
6536
- ? {
6537
- durableCallReceiptKey: directCacheKey,
6538
- executionAuthScopeDigest,
6539
- providerIdempotencyKey: this.providerIdempotencyKeyForToolCall({
6540
- cacheKey: directCacheKey,
6541
- force: toolCachePolicy.force,
6542
- leaseId: directReceiptLeaseId,
6543
- }),
6544
- }
6545
- : {}),
6593
+ durableCallReceiptKey: physicalDirectKey,
6594
+ executionAuthScopeDigest,
6595
+ providerIdempotencyKey: cacheableToolResult
6596
+ ? this.providerIdempotencyKeyForToolCall({
6597
+ cacheKey: directCacheKey,
6598
+ force: toolCachePolicy.force,
6599
+ leaseId: directReceiptLeaseId,
6600
+ logicalCallId,
6601
+ })
6602
+ : physicalDirectKey,
6546
6603
  timeoutMs: resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs),
6547
6604
  ...(directReceiptLeaseId &&
6548
6605
  (this.#options.markRuntimeStepReceiptRunning ||
@@ -6692,7 +6749,7 @@ export class PlayContextImpl {
6692
6749
  ) {
6693
6750
  try {
6694
6751
  const toolRetryPolicy = await this.#options
6695
- .getToolRetryPolicy?.(toolId)
6752
+ .getToolRetryPolicy?.(toolId, input)
6696
6753
  .catch(() => null);
6697
6754
  return await this.executeWithRuntimeReceipt(
6698
6755
  'tool',
@@ -8547,11 +8604,17 @@ export class PlayContextImpl {
8547
8604
  cacheKey: string;
8548
8605
  force?: boolean;
8549
8606
  leaseId?: string | null;
8607
+ logicalCallId?: string;
8550
8608
  }): string {
8551
- const fallbackAttemptId =
8552
- input.force === true && !input.leaseId
8553
- ? `${this.currentReceiptOwnerRunId}:${crypto.randomUUID()}`
8554
- : null;
8609
+ let fallbackAttemptId: string | null = null;
8610
+ if (input.force === true && !input.leaseId) {
8611
+ if (!input.logicalCallId) {
8612
+ throw new Error(
8613
+ 'Forced tool execution without a receipt lease requires a stable logical call identity.',
8614
+ );
8615
+ }
8616
+ fallbackAttemptId = `${this.currentRunId}:${input.logicalCallId}`;
8617
+ }
8555
8618
  return buildDurableToolProviderIdempotencyKey({
8556
8619
  receiptKey: input.cacheKey,
8557
8620
  force: input.force,
@@ -8570,7 +8633,13 @@ export class PlayContextImpl {
8570
8633
  'executorToken and baseUrl are required for tool API calls (cloud execution only)',
8571
8634
  );
8572
8635
  }
8573
- const url = `${this.#options.baseUrl}/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
8636
+ const requestsDurableInvocationFence =
8637
+ this.#options.requestDurableInvocationFence === true ||
8638
+ this.#options.durableInvocationFence === true;
8639
+ const executeSuffix = requestsDurableInvocationFence
8640
+ ? 'execute-fenced-v1'
8641
+ : 'execute';
8642
+ const url = `${this.#options.baseUrl}/api/v2/integrations/${encodeURIComponent(toolId)}/${executeSuffix}`;
8574
8643
  const timeoutMs = resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs);
8575
8644
  const provider = toolId.split(/[._]/)[0]?.trim() || 'provider';
8576
8645
  const activityId = `provider:${toolId}`;
@@ -8607,12 +8676,89 @@ export class PlayContextImpl {
8607
8676
  async (span) => {
8608
8677
  const httpFailureAttempts =
8609
8678
  createToolExecuteHttpFailureAttemptTracker();
8679
+ // Snapshot the caller-controlled payload exactly once. The replay
8680
+ // decision and every physical attempt must refer to the same bytes,
8681
+ // even if the original object has getters or is later mutated.
8682
+ const toolInputSnapshot = JSON.parse(JSON.stringify(input)) as Record<
8683
+ string,
8684
+ unknown
8685
+ >;
8610
8686
  const retryPolicy = await this.#options
8611
- .getToolRetryPolicy?.(toolId)
8687
+ .getToolRetryPolicy?.(toolId, toolInputSnapshot)
8612
8688
  .catch(() => null);
8613
8689
  const retrySafeTransientHttp =
8614
8690
  retryPolicy?.retrySafeTransientHttp === true;
8691
+ const invocationOrgId = this.#options.orgId?.trim() ?? '';
8692
+ const durableCallReceiptKey =
8693
+ options?.durableCallReceiptKey?.trim() ||
8694
+ (requestsDurableInvocationFence && invocationOrgId
8695
+ ? `${buildDurableToolReceiptPrefix({
8696
+ orgId: invocationOrgId,
8697
+ toolId,
8698
+ })}${stableDigest(
8699
+ `transport:${this.currentRunId}:${toolId}:${crypto.randomUUID()}`,
8700
+ )}`
8701
+ : null);
8702
+ const executionAuthScopeDigest = options?.executionAuthScopeDigest
8703
+ ? options.executionAuthScopeDigest.trim() || null
8704
+ : ((await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null);
8705
+ const providerIdempotencyKey =
8706
+ options?.providerIdempotencyKey?.trim() || durableCallReceiptKey;
8707
+ // Correlation identity is stable across every transport retry,
8708
+ // including calls without a durable receipt.
8709
+ const deeplineRequestId = providerIdempotencyKey
8710
+ ? `ctx-tool-${stableDigest(providerIdempotencyKey).slice(0, 32)}`
8711
+ : `ctx-tool-${crypto.randomUUID()}`;
8712
+ const serializedRequestBody = (invocationAttempt: number) =>
8713
+ JSON.stringify({
8714
+ payload: toolInputSnapshot,
8715
+ metadata: {
8716
+ parent_run_id: this.#options.runId,
8717
+ invocation_attempt: invocationAttempt,
8718
+ ...(requestsDurableInvocationFence
8719
+ ? { invocation_fence_version: 1 }
8720
+ : {}),
8721
+ ...(durableCallReceiptKey
8722
+ ? {
8723
+ durable_call_receipt_key: durableCallReceiptKey,
8724
+ ...(executionAuthScopeDigest
8725
+ ? {
8726
+ execution_auth_scope_digest:
8727
+ executionAuthScopeDigest,
8728
+ }
8729
+ : {}),
8730
+ }
8731
+ : {}),
8732
+ ...(options?.customerDbDataset
8733
+ ? {
8734
+ query_result_dataset: {
8735
+ limit: options.customerDbDataset.limit,
8736
+ offset: options.customerDbDataset.offset,
8737
+ page_size: options.customerDbDataset.pageSize,
8738
+ total_rows: options.customerDbDataset.totalRows,
8739
+ },
8740
+ ...(isCustomerDbDatasetTool(toolId)
8741
+ ? {
8742
+ customer_db_dataset: {
8743
+ limit: options.customerDbDataset.limit,
8744
+ offset: options.customerDbDataset.offset,
8745
+ page_size: options.customerDbDataset.pageSize,
8746
+ total_rows: options.customerDbDataset.totalRows,
8747
+ },
8748
+ }
8749
+ : {}),
8750
+ }
8751
+ : {}),
8752
+ ...(providerIdempotencyKey
8753
+ ? { provider_idempotency_key: providerIdempotencyKey }
8754
+ : {}),
8755
+ },
8756
+ ...(this.#options.integrationMode
8757
+ ? { integration_mode: this.#options.integrationMode }
8758
+ : {}),
8759
+ });
8615
8760
  let transportAttempt = 0;
8761
+ let invocationAttempt = 0;
8616
8762
  const retryToolTransportFailure = async (input: {
8617
8763
  error: unknown;
8618
8764
  elapsedMs: number;
@@ -8662,23 +8808,7 @@ export class PlayContextImpl {
8662
8808
  let responseErrorText: string | null = null;
8663
8809
  let providerCallStartedAt: number | null = null;
8664
8810
  let providerCallElapsedMs: number | null = null;
8665
- const durableCallReceiptKey =
8666
- options?.durableCallReceiptKey?.trim() || null;
8667
- const executionAuthScopeDigest =
8668
- durableCallReceiptKey && options?.executionAuthScopeDigest
8669
- ? options.executionAuthScopeDigest.trim() || null
8670
- : durableCallReceiptKey
8671
- ? ((await this.resolveToolAuthScopeDigest(toolId))?.trim() ??
8672
- null)
8673
- : null;
8674
- const providerIdempotencyKey =
8675
- options?.providerIdempotencyKey?.trim() || durableCallReceiptKey;
8676
- // Every gateway request needs a correlation ID. When the call has
8677
- // an idempotency key, keep its stable ID across retries; otherwise
8678
- // give this individual attempt a fresh ID for gateway pairing.
8679
- const deeplineRequestId = providerIdempotencyKey
8680
- ? `ctx-tool-${stableDigest(providerIdempotencyKey).slice(0, 32)}`
8681
- : `ctx-tool-${crypto.randomUUID()}`;
8811
+ let fetchDispatched = false;
8682
8812
  // Receipt ownership is a liveness contract, not a one-time check.
8683
8813
  // Keep it alive for the whole provider HTTP request. The cadence
8684
8814
  // comes from the store-issued expiry because a remote runner may
@@ -8747,6 +8877,7 @@ export class PlayContextImpl {
8747
8877
  const integrationFetchStartedAt = Date.now();
8748
8878
  providerCallStartedAt = integrationFetchStartedAt;
8749
8879
  try {
8880
+ fetchDispatched = true;
8750
8881
  response = await fetch(url, {
8751
8882
  method: 'POST',
8752
8883
  signal: abortController?.signal,
@@ -8765,59 +8896,23 @@ export class PlayContextImpl {
8765
8896
  }
8766
8897
  : {}),
8767
8898
  ...protectionHeaders,
8768
- },
8769
- body: JSON.stringify({
8770
- payload: input,
8771
- metadata: {
8772
- parent_run_id: this.#options.runId,
8773
- ...(durableCallReceiptKey
8774
- ? {
8775
- durable_call_receipt_key: durableCallReceiptKey,
8776
- ...(executionAuthScopeDigest
8777
- ? {
8778
- execution_auth_scope_digest:
8779
- executionAuthScopeDigest,
8780
- }
8781
- : {}),
8782
- }
8783
- : {}),
8784
- ...(options?.customerDbDataset
8785
- ? {
8786
- query_result_dataset: {
8787
- limit: options.customerDbDataset.limit,
8788
- offset: options.customerDbDataset.offset,
8789
- page_size: options.customerDbDataset.pageSize,
8790
- total_rows: options.customerDbDataset.totalRows,
8791
- },
8792
- ...(isCustomerDbDatasetTool(toolId)
8793
- ? {
8794
- customer_db_dataset: {
8795
- limit: options.customerDbDataset.limit,
8796
- offset: options.customerDbDataset.offset,
8797
- page_size:
8798
- options.customerDbDataset.pageSize,
8799
- total_rows:
8800
- options.customerDbDataset.totalRows,
8801
- },
8802
- }
8803
- : {}),
8804
- }
8805
- : {}),
8806
- ...(providerIdempotencyKey
8807
- ? { provider_idempotency_key: providerIdempotencyKey }
8808
- : {}),
8809
- },
8810
- ...(this.#options.integrationMode
8811
- ? { integration_mode: this.#options.integrationMode }
8899
+ ...(this.#options.runtimeTestFaultHeader
8900
+ ? {
8901
+ 'x-deepline-test-fault':
8902
+ this.#options.runtimeTestFaultHeader,
8903
+ }
8812
8904
  : {}),
8813
- }),
8905
+ },
8906
+ body: serializedRequestBody(invocationAttempt),
8814
8907
  });
8815
8908
  if (response.ok) {
8816
8909
  try {
8817
- responseData = (await response.json()) as Record<
8818
- string,
8819
- unknown
8820
- >;
8910
+ responseData = await readToolExecuteResponseBody({
8911
+ toolId,
8912
+ abortController,
8913
+ read: () =>
8914
+ response!.json() as Promise<Record<string, unknown>>,
8915
+ });
8821
8916
  } catch (error) {
8822
8917
  if (error instanceof SyntaxError) {
8823
8918
  throw new ToolExecuteInvalidJsonError(error);
@@ -8826,7 +8921,11 @@ export class PlayContextImpl {
8826
8921
  }
8827
8922
  } else {
8828
8923
  try {
8829
- responseErrorText = await response.text();
8924
+ responseErrorText = await readToolExecuteResponseBody({
8925
+ toolId,
8926
+ abortController,
8927
+ read: () => response!.text(),
8928
+ });
8830
8929
  } catch (error) {
8831
8930
  throw new ToolExecuteResponseBodyTransportError(error);
8832
8931
  }
@@ -8887,11 +8986,39 @@ export class PlayContextImpl {
8887
8986
  402,
8888
8987
  );
8889
8988
  }
8890
- const responseBodyReplaySafe =
8891
- !(error instanceof ToolExecuteResponseBodyTransportError) ||
8892
- retrySafeTransientHttp ||
8893
- response?.status === 429;
8894
- if (!responseBodyReplaySafe) {
8989
+ const ambiguousDispatchedFailure =
8990
+ fetchDispatched &&
8991
+ (response === null ||
8992
+ error instanceof ToolExecuteResponseBodyTransportError);
8993
+ const hasDurableInvocationIdentity = Boolean(
8994
+ durableCallReceiptKey &&
8995
+ providerIdempotencyKey &&
8996
+ executionAuthScopeDigest,
8997
+ );
8998
+ // A post-header body deadline aborts the fetch controller solely
8999
+ // to stop consuming that response. It must not also cancel the
9000
+ // independent gateway health check that establishes whether the
9001
+ // committed response can be replayed safely. Preserve genuine
9002
+ // caller/runtime cancellation by dropping the signal only when
9003
+ // this exact body deadline caused the abort.
9004
+ const durableFenceVerificationSignal =
9005
+ abortController?.signal.aborted === true &&
9006
+ abortController.signal.reason instanceof
9007
+ ToolExecuteResponseBodyTimeoutError
9008
+ ? undefined
9009
+ : abortController?.signal;
9010
+ const durableInvocationFenceVerified =
9011
+ hasDurableInvocationIdentity &&
9012
+ requestsDurableInvocationFence &&
9013
+ (this.#options.durableInvocationFence === true ||
9014
+ (await this.#options
9015
+ .verifyDurableInvocationFence?.(
9016
+ durableFenceVerificationSignal,
9017
+ )
9018
+ .catch(() => false)) === true);
9019
+ const transportReplaySafe =
9020
+ !ambiguousDispatchedFailure || durableInvocationFenceVerified;
9021
+ if (!transportReplaySafe) {
8895
9022
  const diagnostic = describeTransportError(transportError);
8896
9023
  this.log(
8897
9024
  `[runtime.transport_failure] ${JSON.stringify({
@@ -8905,13 +9032,17 @@ export class PlayContextImpl {
8905
9032
  : Date.now() - providerCallStartedAt,
8906
9033
  request_id: deeplineRequestId,
8907
9034
  aborted: abortController?.signal.aborted === true,
8908
- response_headers_received: true,
9035
+ response_headers_received: response !== null,
8909
9036
  retry_safe: false,
8910
9037
  error: diagnostic,
8911
9038
  })}`,
8912
9039
  );
9040
+ const failureBoundary =
9041
+ error instanceof ToolExecuteResponseBodyTransportError
9042
+ ? 'response body transport failed after response headers'
9043
+ : 'request transport failed after dispatch before response headers';
8913
9044
  throw new ToolHttpError(
8914
- `Tool ${toolId} response body transport failed after response headers; the ambiguous call was not retried because this operation is not declared retry-safe: ${diagnostic.message ?? 'unknown transport error'}`,
9045
+ `Tool ${toolId} ${failureBoundary}; the ambiguous call was not retried because this runtime has no durable invocation fence: ${diagnostic.message ?? 'unknown transport error'}`,
8915
9046
  null,
8916
9047
  0,
8917
9048
  'repairable',
@@ -9004,6 +9135,9 @@ export class PlayContextImpl {
9004
9135
  );
9005
9136
  }
9006
9137
  if (failure.shouldRetry) {
9138
+ if (failure.reason !== 'gateway_invocation_in_progress') {
9139
+ invocationAttempt += 1;
9140
+ }
9007
9141
  if (failure.chargeRetryBudget) {
9008
9142
  await this.governor.chargeBudget('retry');
9009
9143
  }
@@ -512,6 +512,15 @@ export interface ContextOptions {
512
512
  /** Short-lived HMAC-signed internal token for tool callbacks. Required for cloud execution. */
513
513
  executorToken?: string;
514
514
  baseUrl?: string;
515
+ /**
516
+ * The integration base URL persists each keyed execute response before
517
+ * exposing it. Only runtime adapters that route through that gateway may
518
+ * enable ambiguous transport replay.
519
+ */
520
+ durableInvocationFence?: boolean;
521
+ requestDurableInvocationFence?: boolean;
522
+ verifyDurableInvocationFence?: (signal?: AbortSignal) => Promise<boolean>;
523
+ runtimeTestFaultHeader?: string | null;
515
524
  /**
516
525
  * Runtime-sheet transport selected by the runner. Daytona sandboxes use the
517
526
  * execution gateway and must never fall back to minting direct DB sessions.
@@ -644,7 +653,10 @@ export interface ContextOptions {
644
653
  resolvePlay?: (playRef: string) => Promise<ResolvedPlayExecution | null>;
645
654
  getToolQueueHints?: (toolId: string) => Promise<readonly PlayQueueHint[]>;
646
655
  getToolProvider?: (toolId: string) => Promise<string | null>;
647
- getToolRetryPolicy?: (toolId: string) => Promise<{
656
+ getToolRetryPolicy?: (
657
+ toolId: string,
658
+ input: Record<string, unknown>,
659
+ ) => Promise<{
648
660
  retrySafeTransientHttp?: boolean;
649
661
  requiresExecutionFence?: boolean;
650
662
  } | null>;
@@ -6,6 +6,7 @@ export type RuntimeTestFaultName =
6
6
  | 'receipt_complete_write_fail'
7
7
  | 'receipt_fail_write_fail'
8
8
  | 'worker_receipt_complete_write_fail'
9
+ | 'invocation_response_delivery_abort'
9
10
  /**
10
11
  * Holds an already checked-out receipt-gateway scheduler client for the
11
12
  * supplied bounded millisecond value. Preview/CI only: this creates a
@@ -70,6 +71,7 @@ const SUPPORTED_RUNTIME_TEST_FAULTS = new Set<RuntimeTestFaultName>([
70
71
  'receipt_complete_write_fail',
71
72
  'receipt_fail_write_fail',
72
73
  'worker_receipt_complete_write_fail',
74
+ 'invocation_response_delivery_abort',
73
75
  'receipt_gateway_hold_ms',
74
76
  ]);
75
77
 
@@ -21,6 +21,8 @@ export const TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS = 1_000;
21
21
  export const TOOL_EXECUTE_RETRY_DELAY_MAX_MS = 5_000;
22
22
  export const TOOL_EXECUTE_BARE_RATE_LIMIT_BACKPRESSURE_MS = 60_000;
23
23
  export const TOOL_EXECUTE_AUTH_SCOPE_CHANGED_CODE = 'AUTH_SCOPE_CHANGED';
24
+ export const TOOL_EXECUTE_CUSTOMER_DB_STORAGE_UNAVAILABLE_CODE =
25
+ 'CUSTOMER_DB_STORAGE_UNAVAILABLE';
24
26
  /**
25
27
  * A provider/action-local outcome contract emits this only when a provider has
26
28
  * explicitly said that the same idempotency key is still executing. It is not
@@ -28,6 +30,9 @@ export const TOOL_EXECUTE_AUTH_SCOPE_CHANGED_CODE = 'AUTH_SCOPE_CHANGED';
28
30
  */
29
31
  export const TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_CODE =
30
32
  'UPSTREAM_IDEMPOTENCY_IN_PROGRESS';
33
+ export const TOOL_EXECUTE_GATEWAY_INVOCATION_IN_PROGRESS_CODE =
34
+ 'GATEWAY_INVOCATION_IN_PROGRESS';
35
+ export const TOOL_EXECUTE_GATEWAY_INVOCATION_IN_PROGRESS_MAX_ATTEMPTS = 65;
31
36
 
32
37
  export class ToolExecuteAuthScopeChangedError extends Error {
33
38
  readonly code = TOOL_EXECUTE_AUTH_SCOPE_CHANGED_CODE;
@@ -45,6 +50,8 @@ export type ToolExecuteHttpRetryDecision = {
45
50
  reason:
46
51
  | 'rate_limit'
47
52
  | 'idempotency_in_progress'
53
+ | 'gateway_invocation_in_progress'
54
+ | 'customer_db_storage_unavailable'
48
55
  | 'retry_safe_transient_5xx'
49
56
  | 'unsafe_transient_5xx'
50
57
  | 'hard_billing_error'
@@ -94,6 +101,35 @@ function isIdempotencyInProgressResponse(input: {
94
101
  );
95
102
  }
96
103
 
104
+ function isGatewayInvocationInProgressResponse(input: {
105
+ status: number;
106
+ bodyText: string;
107
+ }): boolean {
108
+ return (
109
+ input.status === 409 &&
110
+ parseJsonObject(input.bodyText)?.code ===
111
+ TOOL_EXECUTE_GATEWAY_INVOCATION_IN_PROGRESS_CODE
112
+ );
113
+ }
114
+
115
+ function isCustomerDbStorageUnavailableResponse(input: {
116
+ toolId: string;
117
+ status: number;
118
+ bodyText: string;
119
+ }): boolean {
120
+ if (
121
+ input.status !== 503 ||
122
+ (input.toolId !== 'query_customer_db' &&
123
+ input.toolId !== 'customer_db_query_customer_db')
124
+ ) {
125
+ return false;
126
+ }
127
+ return (
128
+ parseJsonObject(input.bodyText)?.code ===
129
+ TOOL_EXECUTE_CUSTOMER_DB_STORAGE_UNAVAILABLE_CODE
130
+ );
131
+ }
132
+
97
133
  function idempotencyInProgressRetryDelayMs(attempt: number): number {
98
134
  return (
99
135
  TOOL_EXECUTE_IDEMPOTENCY_IN_PROGRESS_RETRY_DELAYS_MS[
@@ -125,6 +161,8 @@ export function parseToolExecuteAuthScopeChangedError(input: {
125
161
  function decideToolExecuteHttpRetry(input: {
126
162
  status: number;
127
163
  idempotencyInProgress?: boolean;
164
+ gatewayInvocationInProgress?: boolean;
165
+ customerDbStorageUnavailable?: boolean;
128
166
  hardBillingFailure?: boolean;
129
167
  hasRetryAfterHeader?: boolean;
130
168
  transientHttpRetrySafe?: boolean;
@@ -151,6 +189,13 @@ function decideToolExecuteHttpRetry(input: {
151
189
  reason: 'rate_limit',
152
190
  };
153
191
  }
192
+ if (input.gatewayInvocationInProgress) {
193
+ return {
194
+ retryable: true,
195
+ attemptCap: TOOL_EXECUTE_GATEWAY_INVOCATION_IN_PROGRESS_MAX_ATTEMPTS,
196
+ reason: 'gateway_invocation_in_progress',
197
+ };
198
+ }
154
199
  if (input.idempotencyInProgress) {
155
200
  return {
156
201
  retryable: true,
@@ -158,6 +203,16 @@ function decideToolExecuteHttpRetry(input: {
158
203
  reason: 'idempotency_in_progress',
159
204
  };
160
205
  }
206
+ // This typed response is emitted for a PostgreSQL permission denial. The
207
+ // failed statement cannot commit, so it is safe to retry even when the SQL
208
+ // itself is a mutation. Do not generalize this to arbitrary 503 responses.
209
+ if (input.customerDbStorageUnavailable) {
210
+ return {
211
+ retryable: true,
212
+ attemptCap: TOOL_EXECUTE_TRANSIENT_HTTP_MAX_ATTEMPTS,
213
+ reason: 'customer_db_storage_unavailable',
214
+ };
215
+ }
161
216
  if (input.status >= 500 && input.status < 600) {
162
217
  if (!input.transientHttpRetrySafe) {
163
218
  return {
@@ -186,6 +241,8 @@ export function createToolExecuteHttpFailureAttemptTracker(): ToolExecuteHttpFai
186
241
  > = {
187
242
  rate_limit: 0,
188
243
  idempotency_in_progress: 0,
244
+ gateway_invocation_in_progress: 0,
245
+ customer_db_storage_unavailable: 0,
189
246
  retry_safe_transient_5xx: 0,
190
247
  unsafe_transient_5xx: 0,
191
248
  hard_billing_error: 0,
@@ -200,6 +257,15 @@ export function createToolExecuteHttpFailureAttemptTracker(): ToolExecuteHttpFai
200
257
  status: input.status,
201
258
  bodyText: input.bodyText ?? '',
202
259
  }),
260
+ gatewayInvocationInProgress: isGatewayInvocationInProgressResponse({
261
+ status: input.status,
262
+ bodyText: input.bodyText ?? '',
263
+ }),
264
+ customerDbStorageUnavailable: isCustomerDbStorageUnavailableResponse({
265
+ toolId: input.toolId,
266
+ status: input.status,
267
+ bodyText: input.bodyText ?? '',
268
+ }),
203
269
  hasRetryAfterHeader: true,
204
270
  transientHttpRetrySafe: input.transientHttpRetrySafe === true,
205
271
  });
@@ -240,9 +306,15 @@ export function classifyToolExecuteHttpFailure(input: {
240
306
  typeof input.retryAfterHeader === 'string' &&
241
307
  input.retryAfterHeader.trim().length > 0;
242
308
  const idempotencyInProgress = isIdempotencyInProgressResponse(input);
309
+ const gatewayInvocationInProgress =
310
+ isGatewayInvocationInProgressResponse(input);
311
+ const customerDbStorageUnavailable =
312
+ isCustomerDbStorageUnavailableResponse(input);
243
313
  const initialRetryDecision = decideToolExecuteHttpRetry({
244
314
  status: input.status,
245
315
  idempotencyInProgress,
316
+ gatewayInvocationInProgress,
317
+ customerDbStorageUnavailable,
246
318
  hasRetryAfterHeader,
247
319
  transientHttpRetrySafe,
248
320
  });
@@ -264,6 +336,8 @@ export function classifyToolExecuteHttpFailure(input: {
264
336
  const retryDecision = decideToolExecuteHttpRetry({
265
337
  status: input.status,
266
338
  idempotencyInProgress,
339
+ gatewayInvocationInProgress,
340
+ customerDbStorageUnavailable,
267
341
  hardBillingFailure,
268
342
  hasRetryAfterHeader,
269
343
  transientHttpRetrySafe,
@@ -295,6 +369,9 @@ export function classifyToolExecuteHttpFailure(input: {
295
369
  ),
296
370
  )
297
371
  : retryDecision.reason === 'idempotency_in_progress' &&
372
+ !hasRetryAfterHeader
373
+ ? idempotencyInProgressRetryDelayMs(input.attempt)
374
+ : retryDecision.reason === 'gateway_invocation_in_progress' &&
298
375
  !hasRetryAfterHeader
299
376
  ? idempotencyInProgressRetryDelayMs(input.attempt)
300
377
  : retryAfterMs > 0
@@ -317,6 +394,8 @@ export function classifyToolExecuteHttpFailure(input: {
317
394
  ? retryAfterMs
318
395
  : TOOL_EXECUTE_BARE_RATE_LIMIT_BACKPRESSURE_MS
319
396
  : null,
320
- chargeRetryBudget: shouldRetry,
397
+ chargeRetryBudget:
398
+ shouldRetry &&
399
+ retryDecision.reason !== 'gateway_invocation_in_progress',
321
400
  };
322
401
  }
package/dist/cli/index.js CHANGED
@@ -718,7 +718,7 @@ var SDK_RELEASE = {
718
718
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
719
719
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
720
720
  // Operators use the checkout-local deepline-admin binary instead.
721
- version: "0.1.286",
721
+ version: "0.1.288",
722
722
  contracts: {
723
723
  api: {
724
724
  name: "sdk-http-api",
@@ -2799,6 +2799,53 @@ async function* observeRunEvents(options) {
2799
2799
  }
2800
2800
  }
2801
2801
 
2802
+ // ../shared_libs/integrations/theirstack-execution-policy.ts
2803
+ var THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135e3;
2804
+ var THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS = 6e4 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15e3;
2805
+ var LONG_JOB_SEARCH_WINDOW_MS = 365 * 24 * 60 * 60 * 1e3;
2806
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
2807
+ function parseDateOnly(value) {
2808
+ if (typeof value !== "string" || !DATE_ONLY_PATTERN.test(value)) {
2809
+ return null;
2810
+ }
2811
+ const timestamp = Date.parse(`${value}T00:00:00.000Z`);
2812
+ if (!Number.isFinite(timestamp)) {
2813
+ return null;
2814
+ }
2815
+ return new Date(timestamp).toISOString().slice(0, 10) === value ? timestamp : null;
2816
+ }
2817
+ function usesLongExplicitDateWindow(payload) {
2818
+ const postedAtGte = parseDateOnly(payload.posted_at_gte);
2819
+ const postedAtLte = parseDateOnly(payload.posted_at_lte);
2820
+ if (postedAtGte !== null && postedAtLte !== null) {
2821
+ return postedAtLte - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2822
+ }
2823
+ if (postedAtGte !== null) {
2824
+ return Date.now() - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2825
+ }
2826
+ return postedAtLte !== null && payload.posted_at_gte == null;
2827
+ }
2828
+ function parseMaxAgeDays(value) {
2829
+ if (typeof value === "number") {
2830
+ return Number.isFinite(value) ? value : null;
2831
+ }
2832
+ if (typeof value !== "string" || !/^-?\d+$/.test(value.trim())) {
2833
+ return null;
2834
+ }
2835
+ const parsed = Number(value.trim());
2836
+ return Number.isSafeInteger(parsed) ? parsed : null;
2837
+ }
2838
+ function usesExtendedTheirstackJobSearchBudget(endpointId, payload) {
2839
+ if (endpointId !== "theirstack_job_search") {
2840
+ return false;
2841
+ }
2842
+ if (payload.include_total_results === true) {
2843
+ return true;
2844
+ }
2845
+ const maxAgeDays = parseMaxAgeDays(payload.posted_at_max_age_days);
2846
+ return maxAgeDays !== null && maxAgeDays >= 365 ? true : usesLongExplicitDateWindow(payload);
2847
+ }
2848
+
2802
2849
  // ../shared_libs/play-runtime/runtime-environment.ts
2803
2850
  var PLAY_RUNTIME_ENVIRONMENTS = ["preview"];
2804
2851
  var PLAY_RUNTIME_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]{0,30}$/;
@@ -2999,6 +3046,9 @@ function resolveToolExecuteTimeoutMs(toolId, input2) {
2999
3046
  return Math.floor(requestedTimeoutMs) + APIFY_SYNC_RESPONSE_GRACE_MS;
3000
3047
  }
3001
3048
  }
3049
+ if (usesExtendedTheirstackJobSearchBudget(normalized, input2)) {
3050
+ return THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS;
3051
+ }
3002
3052
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
3003
3053
  }
3004
3054
  var RUNS_FAILED_LOG_LIMIT = 20;
@@ -5220,7 +5270,8 @@ var DeeplineClient = class {
5220
5270
  async listMonitors(options) {
5221
5271
  const params = new URLSearchParams();
5222
5272
  if (options?.status) params.set("status", options.status);
5223
- if (options?.limit !== void 0) params.set("limit", String(options.limit));
5273
+ if (options?.limit !== void 0)
5274
+ params.set("limit", String(options.limit));
5224
5275
  if (options?.cursor) params.set("cursor", options.cursor);
5225
5276
  if (options?.compact) params.set("compact", "true");
5226
5277
  const query = params.toString();
@@ -703,7 +703,7 @@ var SDK_RELEASE = {
703
703
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
704
704
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
705
705
  // Operators use the checkout-local deepline-admin binary instead.
706
- version: "0.1.286",
706
+ version: "0.1.288",
707
707
  contracts: {
708
708
  api: {
709
709
  name: "sdk-http-api",
@@ -2784,6 +2784,53 @@ async function* observeRunEvents(options) {
2784
2784
  }
2785
2785
  }
2786
2786
 
2787
+ // ../shared_libs/integrations/theirstack-execution-policy.ts
2788
+ var THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135e3;
2789
+ var THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS = 6e4 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15e3;
2790
+ var LONG_JOB_SEARCH_WINDOW_MS = 365 * 24 * 60 * 60 * 1e3;
2791
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
2792
+ function parseDateOnly(value) {
2793
+ if (typeof value !== "string" || !DATE_ONLY_PATTERN.test(value)) {
2794
+ return null;
2795
+ }
2796
+ const timestamp = Date.parse(`${value}T00:00:00.000Z`);
2797
+ if (!Number.isFinite(timestamp)) {
2798
+ return null;
2799
+ }
2800
+ return new Date(timestamp).toISOString().slice(0, 10) === value ? timestamp : null;
2801
+ }
2802
+ function usesLongExplicitDateWindow(payload) {
2803
+ const postedAtGte = parseDateOnly(payload.posted_at_gte);
2804
+ const postedAtLte = parseDateOnly(payload.posted_at_lte);
2805
+ if (postedAtGte !== null && postedAtLte !== null) {
2806
+ return postedAtLte - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2807
+ }
2808
+ if (postedAtGte !== null) {
2809
+ return Date.now() - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2810
+ }
2811
+ return postedAtLte !== null && payload.posted_at_gte == null;
2812
+ }
2813
+ function parseMaxAgeDays(value) {
2814
+ if (typeof value === "number") {
2815
+ return Number.isFinite(value) ? value : null;
2816
+ }
2817
+ if (typeof value !== "string" || !/^-?\d+$/.test(value.trim())) {
2818
+ return null;
2819
+ }
2820
+ const parsed = Number(value.trim());
2821
+ return Number.isSafeInteger(parsed) ? parsed : null;
2822
+ }
2823
+ function usesExtendedTheirstackJobSearchBudget(endpointId, payload) {
2824
+ if (endpointId !== "theirstack_job_search") {
2825
+ return false;
2826
+ }
2827
+ if (payload.include_total_results === true) {
2828
+ return true;
2829
+ }
2830
+ const maxAgeDays = parseMaxAgeDays(payload.posted_at_max_age_days);
2831
+ return maxAgeDays !== null && maxAgeDays >= 365 ? true : usesLongExplicitDateWindow(payload);
2832
+ }
2833
+
2787
2834
  // ../shared_libs/play-runtime/runtime-environment.ts
2788
2835
  var PLAY_RUNTIME_ENVIRONMENTS = ["preview"];
2789
2836
  var PLAY_RUNTIME_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]{0,30}$/;
@@ -2984,6 +3031,9 @@ function resolveToolExecuteTimeoutMs(toolId, input2) {
2984
3031
  return Math.floor(requestedTimeoutMs) + APIFY_SYNC_RESPONSE_GRACE_MS;
2985
3032
  }
2986
3033
  }
3034
+ if (usesExtendedTheirstackJobSearchBudget(normalized, input2)) {
3035
+ return THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS;
3036
+ }
2987
3037
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
2988
3038
  }
2989
3039
  var RUNS_FAILED_LOG_LIMIT = 20;
@@ -5205,7 +5255,8 @@ var DeeplineClient = class {
5205
5255
  async listMonitors(options) {
5206
5256
  const params = new URLSearchParams();
5207
5257
  if (options?.status) params.set("status", options.status);
5208
- if (options?.limit !== void 0) params.set("limit", String(options.limit));
5258
+ if (options?.limit !== void 0)
5259
+ params.set("limit", String(options.limit));
5209
5260
  if (options?.cursor) params.set("cursor", options.cursor);
5210
5261
  if (options?.compact) params.set("compact", "true");
5211
5262
  const query = params.toString();
package/dist/index.js CHANGED
@@ -438,7 +438,7 @@ var SDK_RELEASE = {
438
438
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
439
439
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
440
440
  // Operators use the checkout-local deepline-admin binary instead.
441
- version: "0.1.286",
441
+ version: "0.1.288",
442
442
  contracts: {
443
443
  api: {
444
444
  name: "sdk-http-api",
@@ -2519,6 +2519,53 @@ async function* observeRunEvents(options) {
2519
2519
  }
2520
2520
  }
2521
2521
 
2522
+ // ../shared_libs/integrations/theirstack-execution-policy.ts
2523
+ var THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135e3;
2524
+ var THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS = 6e4 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15e3;
2525
+ var LONG_JOB_SEARCH_WINDOW_MS = 365 * 24 * 60 * 60 * 1e3;
2526
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
2527
+ function parseDateOnly(value) {
2528
+ if (typeof value !== "string" || !DATE_ONLY_PATTERN.test(value)) {
2529
+ return null;
2530
+ }
2531
+ const timestamp = Date.parse(`${value}T00:00:00.000Z`);
2532
+ if (!Number.isFinite(timestamp)) {
2533
+ return null;
2534
+ }
2535
+ return new Date(timestamp).toISOString().slice(0, 10) === value ? timestamp : null;
2536
+ }
2537
+ function usesLongExplicitDateWindow(payload) {
2538
+ const postedAtGte = parseDateOnly(payload.posted_at_gte);
2539
+ const postedAtLte = parseDateOnly(payload.posted_at_lte);
2540
+ if (postedAtGte !== null && postedAtLte !== null) {
2541
+ return postedAtLte - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2542
+ }
2543
+ if (postedAtGte !== null) {
2544
+ return Date.now() - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2545
+ }
2546
+ return postedAtLte !== null && payload.posted_at_gte == null;
2547
+ }
2548
+ function parseMaxAgeDays(value) {
2549
+ if (typeof value === "number") {
2550
+ return Number.isFinite(value) ? value : null;
2551
+ }
2552
+ if (typeof value !== "string" || !/^-?\d+$/.test(value.trim())) {
2553
+ return null;
2554
+ }
2555
+ const parsed = Number(value.trim());
2556
+ return Number.isSafeInteger(parsed) ? parsed : null;
2557
+ }
2558
+ function usesExtendedTheirstackJobSearchBudget(endpointId, payload) {
2559
+ if (endpointId !== "theirstack_job_search") {
2560
+ return false;
2561
+ }
2562
+ if (payload.include_total_results === true) {
2563
+ return true;
2564
+ }
2565
+ const maxAgeDays = parseMaxAgeDays(payload.posted_at_max_age_days);
2566
+ return maxAgeDays !== null && maxAgeDays >= 365 ? true : usesLongExplicitDateWindow(payload);
2567
+ }
2568
+
2522
2569
  // ../shared_libs/play-runtime/runtime-environment.ts
2523
2570
  var PLAY_RUNTIME_ENVIRONMENTS = ["preview"];
2524
2571
  var PLAY_RUNTIME_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]{0,30}$/;
@@ -2719,6 +2766,9 @@ function resolveToolExecuteTimeoutMs(toolId, input) {
2719
2766
  return Math.floor(requestedTimeoutMs) + APIFY_SYNC_RESPONSE_GRACE_MS;
2720
2767
  }
2721
2768
  }
2769
+ if (usesExtendedTheirstackJobSearchBudget(normalized, input)) {
2770
+ return THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS;
2771
+ }
2722
2772
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
2723
2773
  }
2724
2774
  var RUNS_FAILED_LOG_LIMIT = 20;
@@ -4940,7 +4990,8 @@ var DeeplineClient = class {
4940
4990
  async listMonitors(options) {
4941
4991
  const params = new URLSearchParams();
4942
4992
  if (options?.status) params.set("status", options.status);
4943
- if (options?.limit !== void 0) params.set("limit", String(options.limit));
4993
+ if (options?.limit !== void 0)
4994
+ params.set("limit", String(options.limit));
4944
4995
  if (options?.cursor) params.set("cursor", options.cursor);
4945
4996
  if (options?.compact) params.set("compact", "true");
4946
4997
  const query = params.toString();
package/dist/index.mjs CHANGED
@@ -367,7 +367,7 @@ var SDK_RELEASE = {
367
367
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
368
368
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
369
369
  // Operators use the checkout-local deepline-admin binary instead.
370
- version: "0.1.286",
370
+ version: "0.1.288",
371
371
  contracts: {
372
372
  api: {
373
373
  name: "sdk-http-api",
@@ -2448,6 +2448,53 @@ async function* observeRunEvents(options) {
2448
2448
  }
2449
2449
  }
2450
2450
 
2451
+ // ../shared_libs/integrations/theirstack-execution-policy.ts
2452
+ var THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS = 135e3;
2453
+ var THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS = 6e4 + THEIRSTACK_SLOW_JOB_SEARCH_PROVIDER_TIMEOUT_MS + 15e3;
2454
+ var LONG_JOB_SEARCH_WINDOW_MS = 365 * 24 * 60 * 60 * 1e3;
2455
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
2456
+ function parseDateOnly(value) {
2457
+ if (typeof value !== "string" || !DATE_ONLY_PATTERN.test(value)) {
2458
+ return null;
2459
+ }
2460
+ const timestamp = Date.parse(`${value}T00:00:00.000Z`);
2461
+ if (!Number.isFinite(timestamp)) {
2462
+ return null;
2463
+ }
2464
+ return new Date(timestamp).toISOString().slice(0, 10) === value ? timestamp : null;
2465
+ }
2466
+ function usesLongExplicitDateWindow(payload) {
2467
+ const postedAtGte = parseDateOnly(payload.posted_at_gte);
2468
+ const postedAtLte = parseDateOnly(payload.posted_at_lte);
2469
+ if (postedAtGte !== null && postedAtLte !== null) {
2470
+ return postedAtLte - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2471
+ }
2472
+ if (postedAtGte !== null) {
2473
+ return Date.now() - postedAtGte >= LONG_JOB_SEARCH_WINDOW_MS;
2474
+ }
2475
+ return postedAtLte !== null && payload.posted_at_gte == null;
2476
+ }
2477
+ function parseMaxAgeDays(value) {
2478
+ if (typeof value === "number") {
2479
+ return Number.isFinite(value) ? value : null;
2480
+ }
2481
+ if (typeof value !== "string" || !/^-?\d+$/.test(value.trim())) {
2482
+ return null;
2483
+ }
2484
+ const parsed = Number(value.trim());
2485
+ return Number.isSafeInteger(parsed) ? parsed : null;
2486
+ }
2487
+ function usesExtendedTheirstackJobSearchBudget(endpointId, payload) {
2488
+ if (endpointId !== "theirstack_job_search") {
2489
+ return false;
2490
+ }
2491
+ if (payload.include_total_results === true) {
2492
+ return true;
2493
+ }
2494
+ const maxAgeDays = parseMaxAgeDays(payload.posted_at_max_age_days);
2495
+ return maxAgeDays !== null && maxAgeDays >= 365 ? true : usesLongExplicitDateWindow(payload);
2496
+ }
2497
+
2451
2498
  // ../shared_libs/play-runtime/runtime-environment.ts
2452
2499
  var PLAY_RUNTIME_ENVIRONMENTS = ["preview"];
2453
2500
  var PLAY_RUNTIME_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]{0,30}$/;
@@ -2648,6 +2695,9 @@ function resolveToolExecuteTimeoutMs(toolId, input) {
2648
2695
  return Math.floor(requestedTimeoutMs) + APIFY_SYNC_RESPONSE_GRACE_MS;
2649
2696
  }
2650
2697
  }
2698
+ if (usesExtendedTheirstackJobSearchBudget(normalized, input)) {
2699
+ return THEIRSTACK_SLOW_JOB_SEARCH_CLIENT_TIMEOUT_MS;
2700
+ }
2651
2701
  return normalized === "deeplineagent" || normalized === "deeplineagent_deeplineagent" || normalized === "ai_inference" || normalized === "deeplineagent_ai_inference" || normalized === "aiinference" ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS : void 0;
2652
2702
  }
2653
2703
  var RUNS_FAILED_LOG_LIMIT = 20;
@@ -4869,7 +4919,8 @@ var DeeplineClient = class {
4869
4919
  async listMonitors(options) {
4870
4920
  const params = new URLSearchParams();
4871
4921
  if (options?.status) params.set("status", options.status);
4872
- if (options?.limit !== void 0) params.set("limit", String(options.limit));
4922
+ if (options?.limit !== void 0)
4923
+ params.set("limit", String(options.limit));
4873
4924
  if (options?.cursor) params.set("cursor", options.cursor);
4874
4925
  if (options?.compact) params.set("compact", "true");
4875
4926
  const query = params.toString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.286",
3
+ "version": "0.1.288",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {