deepline 0.1.103 → 0.1.105

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.
package/dist/index.mjs CHANGED
@@ -188,10 +188,13 @@ var SDK_RELEASE = {
188
188
  // preflight (existence, data rows, quotes, duplicate headers), HTML error
189
189
  // scrubbing, and word-boundary watch truncation.
190
190
  // 0.1.103 ships the refined SDK CLI command surface.
191
- version: "0.1.103",
191
+ // 0.1.104 ships postgres_fast suspension/billing parity and runtime worker hardening.
192
+ // 0.1.105 ships the billing catalog surface: billing plans, subscribe,
193
+ // subscription status/cancel, invoices, and the client.billing namespace.
194
+ version: "0.1.105",
192
195
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
193
196
  supportPolicy: {
194
- latest: "0.1.103",
197
+ latest: "0.1.105",
195
198
  minimumSupported: "0.1.53",
196
199
  deprecatedBelow: "0.1.53"
197
200
  }
@@ -322,7 +325,7 @@ var HttpClient = class {
322
325
  signal: controller.signal
323
326
  });
324
327
  clearTimeout(timeoutId);
325
- if (response.status === 401 || response.status === 403) {
328
+ if (response.status === 401 || response.status === 403 && !options?.forbiddenAsApiError) {
326
329
  throw new AuthError();
327
330
  }
328
331
  if (response.status === 429) {
@@ -1414,6 +1417,9 @@ var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
1414
1417
  var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
1415
1418
  var V2_EXECUTE_RESPONSE_CONTRACT = "v2-tool-response";
1416
1419
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
1420
+ var REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY = 3;
1421
+ var REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT = 3;
1422
+ var REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES = 25e5;
1417
1423
  function sleep2(ms) {
1418
1424
  return new Promise((resolve2) => setTimeout(resolve2, ms));
1419
1425
  }
@@ -1426,6 +1432,45 @@ function isTransientCompileManifestError(error) {
1426
1432
  message
1427
1433
  );
1428
1434
  }
1435
+ async function mapWithConcurrency(items, concurrency, mapper) {
1436
+ const results = new Array(items.length);
1437
+ let nextIndex = 0;
1438
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
1439
+ await Promise.all(
1440
+ Array.from({ length: workerCount }, async () => {
1441
+ for (; ; ) {
1442
+ const index = nextIndex;
1443
+ nextIndex += 1;
1444
+ if (index >= items.length) {
1445
+ return;
1446
+ }
1447
+ results[index] = await mapper(items[index], index);
1448
+ }
1449
+ })
1450
+ );
1451
+ return results;
1452
+ }
1453
+ function jsonUtf8Bytes(value) {
1454
+ return new TextEncoder().encode(JSON.stringify(value)).length;
1455
+ }
1456
+ function chunkRegisterPlayArtifacts(artifacts) {
1457
+ const chunks = [];
1458
+ let current = [];
1459
+ for (const artifact of artifacts) {
1460
+ const candidate = [...current, artifact];
1461
+ const candidateTooLarge = candidate.length > REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT || jsonUtf8Bytes({ artifacts: candidate }) > REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES;
1462
+ if (current.length > 0 && candidateTooLarge) {
1463
+ chunks.push(current);
1464
+ current = [artifact];
1465
+ } else {
1466
+ current = candidate;
1467
+ }
1468
+ }
1469
+ if (current.length > 0) {
1470
+ chunks.push(current);
1471
+ }
1472
+ return chunks;
1473
+ }
1429
1474
  var RUN_LOGS_PAGE_LIMIT = 1e3;
1430
1475
  function isRecord2(value) {
1431
1476
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -1593,6 +1638,8 @@ var DeeplineClient = class {
1593
1638
  config;
1594
1639
  /** Canonical run lifecycle namespace backed by `/api/v2/runs`. */
1595
1640
  runs;
1641
+ /** Billing namespace: subscription status/cancel and invoice history. */
1642
+ billing;
1596
1643
  /**
1597
1644
  * Create a low-level SDK client.
1598
1645
  *
@@ -1613,6 +1660,16 @@ var DeeplineClient = class {
1613
1660
  exportDatasetRows: (input) => this.getPlaySheetRows(input),
1614
1661
  stop: (runId, options2) => this.stopRun(runId, options2)
1615
1662
  };
1663
+ this.billing = {
1664
+ plans: () => this.getBillingPlans(),
1665
+ subscription: {
1666
+ status: () => this.getBillingSubscriptionStatus(),
1667
+ cancel: (options2) => this.cancelBillingSubscription(options2)
1668
+ },
1669
+ invoices: {
1670
+ list: (options2) => this.listBillingInvoices(options2)
1671
+ }
1672
+ };
1616
1673
  }
1617
1674
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
1618
1675
  get baseUrl() {
@@ -1981,8 +2038,13 @@ var DeeplineClient = class {
1981
2038
  * first when a compiler manifest is not already supplied.
1982
2039
  */
1983
2040
  async registerPlayArtifacts(artifacts) {
1984
- const compiledArtifacts = await Promise.all(
1985
- artifacts.map(async (artifact) => ({
2041
+ if (artifacts.length === 0) {
2042
+ return this.http.post("/api/v2/plays/artifacts", { artifacts });
2043
+ }
2044
+ const compiledArtifacts = await mapWithConcurrency(
2045
+ artifacts,
2046
+ REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY,
2047
+ async (artifact) => ({
1986
2048
  ...artifact,
1987
2049
  compilerManifest: artifact.compilerManifest ?? await this.compilePlayManifest({
1988
2050
  name: artifact.name,
@@ -1990,11 +2052,20 @@ var DeeplineClient = class {
1990
2052
  sourceFiles: artifact.sourceFiles,
1991
2053
  artifact: artifact.artifact
1992
2054
  })
1993
- }))
2055
+ })
1994
2056
  );
1995
- return this.http.post("/api/v2/plays/artifacts", {
1996
- artifacts: compiledArtifacts
1997
- });
2057
+ const responses = [];
2058
+ for (const chunk of chunkRegisterPlayArtifacts(compiledArtifacts)) {
2059
+ responses.push(
2060
+ await this.http.post("/api/v2/plays/artifacts", {
2061
+ artifacts: chunk
2062
+ })
2063
+ );
2064
+ }
2065
+ return {
2066
+ success: responses.every((response) => response.success),
2067
+ artifacts: responses.flatMap((response) => response.artifacts)
2068
+ };
1998
2069
  }
1999
2070
  /**
2000
2071
  * Compile a bundled play artifact into the server-side compiler manifest.
@@ -2967,6 +3038,61 @@ var DeeplineClient = class {
2967
3038
  // ——————————————————————————————————————————————————————————
2968
3039
  // Health
2969
3040
  // ——————————————————————————————————————————————————————————
3041
+ /**
3042
+ * Published plans plus the caller's active plan: prices, monthly grant
3043
+ * credits, rollover policy, and which plans are open for subscription.
3044
+ * Prefer `client.billing.plans()`.
3045
+ *
3046
+ * @returns Snake_case catalog from `GET /api/v2/billing/catalog/current`
3047
+ */
3048
+ async getBillingPlans() {
3049
+ return this.http.get("/api/v2/billing/catalog/current");
3050
+ }
3051
+ /**
3052
+ * Subscription state for the active workspace: active plan, whether a
3053
+ * Stripe subscription backs it, renewal/cancellation facts, and remaining
3054
+ * Deepline credit pools. Prefer `client.billing.subscription.status()`.
3055
+ *
3056
+ * @returns Snake_case subscription status from `GET /api/v2/billing/subscription/status`
3057
+ */
3058
+ async getBillingSubscriptionStatus() {
3059
+ return this.http.get(
3060
+ "/api/v2/billing/subscription/status"
3061
+ );
3062
+ }
3063
+ /**
3064
+ * Schedule subscription cancellation at period end, or reverse a pending
3065
+ * cancellation with `{ undo: true }`. The customer keeps the cycle they
3066
+ * paid for and every remaining credit — cancellation never claws back
3067
+ * credits. Prefer `client.billing.subscription.cancel(...)`.
3068
+ *
3069
+ * @throws {@link DeeplineError} with `statusCode: 409` when the workspace
3070
+ * has no active subscription, and `statusCode: 502` when Stripe rejects
3071
+ * the update (the server message is preserved).
3072
+ */
3073
+ async cancelBillingSubscription(options) {
3074
+ return this.http.post(
3075
+ "/api/v2/billing/subscription/cancel",
3076
+ { action: options?.undo ? "undo_cancel" : "cancel" }
3077
+ );
3078
+ }
3079
+ /**
3080
+ * Customer-facing billing history: subscription invoices plus one-time
3081
+ * credit purchase receipts, newest first, with Stripe-hosted links.
3082
+ * Prefer `client.billing.invoices.list(...)`.
3083
+ *
3084
+ * @param options.limit - Maximum entries to return (server clamps to 1–100, default 24).
3085
+ */
3086
+ async listBillingInvoices(options) {
3087
+ const params = new URLSearchParams();
3088
+ if (options?.limit !== void 0) {
3089
+ params.set("limit", String(options.limit));
3090
+ }
3091
+ const suffix = Array.from(params).length > 0 ? `?${params.toString()}` : "";
3092
+ return this.http.get(
3093
+ `/api/v2/billing/invoices${suffix}`
3094
+ );
3095
+ }
2970
3096
  /**
2971
3097
  * Check API connectivity and server health.
2972
3098
  *
@@ -84,6 +84,9 @@ const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
84
84
  const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
85
85
  const V2_EXECUTE_RESPONSE_CONTRACT = 'v2-tool-response';
86
86
  const COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1_000];
87
+ const REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY = 3;
88
+ const REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT = 3;
89
+ const REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES = 2_500_000;
87
90
 
88
91
  function sleep(ms: number): Promise<void> {
89
92
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -104,6 +107,58 @@ function isTransientCompileManifestError(error: unknown): boolean {
104
107
  );
105
108
  }
106
109
 
110
+ async function mapWithConcurrency<T, U>(
111
+ items: T[],
112
+ concurrency: number,
113
+ mapper: (item: T, index: number) => Promise<U>,
114
+ ): Promise<U[]> {
115
+ const results = new Array<U>(items.length);
116
+ let nextIndex = 0;
117
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
118
+ await Promise.all(
119
+ Array.from({ length: workerCount }, async () => {
120
+ for (;;) {
121
+ const index = nextIndex;
122
+ nextIndex += 1;
123
+ if (index >= items.length) {
124
+ return;
125
+ }
126
+ results[index] = await mapper(items[index]!, index);
127
+ }
128
+ }),
129
+ );
130
+ return results;
131
+ }
132
+
133
+ function jsonUtf8Bytes(value: unknown): number {
134
+ return new TextEncoder().encode(JSON.stringify(value)).length;
135
+ }
136
+
137
+ function chunkRegisterPlayArtifacts<T>(artifacts: T[]): T[][] {
138
+ const chunks: T[][] = [];
139
+ let current: T[] = [];
140
+
141
+ for (const artifact of artifacts) {
142
+ const candidate = [...current, artifact];
143
+ const candidateTooLarge =
144
+ candidate.length > REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT ||
145
+ jsonUtf8Bytes({ artifacts: candidate }) >
146
+ REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES;
147
+ if (current.length > 0 && candidateTooLarge) {
148
+ chunks.push(current);
149
+ current = [artifact];
150
+ } else {
151
+ current = candidate;
152
+ }
153
+ }
154
+
155
+ if (current.length > 0) {
156
+ chunks.push(current);
157
+ }
158
+
159
+ return chunks;
160
+ }
161
+
107
162
  type ExecuteToolRawOptions = {
108
163
  includeToolMetadata?: boolean;
109
164
  };
@@ -270,6 +325,153 @@ export type RunsNamespace = {
270
325
  ) => Promise<StopPlayRunResult>;
271
326
  };
272
327
 
328
+ /** One credit grant pool reported by the billing subscription status endpoint. */
329
+ export type BillingCreditPool = {
330
+ pool: string;
331
+ credits_remaining: number;
332
+ credits_granted: number;
333
+ source: string;
334
+ cycle_key: string | null;
335
+ effective_at: string;
336
+ };
337
+
338
+ /**
339
+ * Subscription state for the active workspace, from
340
+ * `GET /api/v2/billing/subscription/status`. All amounts are Deepline credits
341
+ * and Deepline-facing USD — never provider spend.
342
+ */
343
+ export type BillingSubscriptionStatus = {
344
+ org_id: string;
345
+ plan_version_id: string;
346
+ plan_name: string;
347
+ plan_family_id: string;
348
+ /** Whether a Stripe subscription backs the active plan. */
349
+ subscribed: boolean;
350
+ price_usd: number | null;
351
+ price_interval: string | null;
352
+ monthly_grant_credits: number;
353
+ assigned_by: string;
354
+ assigned_at: string | null;
355
+ credit_pools: BillingCreditPool[];
356
+ pooled_credits_remaining: number;
357
+ /** End of the current paid period (ISO timestamp), when Stripe is reachable. */
358
+ current_period_end: string | null;
359
+ /** True when a cancellation is already scheduled for period end. */
360
+ cancel_at_period_end: boolean | null;
361
+ stripe_status: string | null;
362
+ };
363
+
364
+ /**
365
+ * Result of `POST /api/v2/billing/subscription/cancel`. Cancellation is always
366
+ * at period end: the customer keeps the cycle they paid for and every
367
+ * remaining credit. `undo_cancel` reverses a pending cancellation.
368
+ */
369
+ export type BillingSubscriptionCancelResult = {
370
+ org_id: string;
371
+ subscription_id: string;
372
+ cancel_at_period_end: boolean;
373
+ current_period_end: string | null;
374
+ status: string;
375
+ message: string;
376
+ };
377
+
378
+ /**
379
+ * One customer-facing billing history entry from
380
+ * `GET /api/v2/billing/invoices`: a subscription invoice or a one-time credit
381
+ * purchase receipt. Amounts are what the customer paid — never provider spend.
382
+ */
383
+ export type BillingInvoiceEntry = {
384
+ kind: 'invoice' | 'receipt';
385
+ id: string;
386
+ created_at: string;
387
+ description: string;
388
+ amount_cents: number;
389
+ currency: string;
390
+ status: string;
391
+ /** Stripe-hosted page (invoice or card receipt). */
392
+ url: string | null;
393
+ /** Direct PDF when Stripe provides one (invoices only). */
394
+ pdf_url: string | null;
395
+ };
396
+
397
+ export type BillingInvoicesResult = {
398
+ org_id: string;
399
+ entries: BillingInvoiceEntry[];
400
+ };
401
+
402
+ /** One published plan from `GET /api/v2/billing/catalog/current`. */
403
+ export type BillingPlanEntry = {
404
+ plan_family_id: string;
405
+ plan_version_id: string;
406
+ public_name: string;
407
+ price_usd: number | null;
408
+ price_interval: string | null;
409
+ monthly_grant_credits: number;
410
+ rollover: { mode: string; max_credits?: number };
411
+ /** Whether the plan can be purchased via subscription checkout. */
412
+ acquirable: boolean;
413
+ };
414
+
415
+ /** One usage metric published in the billing catalog. */
416
+ export type BillingMetricEntry = {
417
+ id: string;
418
+ name: string;
419
+ };
420
+
421
+ /** The caller's active plan as reported by the plans endpoint. */
422
+ export type BillingActivePlan = {
423
+ plan_family_id: string;
424
+ plan_version_id: string;
425
+ public_name: string;
426
+ rate_card_id: string;
427
+ assigned_by: string;
428
+ assigned_at: string | null;
429
+ has_subscription: boolean;
430
+ };
431
+
432
+ /**
433
+ * Published plans plus the caller's active plan, from
434
+ * `GET /api/v2/billing/catalog/current`. Answers "what plans exist and what
435
+ * am I on". All amounts are Deepline credits and Deepline-facing USD — never
436
+ * provider spend.
437
+ */
438
+ export type BillingPlansResult = {
439
+ catalog_id: string;
440
+ catalog_version: string;
441
+ active_plan: BillingActivePlan;
442
+ plans: BillingPlanEntry[];
443
+ metrics: BillingMetricEntry[];
444
+ };
445
+
446
+ /**
447
+ * Public billing namespace exposed as `client.billing`.
448
+ *
449
+ * Carries the durable Deepline billing product model — plans, subscription
450
+ * state, period-end cancellation, and invoice/receipt history — so CLI
451
+ * commands and programmatic callers share the same surface.
452
+ *
453
+ * @sdkReference client 030 client.billing
454
+ */
455
+ export type BillingNamespace = {
456
+ /** Published plans plus the plan you are on ("what plans exist and what am I on"). */
457
+ plans: () => Promise<BillingPlansResult>;
458
+ subscription: {
459
+ /** Active plan, Stripe subscription state, and remaining credit pools. */
460
+ status: () => Promise<BillingSubscriptionStatus>;
461
+ /**
462
+ * Schedule cancellation at period end (credits are kept), or reverse a
463
+ * pending cancellation with `{ undo: true }`.
464
+ */
465
+ cancel: (options?: {
466
+ undo?: boolean;
467
+ }) => Promise<BillingSubscriptionCancelResult>;
468
+ };
469
+ invoices: {
470
+ /** Subscription invoices plus credit purchase receipts, newest first. */
471
+ list: (options?: { limit?: number }) => Promise<BillingInvoicesResult>;
472
+ };
473
+ };
474
+
273
475
  function isRecord(value: unknown): value is Record<string, unknown> {
274
476
  return Boolean(value && typeof value === 'object' && !Array.isArray(value));
275
477
  }
@@ -593,6 +795,8 @@ export class DeeplineClient {
593
795
  private readonly config: ResolvedConfig;
594
796
  /** Canonical run lifecycle namespace backed by `/api/v2/runs`. */
595
797
  readonly runs: RunsNamespace;
798
+ /** Billing namespace: subscription status/cancel and invoice history. */
799
+ readonly billing: BillingNamespace;
596
800
 
597
801
  /**
598
802
  * Create a low-level SDK client.
@@ -614,6 +818,16 @@ export class DeeplineClient {
614
818
  exportDatasetRows: (input) => this.getPlaySheetRows(input),
615
819
  stop: (runId, options) => this.stopRun(runId, options),
616
820
  };
821
+ this.billing = {
822
+ plans: () => this.getBillingPlans(),
823
+ subscription: {
824
+ status: () => this.getBillingSubscriptionStatus(),
825
+ cancel: (options) => this.cancelBillingSubscription(options),
826
+ },
827
+ invoices: {
828
+ list: (options) => this.listBillingInvoices(options),
829
+ },
830
+ };
617
831
  }
618
832
 
619
833
  /** The resolved base URL this client is targeting (e.g. `"http://localhost:3000"`). */
@@ -1164,8 +1378,13 @@ export class DeeplineClient {
1164
1378
  triggerBindings?: unknown;
1165
1379
  }>;
1166
1380
  }> {
1167
- const compiledArtifacts = await Promise.all(
1168
- artifacts.map(async (artifact) => ({
1381
+ if (artifacts.length === 0) {
1382
+ return this.http.post('/api/v2/plays/artifacts', { artifacts });
1383
+ }
1384
+ const compiledArtifacts = await mapWithConcurrency(
1385
+ artifacts,
1386
+ REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY,
1387
+ async (artifact) => ({
1169
1388
  ...artifact,
1170
1389
  compilerManifest:
1171
1390
  artifact.compilerManifest ??
@@ -1175,11 +1394,35 @@ export class DeeplineClient {
1175
1394
  sourceFiles: artifact.sourceFiles,
1176
1395
  artifact: artifact.artifact,
1177
1396
  })),
1178
- })),
1397
+ }),
1179
1398
  );
1180
- return this.http.post('/api/v2/plays/artifacts', {
1181
- artifacts: compiledArtifacts,
1182
- });
1399
+ const responses = [];
1400
+ for (const chunk of chunkRegisterPlayArtifacts(compiledArtifacts)) {
1401
+ responses.push(
1402
+ await this.http.post<{
1403
+ success: boolean;
1404
+ artifacts: Array<{
1405
+ success?: boolean;
1406
+ name?: string;
1407
+ artifactStorageKey: string;
1408
+ artifactMetadata?: Record<string, unknown> | null;
1409
+ staticPipeline?: unknown;
1410
+ definitionId?: string | null;
1411
+ revisionId?: string | null;
1412
+ version?: number | null;
1413
+ liveVersion?: number | null;
1414
+ triggerMetadata?: unknown;
1415
+ triggerBindings?: unknown;
1416
+ }>;
1417
+ }>('/api/v2/plays/artifacts', {
1418
+ artifacts: chunk,
1419
+ }),
1420
+ );
1421
+ }
1422
+ return {
1423
+ success: responses.every((response) => response.success),
1424
+ artifacts: responses.flatMap((response) => response.artifacts),
1425
+ };
1183
1426
  }
1184
1427
 
1185
1428
  /**
@@ -2420,6 +2663,69 @@ export class DeeplineClient {
2420
2663
  // Health
2421
2664
  // ——————————————————————————————————————————————————————————
2422
2665
 
2666
+ /**
2667
+ * Published plans plus the caller's active plan: prices, monthly grant
2668
+ * credits, rollover policy, and which plans are open for subscription.
2669
+ * Prefer `client.billing.plans()`.
2670
+ *
2671
+ * @returns Snake_case catalog from `GET /api/v2/billing/catalog/current`
2672
+ */
2673
+ async getBillingPlans(): Promise<BillingPlansResult> {
2674
+ return this.http.get<BillingPlansResult>('/api/v2/billing/catalog/current');
2675
+ }
2676
+
2677
+ /**
2678
+ * Subscription state for the active workspace: active plan, whether a
2679
+ * Stripe subscription backs it, renewal/cancellation facts, and remaining
2680
+ * Deepline credit pools. Prefer `client.billing.subscription.status()`.
2681
+ *
2682
+ * @returns Snake_case subscription status from `GET /api/v2/billing/subscription/status`
2683
+ */
2684
+ async getBillingSubscriptionStatus(): Promise<BillingSubscriptionStatus> {
2685
+ return this.http.get<BillingSubscriptionStatus>(
2686
+ '/api/v2/billing/subscription/status',
2687
+ );
2688
+ }
2689
+
2690
+ /**
2691
+ * Schedule subscription cancellation at period end, or reverse a pending
2692
+ * cancellation with `{ undo: true }`. The customer keeps the cycle they
2693
+ * paid for and every remaining credit — cancellation never claws back
2694
+ * credits. Prefer `client.billing.subscription.cancel(...)`.
2695
+ *
2696
+ * @throws {@link DeeplineError} with `statusCode: 409` when the workspace
2697
+ * has no active subscription, and `statusCode: 502` when Stripe rejects
2698
+ * the update (the server message is preserved).
2699
+ */
2700
+ async cancelBillingSubscription(options?: {
2701
+ undo?: boolean;
2702
+ }): Promise<BillingSubscriptionCancelResult> {
2703
+ return this.http.post<BillingSubscriptionCancelResult>(
2704
+ '/api/v2/billing/subscription/cancel',
2705
+ { action: options?.undo ? 'undo_cancel' : 'cancel' },
2706
+ );
2707
+ }
2708
+
2709
+ /**
2710
+ * Customer-facing billing history: subscription invoices plus one-time
2711
+ * credit purchase receipts, newest first, with Stripe-hosted links.
2712
+ * Prefer `client.billing.invoices.list(...)`.
2713
+ *
2714
+ * @param options.limit - Maximum entries to return (server clamps to 1–100, default 24).
2715
+ */
2716
+ async listBillingInvoices(options?: {
2717
+ limit?: number;
2718
+ }): Promise<BillingInvoicesResult> {
2719
+ const params = new URLSearchParams();
2720
+ if (options?.limit !== undefined) {
2721
+ params.set('limit', String(options.limit));
2722
+ }
2723
+ const suffix = Array.from(params).length > 0 ? `?${params.toString()}` : '';
2724
+ return this.http.get<BillingInvoicesResult>(
2725
+ `/api/v2/billing/invoices${suffix}`,
2726
+ );
2727
+ }
2728
+
2423
2729
  /**
2424
2730
  * Check API connectivity and server health.
2425
2731
  *
@@ -42,6 +42,14 @@ interface RequestOptions {
42
42
  headers?: Record<string, string>;
43
43
  /** Per-request timeout override in milliseconds. */
44
44
  timeout?: number;
45
+ /**
46
+ * Treat HTTP 403 as a regular API error instead of a generic {@link AuthError}.
47
+ * Use for endpoints that return 403 with a meaningful server error message
48
+ * (e.g. feature-flagged-off billing subscription checkout) so the server
49
+ * message is preserved and surfaced loudly. HTTP 401 still maps to
50
+ * {@link AuthError}.
51
+ */
52
+ forbiddenAsApiError?: boolean;
45
53
  }
46
54
 
47
55
  interface StreamOptions {
@@ -227,7 +235,10 @@ export class HttpClient {
227
235
 
228
236
  clearTimeout(timeoutId);
229
237
 
230
- if (response.status === 401 || response.status === 403) {
238
+ if (
239
+ response.status === 401 ||
240
+ (response.status === 403 && !options?.forbiddenAsApiError)
241
+ ) {
231
242
  throw new AuthError();
232
243
  }
233
244
 
@@ -57,6 +57,12 @@
57
57
  export { DeeplineClient } from './client.js';
58
58
  export { RunObserveTransportUnavailableError } from './runs/observe-transport.js';
59
59
  export type {
60
+ BillingCreditPool,
61
+ BillingInvoiceEntry,
62
+ BillingInvoicesResult,
63
+ BillingNamespace,
64
+ BillingSubscriptionCancelResult,
65
+ BillingSubscriptionStatus,
60
66
  PlayStatus,
61
67
  PlaySheetRow,
62
68
  PlaySheetRowsResult,
@@ -59,10 +59,13 @@ export const SDK_RELEASE = {
59
59
  // preflight (existence, data rows, quotes, duplicate headers), HTML error
60
60
  // scrubbing, and word-boundary watch truncation.
61
61
  // 0.1.103 ships the refined SDK CLI command surface.
62
- version: '0.1.103',
62
+ // 0.1.104 ships postgres_fast suspension/billing parity and runtime worker hardening.
63
+ // 0.1.105 ships the billing catalog surface: billing plans, subscribe,
64
+ // subscription status/cancel, invoices, and the client.billing namespace.
65
+ version: '0.1.105',
63
66
  apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
64
67
  supportPolicy: {
65
- latest: '0.1.103',
68
+ latest: '0.1.105',
66
69
  minimumSupported: '0.1.53',
67
70
  deprecatedBelow: '0.1.53',
68
71
  },
@@ -176,6 +176,32 @@ export function cloneCsvAliasedRow<T extends Record<string, unknown>>(
176
176
  return cloned;
177
177
  }
178
178
 
179
+ /**
180
+ * Plain enumerable clone for durable serialization (sheet writes, JSON
181
+ * payloads). Projected alias fields are normally non-enumerable so they stay
182
+ * out of spreads and Object.keys — but a JSON round-trip would silently drop
183
+ * them. This materializes every projected alias as a visible field and drops
184
+ * the internal `__deepline*` metadata keys.
185
+ */
186
+ export function toSerializableCsvAliasedRow(
187
+ row: Record<string, unknown>,
188
+ ): Record<string, unknown> {
189
+ const out: Record<string, unknown> = {};
190
+ for (const [field, value] of Object.entries(row)) {
191
+ if (field.startsWith('__deepline')) continue;
192
+ out[field] = value;
193
+ }
194
+ const projectedFields = getCsvProjectedFields(row);
195
+ if (projectedFields) {
196
+ const serializedValues = getCsvProjectedValues(row);
197
+ for (const field of projectedFields) {
198
+ if (Object.prototype.hasOwnProperty.call(out, field)) continue;
199
+ out[field] = row[field] ?? serializedValues?.[field];
200
+ }
201
+ }
202
+ return out;
203
+ }
204
+
179
205
  export function stripCsvProjectedFields<T extends Record<string, unknown>>(
180
206
  row: T,
181
207
  ): T {
@@ -48,7 +48,7 @@ export const PLAY_RUNTIME_PROVIDERS: Record<
48
48
  runner: PLAY_RUNTIME_BACKENDS.daytona,
49
49
  dedup: PLAY_DEDUP_BACKENDS.durableObject,
50
50
  artifactKind: PLAY_ARTIFACT_KINDS.cjsNode20,
51
- label: 'Experimental Postgres Scheduler + warm sandbox runner + DO dedup',
51
+ label: 'BETA: Postgres Scheduler + warm sandbox runner + DO dedup',
52
52
  },
53
53
  postgres_fast_sandbox: {
54
54
  id: PLAY_RUNTIME_PROVIDER_IDS.postgresFastSandbox,
@@ -56,7 +56,7 @@ export const PLAY_RUNTIME_PROVIDERS: Record<
56
56
  runner: PLAY_RUNTIME_BACKENDS.daytona,
57
57
  dedup: PLAY_DEDUP_BACKENDS.durableObject,
58
58
  artifactKind: PLAY_ARTIFACT_KINDS.cjsNode20,
59
- label: 'Experimental Postgres Scheduler + warm sandbox runner + DO dedup',
59
+ label: 'BETA: Postgres Scheduler + warm sandbox runner + DO dedup',
60
60
  },
61
61
  postgres_fast_workers: {
62
62
  id: PLAY_RUNTIME_PROVIDER_IDS.postgresFastWorkers,