deepline 0.3.230 → 0.3.232

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.
@@ -4,7 +4,6 @@ import type {
4
4
  PlaySettingsPathsPage,
5
5
  } from '../../shared_libs/plays/settings-contract';
6
6
  import { iterateCursorPages } from './pagination.js';
7
- import type { ToolResultBilling } from '../../shared_libs/plays/tool-result-types.js';
8
7
  /**
9
8
  * The main API client for interacting with the Deepline platform.
10
9
  *
@@ -41,7 +40,18 @@ import type { ToolResultBilling } from '../../shared_libs/plays/tool-result-type
41
40
  * @module
42
41
  */
43
42
  import { resolveConfig } from './config.js';
44
- import { DeeplineError, ToolExecutionError } from './errors.js';
43
+ import { DeeplineError } from './errors.js';
44
+ import {
45
+ ToolCalls,
46
+ type ExecuteToolRawOptions,
47
+ type ExecutionByKeyResult,
48
+ type ToolExecution,
49
+ } from './tool-calls.js';
50
+ export type {
51
+ ExecutionRecovery,
52
+ ExecutionByKeyResult,
53
+ ToolExecution,
54
+ } from './tool-calls.js';
45
55
  import { HttpClient } from './http.js';
46
56
  import { PRODUCT_NOTIFICATION_SLACK_OAUTH_SCOPES } from '../../shared_libs/product-notifications/contract.js';
47
57
  import {
@@ -112,12 +122,6 @@ import type { PlayStagedFileRef } from './plays/local-file-discovery.js';
112
122
  import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest.js';
113
123
  import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
114
124
  import { RUNTIME_ENVIRONMENT_TOKEN_HEADER } from '../../shared_libs/play-runtime/coordinator-headers.js';
115
- import { resolveTheirstackClientTimeoutMs } from '../../shared_libs/integration-contracts/theirstack/execution-policy.js';
116
- import { resolveWaterfallClientTimeoutMs } from '../../shared_libs/integration-contracts/waterfall/execution-policy.js';
117
- import {
118
- BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS,
119
- usesExtendedBetterContactLauncherBudget,
120
- } from '../../shared_libs/integration-contracts/bettercontact/execution-policy.js';
121
125
  import {
122
126
  normalizePlayRuntimeEnvironment,
123
127
  normalizePlayRuntimeNamespace,
@@ -125,20 +129,10 @@ import {
125
129
  normalizePlayRuntimeSelection,
126
130
  type PlayRuntimeSelection,
127
131
  } from '../../shared_libs/play-runtime/runtime-environment.js';
128
- import {
129
- TOOL_EXECUTION_ERROR_SCHEMA_HEADER,
130
- TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
131
- } from '../../shared_libs/plays/tool-execution-error';
132
132
  import {
133
133
  decodePlayRunPublicStatus,
134
134
  type PlayRunPublicStatus,
135
135
  } from '../../shared_libs/play-runtime/run-lifecycle-policy.js';
136
- import {
137
- legacyRawFromToolResponseRawV2,
138
- providerMetaFromToolResponseRawV2,
139
- RAW_V2_TOOL_RESPONSE_CONTRACT,
140
- } from '../../shared_libs/plays/tool-response-contract.js';
141
-
142
136
  export type SlackNotificationTarget =
143
137
  | { channel: string; memberId?: never }
144
138
  | { channel?: never; memberId: string };
@@ -224,127 +218,14 @@ function notificationObjectKey(kind: 'group' | 'destination') {
224
218
  }
225
219
 
226
220
  const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
227
- const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
228
- const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
229
- const EXECUTE_RESPONSE_INTENT_HEADER = 'x-deepline-execute-response-intent';
230
221
  const MONITOR_NON_RETRYABLE_MUTATION_OPTIONS = {
231
222
  maxRetries: 0,
232
223
  exactUrlOnly: true,
233
224
  };
234
- const RAW_V2_EXECUTE_RESPONSE_CONTRACT = RAW_V2_TOOL_RESPONSE_CONTRACT;
235
- const DEFAULT_EXECUTION_RECOVERY_TIMEOUT_MS = 15 * 60 * 1_000;
236
-
237
- function validateExecutionIdempotencyKey(key: string): void {
238
- if (
239
- typeof key !== 'string' ||
240
- key.length < 1 ||
241
- key.length > 200 ||
242
- !/^[A-Za-z0-9._:-]+$/.test(key)
243
- ) {
244
- throw new DeeplineError(
245
- 'Execution idempotency keys must be 1–200 ASCII letters, digits, dots, underscores, colons, or hyphens.',
246
- undefined,
247
- 'IDEMPOTENCY_KEY_INVALID',
248
- );
249
- }
250
- }
251
-
252
- function isExecutionInProgressResponse(value: unknown): boolean {
253
- return (
254
- typeof value === 'object' &&
255
- value !== null &&
256
- typeof (value as { executionRecovery?: { state?: unknown } })
257
- .executionRecovery === 'object' &&
258
- (value as { executionRecovery: { state?: unknown } }).executionRecovery
259
- .state === 'running'
260
- );
261
- }
262
-
263
- function getExecutionRecoveryState(
264
- value: unknown,
265
- ): ExecutionRecovery['state'] | null {
266
- if (
267
- typeof value !== 'object' ||
268
- value === null ||
269
- typeof (value as { executionRecovery?: { state?: unknown } })
270
- .executionRecovery !== 'object'
271
- ) {
272
- return null;
273
- }
274
- const state = (value as { executionRecovery: { state?: unknown } })
275
- .executionRecovery.state;
276
- return state === 'running' ||
277
- state === 'completed' ||
278
- state === 'outcome_unknown'
279
- ? state
280
- : null;
281
- }
282
-
283
- function isRecoverableExecutionAttemptError(error: unknown): boolean {
284
- if (error instanceof ToolExecutionError) {
285
- return (
286
- error.code === 'EXECUTION_IN_PROGRESS' ||
287
- (error.origin === 'deepline' &&
288
- error.category === 'network' &&
289
- error.networkScope === 'client_to_deepline')
290
- );
291
- }
292
- return (
293
- error instanceof DeeplineError &&
294
- (error.code === 'EXECUTION_IN_PROGRESS' ||
295
- (error.code?.startsWith('NETWORK_') === true &&
296
- error.statusCode === undefined))
297
- );
298
- }
299
-
300
- function isRecoverableExecutionLookupError(error: unknown): boolean {
301
- if (isRecoverableExecutionAttemptError(error)) return true;
302
- return (
303
- error instanceof DeeplineError &&
304
- (error.statusCode === 429 ||
305
- (error.statusCode !== undefined && error.statusCode >= 500))
306
- );
307
- }
308
-
309
- function timeoutWithinRecoveryBudget(
310
- requestTimeoutMs: number | undefined,
311
- defaultRequestTimeoutMs: number,
312
- remainingMs: number,
313
- ): number {
314
- return Math.min(requestTimeoutMs ?? defaultRequestTimeoutMs, remainingMs);
315
- }
316
-
317
- function executionRecoveryError(input: {
318
- toolId: string;
319
- idempotencyKey: string;
320
- code:
321
- | 'EXECUTION_IN_PROGRESS'
322
- | 'EXECUTION_OUTCOME_UNKNOWN'
323
- | 'EXECUTION_RECOVERY_TIMEOUT';
324
- message: string;
325
- }): ToolExecutionError {
326
- return new ToolExecutionError(input.message, {
327
- toolId: input.toolId,
328
- provider: null,
329
- operation: input.toolId,
330
- code: input.code,
331
- origin: 'deepline',
332
- category:
333
- input.code === 'EXECUTION_OUTCOME_UNKNOWN' ? 'unknown' : 'conflict',
334
- retryable: input.code !== 'EXECUTION_OUTCOME_UNKNOWN',
335
- statusCode: null,
336
- requestId: null,
337
- retryAfterMs: null,
338
- networkKind: null,
339
- networkScope: null,
340
- publicDetails: { idempotencyKey: input.idempotencyKey },
341
- });
342
- }
343
225
  const COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1_000];
344
226
  const REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY = 3;
345
227
  const REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT = 3;
346
228
  const REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES = 2_500_000;
347
- const DEEPLINEAGENT_EXECUTE_TIMEOUT_MS = 15 * 60 * 1000;
348
229
 
349
230
  export type IngestionStorageRepairResult = {
350
231
  status?: 'repaired';
@@ -659,124 +540,6 @@ function chunkRegisterPlayArtifacts<T>(artifacts: T[]): T[][] {
659
540
  return chunks;
660
541
  }
661
542
 
662
- type ExecuteToolRawOptions = {
663
- includeToolMetadata?: boolean;
664
- responseIntent?: 'dataset' | 'raw' | 'row_artifact';
665
- metadata?: Record<string, unknown>;
666
- timeout?: number;
667
- maxRetries?: number;
668
- /** Retry transient HTTP API responses when the caller has confirmed safety. */
669
- retryApiErrors?: boolean;
670
- /** Reuse this stable key to recover or retry an ambiguous execution. */
671
- idempotencyKey?: string;
672
- /** Generate a key and enable recoverable execution before dispatch. */
673
- recover?: boolean;
674
- /** Called and awaited with the key before the first network request. */
675
- onExecution?: (execution: { idempotencyKey: string }) => void | Promise<void>;
676
- /** Maximum time to reconnect to an in-progress keyed execution. Defaults to 15 minutes. */
677
- recoveryTimeoutMs?: number;
678
- /** The `search_ref` of the tools search that found this tool. */
679
- searchRef?: string;
680
- };
681
-
682
- /** Durable state returned for a keyed tool execution. */
683
- export type ExecutionRecovery = {
684
- /** Stable caller key used to resume or replay this execution. */
685
- idempotencyKey: string;
686
- /** Whether the execution is still running, completed, or has an unknown provider outcome. */
687
- state: 'running' | 'completed' | 'outcome_unknown';
688
- /** Whether this response came from an existing durable execution. */
689
- replayed: boolean;
690
- /** ISO timestamp after which the completed execution can no longer be replayed. */
691
- expiresAt?: string;
692
- };
693
-
694
- /** Durable lookup result for a keyed tool execution. */
695
- export type ExecutionByKeyResult = {
696
- /** Recovery state and the key that owns the execution. */
697
- executionRecovery: ExecutionRecovery;
698
- /** Provider tool associated with the execution. */
699
- toolId: string;
700
- /** Original server-owned billing request ID, allocated before provider dispatch. */
701
- requestId?: string;
702
- /** HTTP status saved with the original response, when it was terminal. */
703
- responseStatus?: number;
704
- /** Saved execution response, when one is available for replay. */
705
- response?: unknown;
706
- };
707
-
708
- /**
709
- * The server launches the Apify run, polls it for `payload.timeoutMs`, then
710
- * reads its dataset before replying. Keep the client request alive through
711
- * that final hand-off so a pollable run id is never lost to the SDK default
712
- * timeout.
713
- */
714
- const APIFY_SYNC_DEFAULT_TIMEOUT_MS = 300_000;
715
- const APIFY_SYNC_RESPONSE_GRACE_MS = 90_000;
716
- function resolveToolExecuteTimeoutMs(
717
- toolId: string,
718
- input: Record<string, unknown>,
719
- ): number | undefined {
720
- const normalized = toolId.trim().toLowerCase();
721
- if (normalized === 'apify_run_actor_sync') {
722
- const requestedTimeoutMs = input.timeoutMs ?? APIFY_SYNC_DEFAULT_TIMEOUT_MS;
723
- if (
724
- typeof requestedTimeoutMs === 'number' &&
725
- Number.isFinite(requestedTimeoutMs) &&
726
- requestedTimeoutMs > 0
727
- ) {
728
- return Math.floor(requestedTimeoutMs) + APIFY_SYNC_RESPONSE_GRACE_MS;
729
- }
730
- }
731
- // Provider-specific slow-request policies live in shared modules so the SDK
732
- // and server classify the same payloads. If more providers need this, replace
733
- // these individual checks with a shared policy registry, not a wider default.
734
- const theirstackTimeoutMs = resolveTheirstackClientTimeoutMs(
735
- normalized,
736
- input,
737
- );
738
- if (theirstackTimeoutMs !== null) return theirstackTimeoutMs;
739
- const waterfallTimeoutMs = resolveWaterfallClientTimeoutMs(normalized);
740
- if (waterfallTimeoutMs !== null) return waterfallTimeoutMs;
741
- if (usesExtendedBetterContactLauncherBudget(normalized)) {
742
- return BETTERCONTACT_LAUNCHER_CLIENT_TIMEOUT_MS;
743
- }
744
- return normalized === 'deeplineagent' ||
745
- normalized === 'deeplineagent_deeplineagent' ||
746
- normalized === 'ai_inference' ||
747
- normalized === 'deeplineagent_ai_inference' ||
748
- normalized === 'ai_evaluate' ||
749
- normalized === 'deeplineagent_ai_evaluate' ||
750
- normalized === 'aiinference'
751
- ? DEEPLINEAGENT_EXECUTE_TIMEOUT_MS
752
- : undefined;
753
- }
754
-
755
- /**
756
- * Standard provider/tool execution envelope returned by low-level SDK calls.
757
- *
758
- * `toolResponse.rawV2` contains the complete scrubbed provider response;
759
- * `toolResponse.raw` is derived locally as the legacy provider-result projection. `extractedValues` and
760
- * `extractedLists` contain Deepline-normalized getters when the tool exposes
761
- * them. Billing fields are Deepline-facing and must not expose provider spend.
762
- */
763
- export type ToolExecution<TData = unknown, TMeta = Record<string, unknown>> = {
764
- status: string;
765
- job_id?: string;
766
- meta?: Record<string, unknown>;
767
- toolResponse: {
768
- raw: TData;
769
- rawV2?: unknown;
770
- view?: 'data' | 'rawV2';
771
- meta?: TMeta;
772
- responseMeta?: TMeta;
773
- };
774
- extractedLists?: Record<string, unknown>;
775
- extractedValues?: Record<string, unknown>;
776
- billing?: ToolResultBilling;
777
- [key: string]: unknown;
778
- };
779
-
780
543
  /** Filters for `client.runs.list(...)`. */
781
544
  export type RunsListOptions = {
782
545
  play?: string;
@@ -2416,67 +2179,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
2416
2179
  return Boolean(value && typeof value === 'object' && !Array.isArray(value));
2417
2180
  }
2418
2181
 
2419
- /** Materialize raw-v2's rawV2-only wire response into the stable SDK result view. */
2420
- function materializeToolExecutionResponse<
2421
- TData = unknown,
2422
- TMeta = Record<string, unknown>,
2423
- >(response: ToolExecution<TData, TMeta>): ToolExecution<TData, TMeta> {
2424
- const toolResponse = response.toolResponse;
2425
- // An older V2 backend does not understand raw-v2 yet and therefore falls
2426
- // through to the historical `{ result: { data, meta? } }` response. New SDK
2427
- // clients must keep working during that server rollout window.
2428
- if (!toolResponse && isRecord(response.result)) {
2429
- const legacyResult = response.result;
2430
- if (Object.prototype.hasOwnProperty.call(legacyResult, 'data')) {
2431
- const legacyMeta = isRecord(legacyResult.meta)
2432
- ? (legacyResult.meta as TMeta)
2433
- : undefined;
2434
- return {
2435
- ...response,
2436
- toolResponse: {
2437
- raw: legacyResult.data as TData,
2438
- ...(legacyMeta ? { meta: legacyMeta } : {}),
2439
- },
2440
- };
2441
- }
2442
- }
2443
- if (
2444
- !toolResponse ||
2445
- Object.prototype.hasOwnProperty.call(toolResponse, 'raw')
2446
- ) {
2447
- return response;
2448
- }
2449
- const rawV2 = toolResponse.rawV2;
2450
- const view = toolResponse.view;
2451
- const providerMeta = providerMetaFromToolResponseRawV2(
2452
- rawV2,
2453
- view ?? 'rawV2',
2454
- ) as TMeta | undefined;
2455
- const responseMeta = isRecord(toolResponse.responseMeta)
2456
- ? (toolResponse.responseMeta as TMeta)
2457
- : undefined;
2458
- return {
2459
- ...response,
2460
- toolResponse: {
2461
- ...toolResponse,
2462
- raw: legacyRawFromToolResponseRawV2(
2463
- rawV2,
2464
- view ?? 'rawV2',
2465
- responseMeta as Record<string, unknown> | undefined,
2466
- ) as TData,
2467
- ...(toolResponse.meta || providerMeta || responseMeta
2468
- ? {
2469
- meta: {
2470
- ...(toolResponse.meta ?? {}),
2471
- ...(providerMeta ?? {}),
2472
- ...(responseMeta ?? {}),
2473
- } as TMeta,
2474
- }
2475
- : {}),
2476
- },
2477
- };
2478
- }
2479
-
2480
2182
  function isPrebuiltPlayDescription(
2481
2183
  play: Pick<PlayDescription, 'origin' | 'ownerType'>,
2482
2184
  ): boolean {
@@ -2926,6 +2628,7 @@ function playRunResultFromStatus(
2926
2628
  */
2927
2629
  export class DeeplineClient {
2928
2630
  private readonly http: HttpClient;
2631
+ private readonly toolCalls: ToolCalls;
2929
2632
  private readonly config: ResolvedConfig;
2930
2633
  /** Canonical run lifecycle namespace backed by `/api/v2/runs`. */
2931
2634
  readonly runs: RunsNamespace;
@@ -2950,6 +2653,7 @@ export class DeeplineClient {
2950
2653
  constructor(options?: DeeplineClientOptions) {
2951
2654
  this.config = resolveConfig(options);
2952
2655
  this.http = new HttpClient(this.config);
2656
+ this.toolCalls = new ToolCalls(this.http, this.config.timeout);
2953
2657
  this.runs = {
2954
2658
  get: (runId, options) => this.getRunStatus(runId, options),
2955
2659
  status: (runId) => this.getRunLifecycleStatus(runId),
@@ -3493,225 +3197,14 @@ export class DeeplineClient {
3493
3197
  input: Record<string, unknown>,
3494
3198
  options?: ExecuteToolRawOptions,
3495
3199
  ): Promise<ToolExecution<TData, TMeta>> {
3496
- // Snapshot JSON input before the caller's awaited onExecution callback and
3497
- // before any retry, so every POST is byte-equivalent in meaning.
3498
- const inputSnapshot = JSON.parse(JSON.stringify(input)) as Record<
3499
- string,
3500
- unknown
3501
- >;
3502
- const metadataSnapshot = options?.metadata
3503
- ? (JSON.parse(JSON.stringify(options.metadata)) as Record<
3504
- string,
3505
- unknown
3506
- >)
3507
- : undefined;
3508
- const idempotencyKey =
3509
- options?.idempotencyKey ??
3510
- (options?.recover ? crypto.randomUUID() : undefined);
3511
- if (idempotencyKey !== undefined) {
3512
- validateExecutionIdempotencyKey(idempotencyKey);
3513
- if (
3514
- options?.recoveryTimeoutMs !== undefined &&
3515
- (!Number.isFinite(options.recoveryTimeoutMs) ||
3516
- options.recoveryTimeoutMs < 0)
3517
- ) {
3518
- throw new DeeplineError(
3519
- 'recoveryTimeoutMs must be a finite, non-negative number.',
3520
- undefined,
3521
- 'IDEMPOTENCY_KEY_INVALID',
3522
- );
3523
- }
3524
- await options?.onExecution?.({ idempotencyKey });
3525
- const timeoutMs =
3526
- options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, inputSnapshot);
3527
- await this.lookupExecutionByKey(idempotencyKey, timeoutMs);
3528
- }
3529
- const timeoutMs =
3530
- options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, inputSnapshot);
3531
- const isApifySync = toolId.trim().toLowerCase() === 'apify_run_actor_sync';
3532
- const headers = {
3533
- [EXECUTE_RESPONSE_CONTRACT_HEADER]: RAW_V2_EXECUTE_RESPONSE_CONTRACT,
3534
- ...(isApifySync
3535
- ? { 'X-Deepline-Apify-Sync-Response-Budget': 'extended-v1' }
3536
- : {}),
3537
- [TOOL_EXECUTION_ERROR_SCHEMA_HEADER]: String(
3538
- TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
3539
- ),
3540
- ...(options?.includeToolMetadata
3541
- ? { [INCLUDE_TOOL_METADATA_HEADER]: 'true' }
3542
- : {}),
3543
- [EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? 'raw',
3544
- ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
3545
- ...(options?.searchRef
3546
- ? { 'x-deepline-search-ref': options.searchRef }
3547
- : {}),
3548
- };
3549
- const request = (requestTimeoutMs: number | undefined) =>
3550
- this.http.post<ToolExecution<TData, TMeta>>(
3551
- `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
3552
- {
3553
- payload: inputSnapshot,
3554
- ...(metadataSnapshot ? { metadata: metadataSnapshot } : {}),
3555
- },
3556
- headers,
3557
- {
3558
- timeout: requestTimeoutMs,
3559
- maxRetries: idempotencyKey ? 0 : (options?.maxRetries ?? 0),
3560
- retryApiErrors: options?.retryApiErrors === true,
3561
- exactUrlOnly: true,
3562
- toolId,
3563
- idempotencyKey,
3564
- },
3565
- );
3566
- let response: ToolExecution<TData, TMeta>;
3567
- let recoveryDeadline: number | null = null;
3568
- const recoveryTimeoutMs =
3569
- options?.recoveryTimeoutMs ?? DEFAULT_EXECUTION_RECOVERY_TIMEOUT_MS;
3570
- let retryDelayMs = 1_000;
3571
- const recoveryTimeoutError = () =>
3572
- executionRecoveryError({
3573
- toolId,
3574
- idempotencyKey: idempotencyKey!,
3575
- code: 'EXECUTION_RECOVERY_TIMEOUT',
3576
- message: `Execution recovery for ${idempotencyKey} did not finish before the recovery timeout. Inspect client.executions.getByKey() and resume with the same idempotency key.`,
3577
- });
3578
- const waitForRecoveryRetry = async (deadline: number) => {
3579
- const remainingMs = deadline - Date.now();
3580
- if (remainingMs <= 0) throw recoveryTimeoutError();
3581
- await new Promise((resolve) =>
3582
- setTimeout(resolve, Math.min(retryDelayMs, remainingMs)),
3583
- );
3584
- retryDelayMs = Math.min(retryDelayMs * 2, 5_000);
3585
- };
3586
- const lookupDuringRecovery = async () => {
3587
- while (true) {
3588
- const remainingMs = recoveryDeadline! - Date.now();
3589
- if (remainingMs <= 0) throw recoveryTimeoutError();
3590
- try {
3591
- return await this.lookupExecutionByKey(
3592
- idempotencyKey!,
3593
- timeoutWithinRecoveryBudget(
3594
- timeoutMs,
3595
- this.config.timeout,
3596
- remainingMs,
3597
- ),
3598
- );
3599
- } catch (error) {
3600
- if (!isRecoverableExecutionLookupError(error)) throw error;
3601
- await waitForRecoveryRetry(recoveryDeadline!);
3602
- }
3603
- }
3604
- };
3605
- while (true) {
3606
- const remainingMs =
3607
- recoveryDeadline === null ? null : recoveryDeadline - Date.now();
3608
- if (remainingMs !== null && remainingMs <= 0) {
3609
- throw recoveryTimeoutError();
3610
- }
3611
- try {
3612
- response = await request(
3613
- remainingMs === null
3614
- ? timeoutMs
3615
- : timeoutWithinRecoveryBudget(
3616
- timeoutMs,
3617
- this.config.timeout,
3618
- remainingMs,
3619
- ),
3620
- );
3621
- if (getExecutionRecoveryState(response) === 'outcome_unknown') {
3622
- if (!idempotencyKey) break;
3623
- throw executionRecoveryError({
3624
- toolId,
3625
- idempotencyKey,
3626
- code: 'EXECUTION_OUTCOME_UNKNOWN',
3627
- message: `The outcome of execution ${idempotencyKey} is unknown. Do not retry with a new key; inspect client.executions.getByKey().`,
3628
- });
3629
- }
3630
- if (!idempotencyKey || !isExecutionInProgressResponse(response)) {
3631
- break;
3632
- }
3633
- if (recoveryDeadline === null) {
3634
- recoveryDeadline = Date.now() + recoveryTimeoutMs;
3635
- }
3636
- } catch (error) {
3637
- if (
3638
- idempotencyKey &&
3639
- error instanceof DeeplineError &&
3640
- error.code === 'EXECUTION_OUTCOME_UNKNOWN'
3641
- ) {
3642
- throw executionRecoveryError({
3643
- toolId,
3644
- idempotencyKey,
3645
- code: 'EXECUTION_OUTCOME_UNKNOWN',
3646
- message: error.message,
3647
- });
3648
- }
3649
- if (!idempotencyKey || !isRecoverableExecutionAttemptError(error)) {
3650
- throw error;
3651
- }
3652
- if (recoveryDeadline === null) {
3653
- recoveryDeadline = Date.now() + recoveryTimeoutMs;
3654
- }
3655
- }
3656
- await waitForRecoveryRetry(recoveryDeadline!);
3657
- const recovered = await lookupDuringRecovery();
3658
- if (getExecutionRecoveryState(recovered) === 'outcome_unknown') {
3659
- throw executionRecoveryError({
3660
- toolId,
3661
- idempotencyKey,
3662
- code: 'EXECUTION_OUTCOME_UNKNOWN',
3663
- message: `The outcome of execution ${idempotencyKey} is unknown. Do not retry with a new key; inspect client.executions.getByKey().`,
3664
- });
3665
- }
3666
- }
3667
- const materialized = materializeToolExecutionResponse(response);
3668
- return idempotencyKey ? { ...materialized, idempotencyKey } : materialized;
3200
+ return this.toolCalls.call<TData, TMeta>(toolId, input, options);
3669
3201
  }
3670
3202
 
3671
3203
  /** Read the durable state of a keyed tool execution. */
3672
3204
  async getExecutionByKey(
3673
3205
  idempotencyKey: string,
3674
3206
  ): Promise<ExecutionByKeyResult> {
3675
- validateExecutionIdempotencyKey(idempotencyKey);
3676
- const result = await this.lookupExecutionByKey(idempotencyKey);
3677
- if (!result) {
3678
- throw new DeeplineError(
3679
- `No execution exists for idempotency key ${idempotencyKey}.`,
3680
- 404,
3681
- 'EXECUTION_NOT_FOUND',
3682
- );
3683
- }
3684
- return result;
3685
- }
3686
-
3687
- private async lookupExecutionByKey(
3688
- idempotencyKey: string,
3689
- timeoutMs?: number,
3690
- ): Promise<ExecutionByKeyResult | null> {
3691
- validateExecutionIdempotencyKey(idempotencyKey);
3692
- let supported = false;
3693
- const result = await this.http.get<ExecutionByKeyResult | null>(
3694
- `/api/v2/executions/by-key/${encodeURIComponent(idempotencyKey)}`,
3695
- {
3696
- exactUrlOnly: true,
3697
- maxRetries: 0,
3698
- timeout: timeoutMs,
3699
- idempotencyKey,
3700
- allowNotFound: true,
3701
- onResponse: (response) => {
3702
- supported =
3703
- response.headers.get('X-Deepline-Idempotency-Supported') === 'true';
3704
- },
3705
- },
3706
- );
3707
- if (!supported) {
3708
- throw new DeeplineError(
3709
- 'This Deepline server does not support recoverable tool executions; no tool was dispatched.',
3710
- 422,
3711
- 'IDEMPOTENCY_NOT_SUPPORTED',
3712
- );
3713
- }
3714
- return result;
3207
+ return this.toolCalls.getByKey(idempotencyKey);
3715
3208
  }
3716
3209
 
3717
3210
  /** Public recovery namespace. */
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.230',
203
+ version: '0.3.232',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {