deepline 0.3.143 → 0.3.145

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.
@@ -1960,6 +1960,21 @@ export type WorkspacesNamespace = {
1960
1960
  }) => Promise<WorkspaceCreateResult>;
1961
1961
  };
1962
1962
 
1963
+ /** One authenticated per-request usage event from `/api/v2/usage/events`. */
1964
+ export type BillingUsageEvent = {
1965
+ id: string | null;
1966
+ provider: string;
1967
+ operation: string;
1968
+ status: string;
1969
+ request_id: string;
1970
+ billing_outcome_reason: string | null;
1971
+ credits: number | null;
1972
+ billing_mode: string | null;
1973
+ pricing_model: string | null;
1974
+ policy_id: string | null;
1975
+ created_at: string;
1976
+ };
1977
+
1963
1978
  /**
1964
1979
  * Public `client.billing` namespace for CLI commands and programmatic callers.
1965
1980
  * Covers plans, subscription state, cancellation, and invoice/receipt history.
@@ -1989,6 +2004,8 @@ export type BillingNamespace = {
1989
2004
  /** Subscription invoices plus credit purchase receipts, newest first. */
1990
2005
  list: (options?: { limit?: number }) => Promise<BillingInvoicesResult>;
1991
2006
  };
2007
+ /** Read one exact execution outcome using the request_id returned by executeTool. */
2008
+ usageEvent: (requestId: string) => Promise<BillingUsageEvent>;
1992
2009
  /** Metronome-authored target catalog and current Contract projection. */
1993
2010
  targetPlans: () => Promise<TargetBillingPlansResult>;
1994
2011
  /** Normalized target billing state. */
@@ -2600,6 +2617,7 @@ export class DeeplineClient {
2600
2617
  invoices: {
2601
2618
  list: (options) => this.listBillingInvoices(options),
2602
2619
  },
2620
+ usageEvent: (requestId) => this.getBillingUsageEvent(requestId),
2603
2621
  targetPlans: () => this.getTargetBillingPlans(),
2604
2622
  targetStatus: () => this.getTargetBillingStatus(),
2605
2623
  autoRecharge: {
@@ -5803,6 +5821,40 @@ export class DeeplineClient {
5803
5821
  return this.http.get<BillingPlansResult>('/api/v2/billing/catalog/current');
5804
5822
  }
5805
5823
 
5824
+ /**
5825
+ * Read the authenticated usage record for one execution request. The
5826
+ * request id comes from the original `executeTool` result and is not a
5827
+ * retry or idempotency token.
5828
+ */
5829
+ async getBillingUsageEvent(requestId: string): Promise<BillingUsageEvent> {
5830
+ const normalizedRequestId = requestId.trim();
5831
+ if (
5832
+ normalizedRequestId.length === 0 ||
5833
+ normalizedRequestId.length > 200 ||
5834
+ normalizedRequestId !== requestId
5835
+ ) {
5836
+ throw new DeeplineError(
5837
+ 'Usage request_id must contain 1–200 characters with no leading or trailing whitespace.',
5838
+ undefined,
5839
+ 'INVALID_USAGE_REQUEST_ID',
5840
+ );
5841
+ }
5842
+ const response = await this.http.get<{
5843
+ entries?: BillingUsageEvent[];
5844
+ }>(
5845
+ `/api/v2/usage/events?request_id=${encodeURIComponent(normalizedRequestId)}`,
5846
+ );
5847
+ const event = response.entries?.[0];
5848
+ if (!event) {
5849
+ throw new DeeplineError(
5850
+ 'No usage event was found for this request_id.',
5851
+ undefined,
5852
+ 'USAGE_EVENT_NOT_FOUND',
5853
+ );
5854
+ }
5855
+ return event;
5856
+ }
5857
+
5806
5858
  /**
5807
5859
  * Charge the saved payment method and add Deepline credits to the active
5808
5860
  * workspace. Prefer `client.billing.topUp(...)`.
@@ -73,6 +73,7 @@ export type {
73
73
  BillingSubscriptionCancelResult,
74
74
  BillingSubscriptionStatus,
75
75
  BillingTopUpResult,
76
+ BillingUsageEvent,
76
77
  WorkspaceCreateResult,
77
78
  WorkspacesNamespace,
78
79
  MonitorsNamespace,
@@ -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.143',
203
+ version: '0.3.145',
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: {
@@ -664,6 +664,21 @@ function findFirstTargetByPath(
664
664
  return null;
665
665
  }
666
666
 
667
+ function findFirstExplicitNullTargetByPath(
668
+ result: unknown,
669
+ paths: readonly string[] | undefined,
670
+ ): ToolResultTargetMetadata | null {
671
+ for (const path of paths ?? []) {
672
+ for (const candidate of candidateResultPaths(path)) {
673
+ const explicitNull = valuesAtSegments(result, parsePath(candidate)).find(
674
+ (entry) => entry.value === null,
675
+ );
676
+ if (explicitNull) return { value: null, path: explicitNull.path };
677
+ }
678
+ }
679
+ return null;
680
+ }
681
+
667
682
  function firstValueForPaths(
668
683
  result: unknown,
669
684
  paths: readonly string[] | undefined,
@@ -1015,7 +1030,15 @@ function buildTargets(
1015
1030
  continue;
1016
1031
  }
1017
1032
  const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
1018
- if (!fromExtractor) continue;
1033
+ if (!fromExtractor) {
1034
+ // A declared null is an explicit provider answer, not permission to
1035
+ // guess from a similarly named sibling such as `emailDomain`.
1036
+ const explicitNull = isSemanticStatus
1037
+ ? null
1038
+ : findFirstExplicitNullTargetByPath(result, descriptor.paths);
1039
+ if (explicitNull) targets[target] = explicitNull;
1040
+ continue;
1041
+ }
1019
1042
  const transformed = coerceToEnum(
1020
1043
  applyExtractorTransforms(fromExtractor.value, descriptor),
1021
1044
  descriptor,
@@ -1038,6 +1061,14 @@ function buildTargets(
1038
1061
  targets[target] = fromMetadata;
1039
1062
  continue;
1040
1063
  }
1064
+ const explicitNull = findFirstExplicitNullTargetByPath(
1065
+ result,
1066
+ targetGetters?.[target],
1067
+ );
1068
+ if (explicitNull) {
1069
+ targets[target] = explicitNull;
1070
+ continue;
1071
+ }
1041
1072
  // Declared paths are routinely incomplete against the shape a provider
1042
1073
  // actually returns (zerobounce declares `result.data.email` and answers
1043
1074
  // with `address`), so the key scan stays as the rescue. It is bounded by
@@ -1053,6 +1084,7 @@ function buildTargets(
1053
1084
  }
1054
1085
  if (metadataTargets.size === 0) {
1055
1086
  for (const target of ['email', 'phone', 'linkedin', 'domain', 'status']) {
1087
+ if (targets[target]) continue;
1056
1088
  const found = findFirstTargetByKey(result, target);
1057
1089
  if (found) targets[target] = found;
1058
1090
  }
@@ -1333,8 +1365,8 @@ export function readValue(
1333
1365
  selector: readonly string[] | string,
1334
1366
  ): unknown {
1335
1367
  if (typeof selector === 'string') {
1336
- const declared = result.extractedValues[selector]?.get();
1337
- if (declared != null) return declared;
1368
+ const declared = result.extractedValues[selector];
1369
+ if (declared) return declared.get();
1338
1370
  }
1339
1371
  const root = resultRootOf(result);
1340
1372
  const paths = Array.isArray(selector) ? selector : [selector];
@@ -13,7 +13,17 @@ export type ToolResultBilling = {
13
13
  cost_usd?: number;
14
14
  /** Missing on historical responses. Pending pricing has no final amount. */
15
15
  pricing_status?: 'final' | 'pending';
16
- settlement_status?: 'pending' | 'queued' | 'settled' | 'requires_recovery';
16
+ settlement_status?:
17
+ | 'pending'
18
+ | 'queued'
19
+ | 'settled'
20
+ | 'released'
21
+ | 'requires_recovery';
22
+ billing_outcome_reason?:
23
+ | 'free_operation'
24
+ | 'provider_no_billable_result'
25
+ | 'provider_reported_zero_usage'
26
+ | 'zero_price';
17
27
  estimated_credits?: number;
18
28
  estimated_cost_usd?: number;
19
29
  /** Preserve additive public pricing details across runtime/deployment skew. */
@@ -108,6 +108,13 @@ type ProductNotificationRuleDefinition = {
108
108
  type NotificationTypeRegistration = NotificationTypeDefinition & {
109
109
  catalog?: ProductNotificationTypeCatalogMetadata;
110
110
  rule?: ProductNotificationRuleDefinition;
111
+ /**
112
+ * A platform-emitted rule with no catalog entry: never listed, never
113
+ * accepted by a workspace mutation, regardless of caller. Distinct from an
114
+ * inert catalog-less type (e.g. one with no producer yet) because a shadow
115
+ * type still ships a `rule.default` and fires for every workspace.
116
+ */
117
+ shadow?: true;
111
118
  };
112
119
 
113
120
  function validateInsufficientCreditDisable(
@@ -643,29 +650,19 @@ const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
643
650
  eventName: ProductNotificationEvent.CreditsPurchaseCompleted,
644
651
  requiredFilter: {},
645
652
  allowedTriggers: ['threshold_crossing'],
646
- catalog: {
647
- name: 'Credit purchase thank-you',
648
- category: 'credits',
649
- description:
650
- 'Thank the workspace when a credit purchase of at least the chosen size completes.',
651
- allowMultiple: false,
652
- editor: {
653
- kind: 'threshold',
654
- label: 'Minimum credits',
655
- defaultThreshold: 500,
656
- minimum: 0,
657
- suffix: 'credits',
658
- },
659
- },
653
+ // Credit purchase thank-you: fires for every workspace once a purchase
654
+ // reaches 500 credits. Not configurable — see the `shadow` field.
655
+ catalog: undefined,
656
+ shadow: true,
660
657
  rule: {
661
- default: { ruleKey: 'default.credits_purchased', groupKey: 'default' },
662
- create: (input, base, editor) => ({
658
+ default: { ruleKey: 'default.credits_purchased', groupKey: 'shadow' },
659
+ create: (input, base) => ({
663
660
  ...base,
664
661
  repeatMode: 'every_match',
665
662
  trigger: {
666
663
  kind: 'threshold_crossing',
667
664
  direction: 'at_or_above',
668
- threshold: input.threshold ?? defaultThreshold(editor),
665
+ threshold: input.threshold ?? 500,
669
666
  unit: 'credits',
670
667
  },
671
668
  }),
@@ -707,15 +704,12 @@ const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
707
704
  eventName: ProductNotificationEvent.BillingPlanPurchased,
708
705
  requiredFilter: {},
709
706
  allowedTriggers: ['each_match'],
710
- catalog: {
711
- name: 'Plan welcome',
712
- category: 'credits',
713
- description: 'Thank the workspace when it purchases a plan.',
714
- allowMultiple: false,
715
- editor: { kind: 'none' },
716
- },
707
+ // Plan welcome: fires for every workspace on plan purchase. Not
708
+ // configurable — see the `shadow` field.
709
+ catalog: undefined,
710
+ shadow: true,
717
711
  rule: {
718
- default: { ruleKey: 'default.plan_purchased', groupKey: 'default' },
712
+ default: { ruleKey: 'default.plan_purchased', groupKey: 'shadow' },
719
713
  create: (_input, base) => ({
720
714
  ...base,
721
715
  repeatMode: 'every_match',
@@ -911,10 +905,10 @@ export function createProductNotificationRule(input: {
911
905
  };
912
906
  if (rule?.create) {
913
907
  const editor = registration.catalog?.editor;
914
- if (!editor) {
908
+ if (!editor && !(registration as NotificationTypeRegistration).shadow) {
915
909
  throw new Error(`${input.notificationType} is not configurable.`);
916
910
  }
917
- return rule.create(input, base, editor);
911
+ return rule.create(input, base, editor ?? { kind: 'none' });
918
912
  }
919
913
  throw new Error(
920
914
  `Notification type ${input.notificationType} has no rule builder.`,
@@ -927,6 +921,17 @@ export const PRODUCT_NOTIFICATION_TYPE_CATALOG: readonly ProductNotificationType
927
921
  catalog ? [{ id, ...catalog }] : [],
928
922
  );
929
923
 
924
+ /**
925
+ * Platform-owned notification types with a default rule that fires for every
926
+ * workspace but is never listed or user-editable. A workspace mutation must
927
+ * reject any of these ids outright, not merely omit them from a listing.
928
+ */
929
+ export const PRODUCT_NOTIFICATION_SHADOW_TYPES: ReadonlySet<string> = new Set(
930
+ PRODUCT_NOTIFICATION_TYPE_REGISTRY.filter(
931
+ (registration) => (registration as NotificationTypeRegistration).shadow,
932
+ ).map(({ id }) => id),
933
+ );
934
+
930
935
  /** Code-owned defaults declared beside the notification types they enable. */
931
936
  export const PRODUCT_NOTIFICATION_DEFAULT_RULES =
932
937
  PRODUCT_NOTIFICATION_TYPE_REGISTRY.flatMap((registration) => {
@@ -14,7 +14,7 @@ import {
14
14
  * they create sparse copy-on-write overrides.
15
15
  */
16
16
  export const CURRENT_PRODUCT_NOTIFICATION_DEFAULT_POLICY = {
17
- version: 7,
17
+ version: 8,
18
18
  groups: [
19
19
  {
20
20
  groupKey: 'default',
@@ -22,6 +22,16 @@ export const CURRENT_PRODUCT_NOTIFICATION_DEFAULT_POLICY = {
22
22
  enabled: true,
23
23
  deliveryTiming: DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING,
24
24
  },
25
+ {
26
+ // Holds shadow (platform-owned, unconfigurable) rules. Kept in its own
27
+ // group so a customer save of the "default" group's rules never diffs
28
+ // against — and so never implicitly disables — a shadow rule it can't
29
+ // see. See PRODUCT_NOTIFICATION_SHADOW_TYPES.
30
+ groupKey: 'shadow',
31
+ name: 'Platform notifications',
32
+ enabled: true,
33
+ deliveryTiming: DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING,
34
+ },
25
35
  ],
26
36
  destinations: [
27
37
  {
@@ -31,6 +41,13 @@ export const CURRENT_PRODUCT_NOTIFICATION_DEFAULT_POLICY = {
31
41
  enabled: true,
32
42
  settings: { kind: 'email_roles', roles: ['owner'] },
33
43
  },
44
+ {
45
+ destinationKey: 'shadow.roles',
46
+ groupKey: 'shadow',
47
+ name: 'Workspace owners',
48
+ enabled: true,
49
+ settings: { kind: 'email_roles', roles: ['owner'] },
50
+ },
34
51
  ],
35
52
  rules: PRODUCT_NOTIFICATION_DEFAULT_RULES,
36
53
  } satisfies ProductNotificationDefaultPolicy;
package/dist/cli/index.js CHANGED
@@ -3068,7 +3068,7 @@ var SDK_RELEASE = {
3068
3068
  // getters keep their established compatibility behavior.
3069
3069
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
3070
3070
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
3071
- version: "0.3.143",
3071
+ version: "0.3.145",
3072
3072
  updateSummary: "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.",
3073
3073
  packageCapabilities: {
3074
3074
  updatePreferences: 1
@@ -7100,6 +7100,7 @@ var DeeplineClient = class _DeeplineClient {
7100
7100
  invoices: {
7101
7101
  list: (options2) => this.listBillingInvoices(options2)
7102
7102
  },
7103
+ usageEvent: (requestId) => this.getBillingUsageEvent(requestId),
7103
7104
  targetPlans: () => this.getTargetBillingPlans(),
7104
7105
  targetStatus: () => this.getTargetBillingStatus(),
7105
7106
  autoRecharge: {
@@ -9471,6 +9472,33 @@ var DeeplineClient = class _DeeplineClient {
9471
9472
  async getBillingPlans() {
9472
9473
  return this.http.get("/api/v2/billing/catalog/current");
9473
9474
  }
9475
+ /**
9476
+ * Read the authenticated usage record for one execution request. The
9477
+ * request id comes from the original `executeTool` result and is not a
9478
+ * retry or idempotency token.
9479
+ */
9480
+ async getBillingUsageEvent(requestId) {
9481
+ const normalizedRequestId = requestId.trim();
9482
+ if (normalizedRequestId.length === 0 || normalizedRequestId.length > 200 || normalizedRequestId !== requestId) {
9483
+ throw new DeeplineError(
9484
+ "Usage request_id must contain 1\u2013200 characters with no leading or trailing whitespace.",
9485
+ void 0,
9486
+ "INVALID_USAGE_REQUEST_ID"
9487
+ );
9488
+ }
9489
+ const response = await this.http.get(
9490
+ `/api/v2/usage/events?request_id=${encodeURIComponent(normalizedRequestId)}`
9491
+ );
9492
+ const event = response.entries?.[0];
9493
+ if (!event) {
9494
+ throw new DeeplineError(
9495
+ "No usage event was found for this request_id.",
9496
+ void 0,
9497
+ "USAGE_EVENT_NOT_FOUND"
9498
+ );
9499
+ }
9500
+ return event;
9501
+ }
9474
9502
  /**
9475
9503
  * Charge the saved payment method and add Deepline credits to the active
9476
9504
  * workspace. Prefer `client.billing.topUp(...)`.
@@ -47961,6 +47989,7 @@ Notes:
47961
47989
  Examples:
47962
47990
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
47963
47991
  deepline tools execute hunter_email_verifier -p email=a@b.com
47992
+ deepline tools execute test_rate_limit --input '{"key":"smoke"}' --timeout 90s --json
47964
47993
  deepline tools execute test_rate_limit --input '{"key":"smoke"}' --json | jq '.status'
47965
47994
  deepline tools execute free_simple_company_search --input '{"sql":"SELECT company_name FROM companies LIMIT 10"}' --out companies.csv
47966
47995
  `
@@ -47981,6 +48010,9 @@ Examples:
47981
48010
  ).option(
47982
48011
  "--payload <payload>",
47983
48012
  "Merge a JSON object or @file path into the tool params"
48013
+ ).option(
48014
+ "--timeout <duration>",
48015
+ "Execution HTTP deadline (for example 90s, 5m, or 1h; bare numbers are seconds)"
47984
48016
  ).option(
47985
48017
  "--output-format <format>",
47986
48018
  "Output format: auto, csv, csv_file, json, or json_file"
@@ -47995,6 +48027,7 @@ Examples:
47995
48027
  ...typeof options.json === "string" ? ["--json", options.json] : [],
47996
48028
  ...options.input ? ["--input", options.input] : [],
47997
48029
  ...options.payload ? ["--payload", options.payload] : [],
48030
+ ...options.timeout ? ["--timeout", options.timeout] : [],
47998
48031
  ...options.outputFormat ? ["--output-format", options.outputFormat] : [],
47999
48032
  ...options.out ? ["--out", options.out] : [],
48000
48033
  ...options.preview === false ? ["--no-preview"] : []
@@ -48747,8 +48780,10 @@ function toolInputFieldsForDisplay(inputSchema) {
48747
48780
  const jsonSchemaField = typeof field.name === "string" ? jsonSchemaFields.get(field.name) : void 0;
48748
48781
  const minItems = typeof field.minItems === "number" ? field.minItems : jsonSchemaField?.minItems;
48749
48782
  const maxItems = typeof field.maxItems === "number" ? field.maxItems : jsonSchemaField?.maxItems;
48783
+ const items = isRecord12(field.items) ? field.items : jsonSchemaField?.items;
48750
48784
  return {
48751
48785
  ...field,
48786
+ ...isRecord12(items) ? { items } : {},
48752
48787
  ...typeof minItems === "number" ? { minItems } : {},
48753
48788
  ...typeof maxItems === "number" ? { maxItems } : {}
48754
48789
  };
@@ -48764,6 +48799,7 @@ function toolInputFieldsForDisplay(inputSchema) {
48764
48799
  type: typeof property.type === "string" ? property.type : "unknown",
48765
48800
  required: required.has(name),
48766
48801
  description: property.description,
48802
+ ...isRecord12(property.items) ? { items: property.items } : {},
48767
48803
  ...typeof property.minItems === "number" ? { minItems: property.minItems } : {},
48768
48804
  ...typeof property.maxItems === "number" ? { maxItems: property.maxItems } : {},
48769
48805
  ...Object.prototype.hasOwnProperty.call(property, "default") ? { default: property.default } : {}
@@ -48883,6 +48919,7 @@ function recursiveToolInputFieldsForDisplay(inputSchema) {
48883
48919
  required,
48884
48920
  description: schema.description,
48885
48921
  ...Array.isArray(schema.enum) ? { enum: schema.enum } : {},
48922
+ ...isRecord12(schema.items) ? { items: schema.items } : {},
48886
48923
  ...typeof schema.minItems === "number" ? { minItems: schema.minItems } : {},
48887
48924
  ...typeof schema.maxItems === "number" ? { maxItems: schema.maxItems } : {},
48888
48925
  ...Object.prototype.hasOwnProperty.call(schema, "default") ? { default: schema.default } : {}
@@ -48917,25 +48954,31 @@ function recursiveToolInputFieldsForDisplay(inputSchema) {
48917
48954
  return fields.size ? [...fields.values()] : toolInputFieldsForDisplay(inputSchema);
48918
48955
  }
48919
48956
  function inputFieldJsonForDescribe(field) {
48957
+ const publicItems = isRecord12(field.items) ? publicToolInputSchemaForDescribe({ items: field.items }).items : void 0;
48920
48958
  return {
48921
48959
  name: field.name,
48922
48960
  type: field.type ?? "unknown",
48923
48961
  required: Boolean(field.required),
48924
48962
  ...field.description ? { description: field.description } : {},
48963
+ ...isRecord12(publicItems) ? { items: publicItems } : {},
48925
48964
  ...typeof field.minItems === "number" ? { minItems: field.minItems } : {},
48926
48965
  ...typeof field.maxItems === "number" ? { maxItems: field.maxItems } : {},
48927
48966
  ...Object.prototype.hasOwnProperty.call(field, "default") ? { default: field.default } : {}
48928
48967
  };
48929
48968
  }
48930
48969
  function arrayItemBoundsSuffix(field) {
48970
+ const itemSchema = isRecord12(field.items) ? field.items : null;
48971
+ const itemType = itemSchema ? typeof itemSchema.type === "string" ? itemSchema.type : isRecord12(itemSchema.properties) ? "object" : "unknown" : null;
48972
+ const itemEnum = Array.isArray(itemSchema?.enum) ? ` enum=${itemSchema.enum.map(String).join("|")}` : "";
48973
+ const itemTypeSuffix = itemType && itemType !== "unknown" ? ` items=${itemType}${itemEnum}` : "";
48931
48974
  const minItems = typeof field.minItems === "number" && Number.isFinite(field.minItems) ? field.minItems : null;
48932
48975
  const maxItems = typeof field.maxItems === "number" && Number.isFinite(field.maxItems) ? field.maxItems : null;
48933
48976
  if (minItems !== null && maxItems !== null) {
48934
- return ` items=${minItems}..${maxItems}`;
48977
+ return itemTypeSuffix ? `${itemTypeSuffix} minItems=${minItems} maxItems=${maxItems}` : ` items=${minItems}..${maxItems}`;
48935
48978
  }
48936
- if (minItems !== null) return ` minItems=${minItems}`;
48937
- if (maxItems !== null) return ` maxItems=${maxItems}`;
48938
- return "";
48979
+ if (minItems !== null) return `${itemTypeSuffix} minItems=${minItems}`;
48980
+ if (maxItems !== null) return `${itemTypeSuffix} maxItems=${maxItems}`;
48981
+ return itemTypeSuffix;
48939
48982
  }
48940
48983
  function printToolExecutionHints(value) {
48941
48984
  const batchCapabilityRecord = recordField2(
@@ -49224,13 +49267,14 @@ function parseExecuteOptions(args) {
49224
49267
  const toolId = args[0];
49225
49268
  if (!toolId) {
49226
49269
  throw new Error(
49227
- `Usage: deepline tools execute <toolId> [--param key=value ...] [--input '{"k":"v"}'] [--out rows.csv] [--output-format auto|csv|csv_file|json|json_file] [--no-preview]`
49270
+ `Usage: deepline tools execute <toolId> [--param key=value ...] [--input '{"k":"v"}'] [--timeout <duration>] [--out rows.csv] [--output-format auto|csv|csv_file|json|json_file] [--no-preview]`
49228
49271
  );
49229
49272
  }
49230
49273
  const params = {};
49231
49274
  let outputFormat = "auto";
49232
49275
  let noPreview = false;
49233
49276
  let outPath = null;
49277
+ let timeoutMs;
49234
49278
  for (let index = 1; index < args.length; index += 1) {
49235
49279
  const arg = args[index];
49236
49280
  if ((arg === "--param" || arg === "-p") && args[index + 1]) {
@@ -49254,6 +49298,10 @@ function parseExecuteOptions(args) {
49254
49298
  Object.assign(params, parseJsonObjectArgument(args[++index], arg));
49255
49299
  continue;
49256
49300
  }
49301
+ if (arg === "--timeout" && args[index + 1]) {
49302
+ timeoutMs = parseToolExecuteTimeout(args[++index]);
49303
+ continue;
49304
+ }
49257
49305
  if (arg === "--output-format" && args[index + 1]) {
49258
49306
  outputFormat = normalizeOutputFormat(args[++index]);
49259
49307
  continue;
@@ -49268,7 +49316,25 @@ function parseExecuteOptions(args) {
49268
49316
  }
49269
49317
  throw new Error(`Unknown option: ${arg}`);
49270
49318
  }
49271
- return { toolId, params, outputFormat, noPreview, outPath };
49319
+ return { toolId, params, outputFormat, noPreview, outPath, timeoutMs };
49320
+ }
49321
+ function parseToolExecuteTimeout(raw) {
49322
+ const match = /^(\d+)(ms|s|m|h)?$/i.exec(raw.trim());
49323
+ if (!match) {
49324
+ throw new Error(
49325
+ "--timeout must be a duration like 500ms, 90s, 10m, or 1h (a bare number is seconds)."
49326
+ );
49327
+ }
49328
+ const value = Number(match[1]);
49329
+ const unit = (match[2] ?? "s").toLowerCase();
49330
+ const factor = unit === "ms" ? 1 : unit === "s" ? 1e3 : unit === "m" ? 6e4 : 36e5;
49331
+ const timeoutMs = value * factor;
49332
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647) {
49333
+ throw new Error(
49334
+ "--timeout must be between 1ms and 2147483647ms (24d 20h 31m 23s)."
49335
+ );
49336
+ }
49337
+ return timeoutMs;
49272
49338
  }
49273
49339
  function safeFileStem(value) {
49274
49340
  return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "tool";
@@ -49571,7 +49637,8 @@ async function executeTool(args) {
49571
49637
  return 2;
49572
49638
  }
49573
49639
  const rawResponse = await client2.executeTool(parsed.toolId, parsed.params, {
49574
- responseIntent: parsed.outPath || parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file" ? "row_artifact" : "raw"
49640
+ responseIntent: parsed.outPath || parsed.outputFormat === "csv" || parsed.outputFormat === "csv_file" ? "row_artifact" : "raw",
49641
+ ...parsed.timeoutMs !== void 0 ? { timeout: parsed.timeoutMs } : {}
49575
49642
  });
49576
49643
  const listConversion = tryConvertToList(rawResponse, {
49577
49644
  listExtractorPaths: listExtractorPathsFromUsageGuidance(metadata)