deepline 0.2.9 → 0.2.11

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.
@@ -902,6 +902,66 @@ export type BillingInvoicesResult = {
902
902
  entries: BillingInvoiceEntry[];
903
903
  };
904
904
 
905
+ export type TargetBillingOperation = {
906
+ id: string;
907
+ state: string;
908
+ version: number;
909
+ next_action?: string | null;
910
+ };
911
+
912
+ export type TargetBillingPlan = {
913
+ sku: 'payg-v1' | 'builder-v1' | 'team-v1';
914
+ name: string;
915
+ recurring_price_usd: number;
916
+ interval: 'month';
917
+ included_credits: number;
918
+ purchased_credit_price_usd: number;
919
+ };
920
+
921
+ export type TargetBillingPlansResult = {
922
+ org_id: string;
923
+ plans: TargetBillingPlan[];
924
+ current_plan_sku: string;
925
+ pending_plan_sku: string | null;
926
+ acquisition_enabled: boolean;
927
+ };
928
+
929
+ export type TargetBillingStatusResult = {
930
+ org_id: string;
931
+ plan: {
932
+ sku: string;
933
+ name: string;
934
+ recurring_price_usd: number | null;
935
+ interval: string | null;
936
+ included_credits: number | null;
937
+ };
938
+ state: string;
939
+ changes_allowed: boolean;
940
+ payment_state: string;
941
+ recharge_state: string;
942
+ next_action: string | null;
943
+ pending_plan_sku: string | null;
944
+ as_of: string | null;
945
+ };
946
+
947
+ export type TargetBillingMutationResult = {
948
+ data: Record<string, unknown>;
949
+ operation: TargetBillingOperation;
950
+ request_id?: string;
951
+ };
952
+
953
+ export type TargetBillingPlanTransitionOptions =
954
+ | {
955
+ action: 'start_or_change';
956
+ targetPlanSku: 'payg-v1' | 'builder-v1' | 'team-v1';
957
+ idempotencyKey: string;
958
+ }
959
+ | {
960
+ action: 'cancel' | 'undo_cancel';
961
+ targetPlanSku?: never;
962
+ idempotencyKey: string;
963
+ };
964
+
905
965
  /** Saved payment method details returned by one-click billing top-up. */
906
966
  export type BillingPaymentMethodSummary = {
907
967
  brand?: string | null;
@@ -977,9 +1037,8 @@ export type BillingPlansResult = {
977
1037
  /**
978
1038
  * Public billing namespace exposed as `client.billing`.
979
1039
  *
980
- * Carries the durable Deepline billing product model — plans, subscription
981
- * state, period-end cancellation, and invoice/receipt history so CLI
982
- * commands and programmatic callers share the same surface.
1040
+ * Carries plans, subscription state, cancellation, and invoice/receipt history
1041
+ * so CLI commands and programmatic callers share one surface.
983
1042
  *
984
1043
  * @sdkReference client 030 client.billing
985
1044
  */
@@ -1006,8 +1065,39 @@ export type BillingNamespace = {
1006
1065
  /** Subscription invoices plus credit purchase receipts, newest first. */
1007
1066
  list: (options?: { limit?: number }) => Promise<BillingInvoicesResult>;
1008
1067
  };
1068
+ /** Metronome-authored target catalog and current Contract projection. */
1069
+ targetPlans: () => Promise<TargetBillingPlansResult>;
1070
+ /** Normalized target billing state. */
1071
+ targetStatus: () => Promise<TargetBillingStatusResult>;
1072
+ /** Buy Deepline credits through a payment-gated Metronome commit. */
1073
+ purchaseCredits: (options: {
1074
+ credits: number;
1075
+ idempotencyKey: string;
1076
+ }) => Promise<TargetBillingMutationResult>;
1077
+ /** Start, change, cancel, or undo a target plan transition. */
1078
+ transitionPlan: (
1079
+ options: TargetBillingPlanTransitionOptions,
1080
+ ) => Promise<TargetBillingMutationResult>;
1081
+ /** Create a Stripe-hosted billing Portal session. */
1082
+ portalSession: () => Promise<{ url: string }>;
1009
1083
  };
1010
1084
 
1085
+ function requireTargetBillingIdempotencyKey(value: string): string {
1086
+ const normalized = value.trim();
1087
+ if (
1088
+ normalized.length === 0 ||
1089
+ normalized.length > 200 ||
1090
+ normalized !== value
1091
+ ) {
1092
+ throw new DeeplineError(
1093
+ 'Billing idempotencyKey must contain 1–200 characters with no leading or trailing whitespace.',
1094
+ undefined,
1095
+ 'INVALID_BILLING_IDEMPOTENCY_KEY',
1096
+ );
1097
+ }
1098
+ return normalized;
1099
+ }
1100
+
1011
1101
  function isRecord(value: unknown): value is Record<string, unknown> {
1012
1102
  return Boolean(value && typeof value === 'object' && !Array.isArray(value));
1013
1103
  }
@@ -1495,6 +1585,11 @@ export class DeeplineClient {
1495
1585
  invoices: {
1496
1586
  list: (options) => this.listBillingInvoices(options),
1497
1587
  },
1588
+ targetPlans: () => this.getTargetBillingPlans(),
1589
+ targetStatus: () => this.getTargetBillingStatus(),
1590
+ purchaseCredits: (options) => this.purchaseTargetBillingCredits(options),
1591
+ transitionPlan: (options) => this.transitionTargetBillingPlan(options),
1592
+ portalSession: () => this.createTargetBillingPortalSession(),
1498
1593
  };
1499
1594
  this.monitors = {
1500
1595
  status: () => this.getMonitorsAccess(),
@@ -4016,6 +4111,67 @@ export class DeeplineClient {
4016
4111
  );
4017
4112
  }
4018
4113
 
4114
+ /** List the reviewed target plans and whether new acquisition is enabled. */
4115
+ async getTargetBillingPlans(): Promise<TargetBillingPlansResult> {
4116
+ return this.http.get<TargetBillingPlansResult>('/api/v2/billing/plans');
4117
+ }
4118
+
4119
+ /** Read the workspace's normalized target plan, payment, and balance state. */
4120
+ async getTargetBillingStatus(): Promise<TargetBillingStatusResult> {
4121
+ return this.http.get<TargetBillingStatusResult>('/api/v2/billing/status');
4122
+ }
4123
+
4124
+ /**
4125
+ * Purchase target-billing credits through the durable commercial operation
4126
+ * flow. The caller supplies an idempotency key for safe retries.
4127
+ */
4128
+ async purchaseTargetBillingCredits(options: {
4129
+ credits: number;
4130
+ idempotencyKey: string;
4131
+ }): Promise<TargetBillingMutationResult> {
4132
+ const idempotencyKey = requireTargetBillingIdempotencyKey(
4133
+ options.idempotencyKey,
4134
+ );
4135
+ return this.http.post<TargetBillingMutationResult>(
4136
+ '/api/v2/billing/credit-purchases',
4137
+ { credits: options.credits },
4138
+ { 'Idempotency-Key': idempotencyKey },
4139
+ { maxRetries: 0, exactUrlOnly: true },
4140
+ );
4141
+ }
4142
+
4143
+ /**
4144
+ * Start, change, cancel, or restore a target plan through one idempotent
4145
+ * commercial operation.
4146
+ */
4147
+ async transitionTargetBillingPlan(
4148
+ options: TargetBillingPlanTransitionOptions,
4149
+ ): Promise<TargetBillingMutationResult> {
4150
+ const idempotencyKey = requireTargetBillingIdempotencyKey(
4151
+ options.idempotencyKey,
4152
+ );
4153
+ return this.http.post<TargetBillingMutationResult>(
4154
+ '/api/v2/billing/plan-transitions',
4155
+ {
4156
+ action: options.action,
4157
+ ...(options.targetPlanSku
4158
+ ? { target_plan_sku: options.targetPlanSku }
4159
+ : {}),
4160
+ },
4161
+ { 'Idempotency-Key': idempotencyKey },
4162
+ { maxRetries: 0, exactUrlOnly: true },
4163
+ );
4164
+ }
4165
+
4166
+ /** Create a Stripe-hosted portal session for payment recovery and invoices. */
4167
+ async createTargetBillingPortalSession(): Promise<{ url: string }> {
4168
+ const response = await this.http.post<{
4169
+ data: { url: string };
4170
+ request_id?: string;
4171
+ }>('/api/v2/billing/portal-sessions', {});
4172
+ return response.data;
4173
+ }
4174
+
4019
4175
  // ——————————————————————————————————————————————————————————
4020
4176
  // Monitors
4021
4177
  // ——————————————————————————————————————————————————————————
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
160
160
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
161
161
  // exposed storage-dependent synchronous access. This deliberate minor
162
162
  // release keeps lazy paging semantics independent of row residency.
163
- version: '0.2.9',
163
+ version: '0.2.11',
164
164
  contracts: {
165
165
  api: {
166
166
  name: 'sdk-http-api',
@@ -328,6 +328,7 @@ type ProjectionStep = {
328
328
  nodeId: string;
329
329
  status?: string;
330
330
  label?: string;
331
+ artifactTableNamespace?: string | null;
331
332
  updatedAt?: number | null;
332
333
  progress?: PlayActivityProgress | null;
333
334
  };
@@ -490,6 +491,19 @@ export function projectPlayRunActivity(input: {
490
491
  )
491
492
  .sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
492
493
  if (pendingDataset) {
494
+ const activeDatasetStep = input.nodeStates?.find(
495
+ (candidate) =>
496
+ candidate.nodeId === input.activeNodeId &&
497
+ candidate.status === 'running' &&
498
+ candidate.artifactTableNamespace === pendingDataset.tableNamespace,
499
+ );
500
+ const datasetStep =
501
+ activeDatasetStep ??
502
+ input.nodeStates?.find(
503
+ (candidate) =>
504
+ candidate.artifactTableNamespace === pendingDataset.tableNamespace,
505
+ );
506
+ const datasetProgress = datasetStep?.progress ?? null;
493
507
  return projection(
494
508
  {
495
509
  schemaVersion: 1,
@@ -502,17 +516,20 @@ export function projectPlayRunActivity(input: {
502
516
  state: {
503
517
  kind: 'active',
504
518
  progress: {
505
- completed: pendingDataset.persistedRows,
519
+ completed:
520
+ datasetProgress?.completed ?? pendingDataset.persistedRows,
506
521
  total:
507
- typeof pendingDataset.succeededRows === 'number' ||
522
+ datasetProgress?.total ??
523
+ (typeof pendingDataset.succeededRows === 'number' ||
508
524
  typeof pendingDataset.failedRows === 'number'
509
525
  ? (pendingDataset.succeededRows ?? 0) +
510
526
  (pendingDataset.failedRows ?? 0)
511
- : undefined,
512
- failed: pendingDataset.failedRows,
527
+ : undefined),
528
+ failed: datasetProgress?.failed ?? pendingDataset.failedRows,
513
529
  },
514
530
  },
515
- observedAt: pendingDataset.updatedAt ?? observedAt,
531
+ observedAt:
532
+ datasetStep?.updatedAt ?? pendingDataset.updatedAt ?? observedAt,
516
533
  },
517
534
  now,
518
535
  );
@@ -367,17 +367,8 @@ function isRetryableAppRuntimeResponse(input: {
367
367
  // The app runtime may explicitly classify an otherwise-client-error status
368
368
  // as transient. Keep the structured delivery contract authoritative rather
369
369
  // than reducing every 4xx to permanent at the worker boundary.
370
- try {
371
- const parsed = JSON.parse(input.body) as unknown;
372
- if (
373
- parsed &&
374
- typeof parsed === 'object' &&
375
- !Array.isArray(parsed) &&
376
- (parsed as { retryable?: unknown }).retryable === true
377
- ) {
378
- return true;
379
- }
380
- } catch {}
370
+ const explicitRetryable = appRuntimeExplicitRetryable(input.body);
371
+ if (explicitRetryable !== null) return explicitRetryable;
381
372
  if (
382
373
  input.action === 'append_run_events' &&
383
374
  input.status === 500 &&
@@ -391,6 +382,19 @@ function isRetryableAppRuntimeResponse(input: {
391
382
  return isRetryableAppRuntimeResponseStatus(input.status);
392
383
  }
393
384
 
385
+ function appRuntimeExplicitRetryable(body: string): boolean | null {
386
+ try {
387
+ const parsed = JSON.parse(body) as unknown;
388
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
389
+ return null;
390
+ }
391
+ const retryable = (parsed as { retryable?: unknown }).retryable;
392
+ return typeof retryable === 'boolean' ? retryable : null;
393
+ } catch {
394
+ return null;
395
+ }
396
+ }
397
+
394
398
  function isRetryableAppRuntimeResponseStatus(status: number): boolean {
395
399
  return (
396
400
  status === 429 ||
@@ -1158,15 +1162,14 @@ async function postAppRuntimeApi<TResponse>(
1158
1162
  status: response.status,
1159
1163
  code,
1160
1164
  requestId,
1161
- // Immediate request retries are intentionally selective, but durable
1162
- // delivery must never classify an unrecognized upstream 5xx as poison.
1163
- retryable:
1164
- response.status >= 500 ||
1165
- isRetryableAppRuntimeResponse({
1166
- action: body.action,
1167
- status: response.status,
1168
- body: responseText,
1169
- }),
1165
+ // The structured response is authoritative even for a 5xx. Retrying an
1166
+ // explicitly deterministic database failure here turns one rejected SQL
1167
+ // statement into an unbounded outer-writer loop.
1168
+ retryable: isRetryableAppRuntimeResponse({
1169
+ action: body.action,
1170
+ status: response.status,
1171
+ body: responseText,
1172
+ }),
1170
1173
  detail: summarizeAppRuntimeErrorBody(responseText),
1171
1174
  boundaryLabel,
1172
1175
  });