deepline 0.3.238 → 0.3.240

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.
@@ -1806,6 +1806,15 @@ export type TargetBillingStatusResult = {
1806
1806
  payment_state: string;
1807
1807
  recharge_state: string;
1808
1808
  billing_email?: string | null;
1809
+ /** Omitted when unavailable; null when no address has been saved. */
1810
+ billing_address?: {
1811
+ line1: string | null;
1812
+ line2: string | null;
1813
+ city: string | null;
1814
+ state: string | null;
1815
+ postal_code: string | null;
1816
+ country: string | null;
1817
+ } | null;
1809
1818
  collection_method?: 'charge_automatically' | 'send_invoice';
1810
1819
  payment_access_policy?: 'payment_required' | 'net_30' | 'net_60';
1811
1820
  plan_invoice?: {
@@ -2163,7 +2172,9 @@ export type BillingNamespace = {
2163
2172
  options: TargetBillingPlanTransitionOptions,
2164
2173
  ) => Promise<TargetBillingMutationResult>;
2165
2174
  /** Create a Stripe-hosted billing Portal session. */
2166
- portalSession: () => Promise<{ url: string }>;
2175
+ portalSession: (options?: {
2176
+ flow?: 'customer_update';
2177
+ }) => Promise<{ url: string }>;
2167
2178
  };
2168
2179
 
2169
2180
  function requireTargetBillingIdempotencyKey(value: string): string {
@@ -2713,7 +2724,8 @@ export class DeeplineClient {
2713
2724
  recoverCreditPurchase: (options) =>
2714
2725
  this.recoverTargetBillingCreditPurchase(options),
2715
2726
  transitionPlan: (options) => this.transitionTargetBillingPlan(options),
2716
- portalSession: () => this.createTargetBillingPortalSession(),
2727
+ portalSession: (options) =>
2728
+ this.createTargetBillingPortalSession(options),
2717
2729
  };
2718
2730
  this.workspaces = {
2719
2731
  create: (options) => this.createWorkspace(options),
@@ -6154,11 +6166,18 @@ export class DeeplineClient {
6154
6166
  }
6155
6167
 
6156
6168
  /** Create a Stripe-hosted portal session for payment recovery and invoices. */
6157
- async createTargetBillingPortalSession(): Promise<{ url: string }> {
6169
+ async createTargetBillingPortalSession(options?: {
6170
+ flow?: 'customer_update';
6171
+ }): Promise<{ url: string }> {
6158
6172
  const response = await this.http.post<{
6159
6173
  data: { url: string };
6160
6174
  request_id?: string;
6161
- }>('/api/v2/billing/portal-sessions', {});
6175
+ }>(
6176
+ options?.flow === 'customer_update'
6177
+ ? '/api/v2/billing/portal-sessions?flow=customer_update'
6178
+ : '/api/v2/billing/portal-sessions',
6179
+ {},
6180
+ );
6162
6181
  return response.data;
6163
6182
  }
6164
6183
 
@@ -395,6 +395,11 @@ export class HttpClient {
395
395
  let fetchAttempts = 0;
396
396
  let lastRequestUrl = url;
397
397
  let networkFailedAt: number | undefined;
398
+ let networkPhase:
399
+ | 'before_request'
400
+ | 'retry_wait'
401
+ | 'awaiting_headers'
402
+ | 'reading_body' = 'before_request';
398
403
 
399
404
  const headers = this.authHeaders(options?.headers);
400
405
 
@@ -423,6 +428,7 @@ export class HttpClient {
423
428
  ? backoffMs
424
429
  : Math.max(backoffMs, retryAfterDelayMs);
425
430
  retryAfterDelayMs = null;
431
+ networkPhase = 'retry_wait';
426
432
  try {
427
433
  await sleep(delayMs, options?.signal);
428
434
  } catch (error) {
@@ -450,6 +456,7 @@ export class HttpClient {
450
456
  once: true,
451
457
  });
452
458
 
459
+ networkPhase = 'before_request';
453
460
  try {
454
461
  const requestBody =
455
462
  options?.formData !== undefined
@@ -461,6 +468,7 @@ export class HttpClient {
461
468
  : undefined;
462
469
  fetchAttempts += 1;
463
470
  lastRequestUrl = candidateUrl;
471
+ networkPhase = 'awaiting_headers';
464
472
  const response = await fetch(candidateUrl, {
465
473
  method,
466
474
  headers,
@@ -468,6 +476,7 @@ export class HttpClient {
468
476
  signal: controller.signal,
469
477
  });
470
478
 
479
+ networkPhase = 'reading_body';
471
480
  options?.onResponse?.(response);
472
481
  if (response.status === 404 && options?.allowNotFound) {
473
482
  clearTimeout(timeoutId);
@@ -671,6 +680,7 @@ export class HttpClient {
671
680
  // Elapsed time covers the entire logical request, including retries.
672
681
  elapsedMs: Math.max(0, failedAt - requestStartedAt),
673
682
  attempts: fetchAttempts,
683
+ phase: networkPhase,
674
684
  },
675
685
  };
676
686
  if (options?.toolId) {
@@ -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.238',
203
+ version: '0.3.240',
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: {
@@ -1,3 +1,4 @@
1
+ import { OPENSOSDATA_LOOKUP_CLIENT_TIMEOUT_MS } from '../../shared_libs/integration-contracts/opensosdata/execution-policy.js';
1
2
  import type { ToolResultBilling } from '../../shared_libs/plays/tool-result-types.js';
2
3
  import { resolveTheirstackClientTimeoutMs } from '../../shared_libs/integration-contracts/theirstack/execution-policy.js';
3
4
  import { resolveWaterfallClientTimeoutMs } from '../../shared_libs/integration-contracts/waterfall/execution-policy.js';
@@ -172,6 +173,9 @@ function resolveToolExecuteTimeoutMs(
172
173
  input: Record<string, unknown>,
173
174
  ): number | undefined {
174
175
  const normalized = toolId.trim().toLowerCase();
176
+ if (normalized === 'opensosdata_business_lookup') {
177
+ return OPENSOSDATA_LOOKUP_CLIENT_TIMEOUT_MS;
178
+ }
175
179
  if (normalized === 'apify_run_actor_sync') {
176
180
  const requestedTimeoutMs = input.timeoutMs ?? APIFY_SYNC_DEFAULT_TIMEOUT_MS;
177
181
  if (
@@ -228,6 +228,8 @@ export interface ToolDefinition extends ToolExecutionMetadataFields {
228
228
  outputSchema?: Record<string, unknown>;
229
229
  /** User-facing pricing summary. Internal provider/settlement costs are intentionally omitted. */
230
230
  pricing?: ToolPricingSummary | null;
231
+ /** How to use the tool well: the provider's summary and the tool's own notes. */
232
+ guidance?: { provider?: string; tool?: string };
231
233
  /** Copyable play-runtime guidance for V2 tool execution results. */
232
234
  usageGuidance?: {
233
235
  execute?: string;
@@ -0,0 +1,18 @@
1
+ /** OpenSOSData's single-entity lookup can complete synchronously or poll a 202 job. */
2
+ export const OPENSOSDATA_LOOKUP_REQUEST_TIMEOUT_MS = 120_000;
3
+ export const OPENSOSDATA_POLL_INTERVAL_MS = 3_000;
4
+ export const OPENSOSDATA_POLL_TIMEOUT_MS = 90_000;
5
+
6
+ /**
7
+ * Preserve the provider's existing request and polling windows. A final poll
8
+ * can begin just before the poll window ends, so include that in-flight request
9
+ * as well as route/admission and response overhead. Explicit caller deadlines
10
+ * still take precedence; unrelated tools retain their existing budgets.
11
+ */
12
+ export const OPENSOSDATA_LOOKUP_CLIENT_TIMEOUT_MS =
13
+ 60_000 +
14
+ OPENSOSDATA_LOOKUP_REQUEST_TIMEOUT_MS +
15
+ OPENSOSDATA_POLL_TIMEOUT_MS +
16
+ OPENSOSDATA_POLL_INTERVAL_MS +
17
+ OPENSOSDATA_LOOKUP_REQUEST_TIMEOUT_MS +
18
+ 15_000;
@@ -263,10 +263,40 @@ function targetLabel(target: PlayActivityTarget): string {
263
263
  case 'step':
264
264
  return target.label ?? target.stepId;
265
265
  case 'capacity':
266
- return target.label ?? `${target.pool} capacity`;
266
+ return target.pool === 'provider'
267
+ ? providerCapacityLabel(target.label)
268
+ : (target.label ?? `${target.pool} capacity`);
267
269
  }
268
270
  }
269
271
 
272
+ /** Older waits carried a rate scope rather than a customer-facing provider. */
273
+ export function providerCapacityLabel(label?: string): string {
274
+ if (!label) return 'The provider';
275
+ if (label.startsWith('org:')) return label.split(':')[1] || 'The provider';
276
+ return label.includes(':') ? 'The provider' : label;
277
+ }
278
+
279
+ function publicProviderCapacityObservation(
280
+ observation: PlayActivityObservation,
281
+ ): PlayActivityObservation {
282
+ if (
283
+ observation.target.kind !== 'capacity' ||
284
+ observation.target.pool !== 'provider'
285
+ )
286
+ return observation;
287
+ if (
288
+ !observation.target.label?.includes(':') &&
289
+ !observation.activityId.startsWith('capacity:provider:org:')
290
+ )
291
+ return observation;
292
+ const label = providerCapacityLabel(observation.target.label);
293
+ return {
294
+ ...observation,
295
+ activityId: `capacity:provider:${label}`,
296
+ target: { ...observation.target, label },
297
+ };
298
+ }
299
+
270
300
  export function summarizePlayActivity(
271
301
  observation: PlayActivityObservation,
272
302
  ): string {
@@ -297,10 +327,10 @@ export function summarizePlayActivity(
297
327
  if (state.reason.kind === 'lease') {
298
328
  return `Contended: waiting for ${state.reason.resourceKind} lease ${state.reason.resourceLabel}.`;
299
329
  }
300
- return `Paused: ${label}; the legacy producer did not report a typed reason.`;
330
+ return `Waiting: ${label}.`;
301
331
  case 'retrying':
302
332
  if (state.reason === 'rate_limit') {
303
- return `Waiting for ${label} request capacity; the run will retry automatically.`;
333
+ return `Waiting for ${label}'s rate limit to allow more calls. Completed work is saved; remaining calls will continue automatically.`;
304
334
  }
305
335
  return `Delayed: ${label} will retry automatically.`;
306
336
  case 'scheduled':
@@ -385,6 +415,7 @@ export function projectPlayRunActivity(input: {
385
415
  status: string;
386
416
  updatedAt?: number | null;
387
417
  waitKind?: string | null;
418
+ waitBoundaryId?: string | null;
388
419
  waitUntil?: number | null;
389
420
  eventKey?: string | null;
390
421
  runtimeBackend?: string | null;
@@ -410,17 +441,24 @@ export function projectPlayRunActivity(input: {
410
441
  const latestExplicit = [...(input.explicit ?? [])]
411
442
  .filter((candidate) => !isTerminalActivityState(candidate.state))
412
443
  .sort((left, right) => right.observedAt - left.observedAt)[0];
413
- // A provider wait is an observation, not a permanent run state. Once its
414
- // deadline has passed, use the current lifecycle/step facts until the next
415
- // producer observation arrives instead of claiming the old hold persists.
444
+ // Retry-After is the earliest retry, not evidence that execution resumed.
445
+ // Retain the reason while its provider boundary is still waiting, including
446
+ // lagging readers with no wait projection. A different durable wait wins.
447
+ const stillWaitingForProvider =
448
+ normalizedStatus === 'waiting' &&
449
+ (!input.waitKind ||
450
+ (input.waitKind === 'sleep' &&
451
+ input.waitBoundaryId?.includes(':provider-backpressure-') === true));
416
452
  const explicit =
453
+ !stillWaitingForProvider &&
417
454
  latestExplicit?.target.kind === 'capacity' &&
418
455
  latestExplicit.target.pool === 'provider' &&
419
456
  latestExplicit.state.kind === 'retrying' &&
420
457
  latestExplicit.state.retryAt < now
421
458
  ? null
422
459
  : latestExplicit;
423
- if (explicit) return projection(explicit, now);
460
+ if (explicit)
461
+ return projection(publicProviderCapacityObservation(explicit), now);
424
462
 
425
463
  if (input.waitKind === 'detached_runner') {
426
464
  return projection(
@@ -57,6 +57,7 @@ import type {
57
57
  } from './governor/rate-state-backend';
58
58
  import type { MapRowOutcome } from './durability-store';
59
59
  import { stringifyPostgresJson } from './postgres-json';
60
+ import { providerCapacityLabel } from './activity-observation';
60
61
  import { RUNTIME_ROW_OUTPUT_NOT_JSON_SERIALIZABLE } from './runtime-sheet-row-transition';
61
62
  import {
62
63
  RuntimeSheetRowWriter,
@@ -204,7 +205,7 @@ import {
204
205
  TOOL_EXECUTE_RUNNER_ACCEPTS_VALUE,
205
206
  TOOL_EXECUTE_PROVIDER_RATE_SCOPE_HEADER,
206
207
  TOOL_EXECUTE_PROVIDER_RATE_RETRY_SNAPSHOT_HEADER,
207
- TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER,
208
+ isAttestedProvider429Response,
208
209
  classifyToolExecuteHttpFailure,
209
210
  createToolExecuteHttpFailureAttemptTracker,
210
211
  parseToolExecuteRetryAfterMs,
@@ -9143,6 +9144,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9143
9144
 
9144
9145
  successfulCount = Math.max(0, successfulCount - staleCompletedKeys.size);
9145
9146
  failedCount = Math.max(0, failedCount - staleFailedKeys.size);
9147
+ this.#options.onMapRowsSettled?.({
9148
+ playName: this.#options.playName,
9149
+ runId: this.#options.runId,
9150
+ tableNamespace: resolvedTableNamespace,
9151
+ succeededRows: successfulCount,
9152
+ failedRows: failedCount,
9153
+ });
9146
9154
 
9147
9155
  // Isolated failures are a partial result only when at least one admitted
9148
9156
  // row completed. A fully failed admission run is systemic: preserve the
@@ -9995,6 +10003,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9995
10003
  });
9996
10004
  }
9997
10005
 
10006
+ this.#options.onMapRowsSettled?.({
10007
+ playName: this.#options.playName,
10008
+ runId: this.#options.runId,
10009
+ tableNamespace: resolvedTableNamespace,
10010
+ succeededRows:
10011
+ previouslyCompletedRowKeys.length + visibleCompletedRows.length,
10012
+ failedRows: durableFailedRows,
10013
+ });
10014
+
9998
10015
  // This map's terminal rows have already crossed the persistence barrier
9999
10016
  // above. Keep that verified snapshot as the authored return value instead
10000
10017
  // of immediately re-reading the Runtime Sheet projection. A second read
@@ -14532,11 +14549,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14532
14549
  retryAtMs: number,
14533
14550
  ): void {
14534
14551
  const observedAt = Date.now();
14552
+ const label = providerCapacityLabel(provider);
14535
14553
  this.emitExecutionEvent({
14536
14554
  type: 'activity.observed',
14537
14555
  observation: {
14538
- activityId: `capacity:provider:${provider}`,
14539
- target: { kind: 'capacity', pool: 'provider', label: provider },
14556
+ activityId: `capacity:provider:${label}`,
14557
+ target: { kind: 'capacity', pool: 'provider', label },
14540
14558
  state: {
14541
14559
  kind: 'retrying',
14542
14560
  reason: 'rate_limit',
@@ -19546,12 +19564,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19546
19564
  : undefined;
19547
19565
  }
19548
19566
  if (
19549
- dispatchedResponse.status === 429 &&
19550
- dispatchedResponse.headers.get(
19551
- TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER,
19552
- ) === '1'
19567
+ isAttestedProvider429Response(dispatchedResponse)
19553
19568
  ) {
19554
19569
  this.resourceGovernor.recordProvider429();
19570
+ }
19571
+ if (
19572
+ dispatchedResponse.status === 429 &&
19573
+ isAttestedProvider429Response(dispatchedResponse)
19574
+ ) {
19555
19575
  provider429RetryDiagnosticStartedAt =
19556
19576
  performance.now();
19557
19577
  provider429RetryDiagnosticNextInvocationAttempt =
@@ -931,6 +931,14 @@ export interface ContextOptions {
931
931
  */
932
932
  supersededKeys?: string[];
933
933
  }>;
934
+ /** Distinct terminal rows after the map's persistence barrier, across resumes. */
935
+ onMapRowsSettled?: (input: {
936
+ playName?: string;
937
+ runId?: string;
938
+ tableNamespace: string;
939
+ succeededRows: number;
940
+ failedRows: number;
941
+ }) => void;
934
942
  /**
935
943
  * Persists coalesced, non-terminal row patches before a map suspends.
936
944
  * Terminal rows use onMapRowsCompleted; this narrow seam only preserves
@@ -328,6 +328,7 @@ export function buildPlayRunStatusSnapshot(input: {
328
328
  status: projectedSnapshot.status,
329
329
  updatedAt: projectedSnapshot.updatedAt,
330
330
  waitKind: input.run.waitKind ?? input.run.wait?.kind ?? null,
331
+ waitBoundaryId: input.run.wait?.boundaryId ?? input.run.activeBoundaryId,
331
332
  waitUntil: input.run.waitUntil,
332
333
  eventKey: input.run.wait?.eventKey ?? null,
333
334
  runtimeBackend: input.run.runtimeBackend,
@@ -496,6 +496,20 @@ export function runtimeSheetTerminalWriteCounts(input: {
496
496
  };
497
497
  }
498
498
 
499
+ /** Distinct settled counts include saved siblings, never skipped input duplicates. */
500
+ export function runtimeSheetTerminalMapCounts(
501
+ current: { persistedRows: number; succeededRows: number; failedRows: number },
502
+ map: { succeededRows: number; failedRows: number },
503
+ ): { persistedRows: number; succeededRows: number; failedRows: number } {
504
+ const succeededRows = Math.max(current.succeededRows, map.succeededRows);
505
+ const failedRows = Math.max(current.failedRows, map.failedRows);
506
+ return {
507
+ persistedRows: Math.max(current.persistedRows, succeededRows + failedRows),
508
+ succeededRows,
509
+ failedRows,
510
+ };
511
+ }
512
+
499
513
  async function vercelBypassHeaders(
500
514
  config: PlayRunnerExecutionConfig,
501
515
  ): Promise<Record<string, string>> {
@@ -2308,6 +2322,35 @@ async function run(
2308
2322
  const MAP_ROW_UPDATED_EMIT_INTERVAL_MS = 200;
2309
2323
  const MAP_ROW_UPDATED_EMIT_SAMPLE_RATE = 100;
2310
2324
  return (event: PlayExecutionEvent) => {
2325
+ if (event.type === 'map.suspended') {
2326
+ const datasetId = createRuntimeDatasetId(
2327
+ config.context.playName!,
2328
+ event.artifactTableNamespace,
2329
+ );
2330
+ const settlement = runtimeMapDatasetSettlements.get(datasetId);
2331
+ if (settlement) {
2332
+ // This event follows the map writer's durability barrier. Only
2333
+ // report accepted writes, not progress (which includes duplicates).
2334
+ emitRunnerEvent(
2335
+ {
2336
+ type: 'execution_event',
2337
+ event: {
2338
+ type: 'dataset.lifecycle',
2339
+ datasetId,
2340
+ path: settlement.path,
2341
+ tableNamespace: settlement.tableNamespace,
2342
+ phase: 'registered',
2343
+ persistedRows: settlement.persistedRows,
2344
+ succeededRows: settlement.succeededRows,
2345
+ failedRows: settlement.failedRows,
2346
+ complete: false,
2347
+ at: event.at,
2348
+ },
2349
+ },
2350
+ options,
2351
+ );
2352
+ }
2353
+ }
2311
2354
  if (event.type === 'map.row.updated') {
2312
2355
  mapRowUpdatedCount++;
2313
2356
  const now = Date.now();
@@ -2638,6 +2681,19 @@ async function run(
2638
2681
  ),
2639
2682
  });
2640
2683
  },
2684
+ onMapRowsSettled: (input) => {
2685
+ const datasetId = createRuntimeDatasetId(
2686
+ input.playName ?? config.context.playName!,
2687
+ input.tableNamespace,
2688
+ );
2689
+ const settlement = runtimeMapDatasetSettlements.get(datasetId);
2690
+ if (settlement && (!input.runId || settlement.runId === input.runId)) {
2691
+ Object.assign(
2692
+ settlement,
2693
+ runtimeSheetTerminalMapCounts(settlement, input),
2694
+ );
2695
+ }
2696
+ },
2641
2697
  onMapRowsCompleted: async (input) => {
2642
2698
  // Persist executed rows (data + cell meta) into the tenant sheet —
2643
2699
  // the sheet is the source of truth for dataset state, and the
@@ -86,6 +86,23 @@ export const TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER =
86
86
  'x-deepline-provider-throttle';
87
87
  export const TOOL_EXECUTE_PROVIDER_PACING_WAIT_CODE = 'PROVIDER_PACING_WAIT';
88
88
 
89
+ /** A trusted provider 429 remains observable if the gateway cannot commit its body. */
90
+ export function isAttestedProvider429Response(response: {
91
+ status: number;
92
+ headers: Pick<Headers, 'get'>;
93
+ }): boolean {
94
+ if (response.headers.get(TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER) !== '1') {
95
+ return false;
96
+ }
97
+ if (response.status === 429) return true;
98
+ const code = response.headers.get('x-deepline-error-code');
99
+ return (
100
+ response.status === 409 &&
101
+ (code === 'durable_invocation_outcome_unknown' ||
102
+ code === 'durable_invocation_commit_failed')
103
+ );
104
+ }
105
+
89
106
  /**
90
107
  * Parse the advertisement as a comma-separated token list so a later runner can
91
108
  * add a capability without a second header. An absent or unparsable value means