deepline 0.3.59 → 0.3.60

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.
@@ -2057,6 +2057,9 @@ export class DeeplineClient {
2057
2057
  currentPublishedVersion:
2058
2058
  play.currentPublishedVersion ?? play.liveRevision?.version ?? null,
2059
2059
  latestRunId: play.latestRunId ?? detail.latestRuns[0]?.workflowId ?? null,
2060
+ ...(play.triggerMetadata
2061
+ ? { triggerMetadata: play.triggerMetadata }
2062
+ : {}),
2060
2063
  ...(play.runtimeLimit ? { runtimeLimit: play.runtimeLimit } : {}),
2061
2064
  ...(play.activeScheduledPlays
2062
2065
  ? { activeScheduledPlays: play.activeScheduledPlays }
@@ -94,6 +94,27 @@ export type ProjectPinTarget =
94
94
  candidates: string[];
95
95
  };
96
96
 
97
+ /**
98
+ * Where the CLI obtained the credentials for the current invocation.
99
+ *
100
+ * This is provenance only. SDK consumers may intentionally use host-level
101
+ * credentials, so CLI-only safety policy must be enforced by the CLI command
102
+ * dispatcher rather than by {@link resolveConfig}.
103
+ */
104
+ export type CliAuthProvenance = {
105
+ scope: 'env' | 'folder' | 'global';
106
+ project:
107
+ | {
108
+ state: 'project';
109
+ dir: string;
110
+ pinPath: string;
111
+ source: 'marker' | 'cowork' | 'folder';
112
+ }
113
+ | { state: 'not_project' }
114
+ | { state: 'ambiguous_cowork_project'; candidates: string[] };
115
+ folderAuthPath: string | null;
116
+ };
117
+
97
118
  /**
98
119
  * Convert a base URL to a filesystem-safe slug for per-host config storage.
99
120
  *
@@ -676,6 +697,75 @@ export function getActiveProjectAuthSource(
676
697
  return loadProjectEnvCandidates(startDir)[0] ?? null;
677
698
  }
678
699
 
700
+ function findNearestProjectMarkerDir(startDir: string): string | null {
701
+ let current = resolve(startDir);
702
+ while (true) {
703
+ if (
704
+ COWORK_PROJECT_MARKERS.some((marker) => existsSync(join(current, marker)))
705
+ ) {
706
+ return current;
707
+ }
708
+ const parent = dirname(current);
709
+ if (parent === current) return null;
710
+ current = parent;
711
+ }
712
+ }
713
+
714
+ /**
715
+ * Resolve credential provenance and whether the command is being run from a
716
+ * project. A project is a marked checkout, a Cowork-mounted project, or a
717
+ * folder that already carries Deepline project auth.
718
+ *
719
+ * The result intentionally does not expose an API key. It lets the CLI reject
720
+ * cloud mutations which would otherwise inherit a different project's shared
721
+ * host-level organization selection.
722
+ */
723
+ export function resolveCliAuthProvenance(
724
+ config: Pick<ResolvedConfig, 'baseUrl' | 'apiKey'>,
725
+ startDir: string = process.cwd(),
726
+ ): CliAuthProvenance {
727
+ const envApiKey = process.env[API_KEY_ENV]?.trim();
728
+ const folderAuth = getResolvedProjectAuthSource(
729
+ config.baseUrl,
730
+ config.apiKey,
731
+ startDir,
732
+ );
733
+ const pinTarget = resolveProjectPinTarget(startDir);
734
+ const markerDir = findNearestProjectMarkerDir(startDir);
735
+ const project = !pinTarget.ok
736
+ ? {
737
+ state: 'ambiguous_cowork_project' as const,
738
+ candidates: pinTarget.candidates,
739
+ }
740
+ : folderAuth
741
+ ? {
742
+ state: 'project' as const,
743
+ dir: dirname(folderAuth.filePath),
744
+ pinPath: folderAuth.filePath,
745
+ source: 'folder' as const,
746
+ }
747
+ : pinTarget.source === 'cowork'
748
+ ? {
749
+ state: 'project' as const,
750
+ dir: pinTarget.dir,
751
+ pinPath: join(pinTarget.dir, PROJECT_DEEPLINE_ENV_FILE),
752
+ source: 'cowork' as const,
753
+ }
754
+ : markerDir
755
+ ? {
756
+ state: 'project' as const,
757
+ dir: markerDir,
758
+ pinPath: join(markerDir, PROJECT_DEEPLINE_ENV_FILE),
759
+ source: 'marker' as const,
760
+ }
761
+ : { state: 'not_project' as const };
762
+ return {
763
+ scope: envApiKey ? 'env' : folderAuth ? 'folder' : 'global',
764
+ project,
765
+ folderAuthPath: folderAuth?.filePath ?? null,
766
+ };
767
+ }
768
+
679
769
  export {
680
770
  baseUrlSlug,
681
771
  loadCliEnv,
@@ -241,7 +241,8 @@ export type PlayFetchResponse = PlayAuthoringFetchResponse;
241
241
  *
242
242
  * @sdkReference runtime 030
243
243
  */
244
- export type PlayBindings = PlayAuthoringBindings;
244
+ export type PlayBindings<TInput = Record<string, unknown>> =
245
+ PlayAuthoringBindings<TInput>;
245
246
  export type SqlListenerOperation = PlaySqlListenerOperation;
246
247
  export type SqlListenerFilterScalar = PlaySqlListenerFilterScalar;
247
248
  export type SqlListenerFilterOperator = PlaySqlListenerFilterOperator;
@@ -873,14 +874,28 @@ export type DefinedPlay<
873
874
  DeeplineNamedPlay<TInput, TOutput>
874
875
  >;
875
876
 
876
- type PlayMetadata = {
877
+ type PlayHandlerInput<THandler> = THandler extends (
878
+ context: DeeplinePlayRuntimeContext,
879
+ input: infer TInput,
880
+ ) => Promise<PlayReturnObject>
881
+ ? TInput
882
+ : never;
883
+
884
+ type PlayHandlerOutput<THandler> = THandler extends (
885
+ context: DeeplinePlayRuntimeContext,
886
+ input: unknown,
887
+ ) => Promise<infer TOutput extends PlayReturnObject>
888
+ ? TOutput
889
+ : never;
890
+
891
+ type PlayMetadata<TInput = Record<string, unknown>> = {
877
892
  name: string;
878
893
  description?: string;
879
- bindings?: PlayBindings;
894
+ bindings?: PlayBindings<TInput>;
880
895
  inputSchema?: Record<string, unknown>;
881
- billing?: PlayBindings['billing'];
882
- runtime?: PlayBindings['runtime'];
883
- compatibility?: PlayBindings['compatibility'];
896
+ billing?: PlayBindings<TInput>['billing'];
897
+ runtime?: PlayBindings<TInput>['runtime'];
898
+ compatibility?: PlayBindings<TInput>['compatibility'];
884
899
  };
885
900
 
886
901
  const PLAY_METADATA_SYMBOL = Symbol.for('deepline.play.metadata');
@@ -1608,6 +1623,18 @@ export function defineInput<TInput>(
1608
1623
  export function definePlay<TInput, TOutput extends PlayReturnObject>(
1609
1624
  config: DefinePlayConfig<TInput, TOutput>,
1610
1625
  ): DefinedPlay<TInput, TOutput>;
1626
+ /** @internal Contextually type unannotated monitor-event handlers as unknown. */
1627
+ export function definePlay<TOutput extends PlayReturnObject>(
1628
+ name: string,
1629
+ fn: (
1630
+ context: DeeplinePlayRuntimeContext,
1631
+ input: unknown,
1632
+ ) => Promise<TOutput>,
1633
+ bindings: PlayBindings<never> & {
1634
+ readonly sqlListeners: readonly SqlListenerDeclaration[];
1635
+ },
1636
+ ): DefinedPlay<unknown, TOutput>;
1637
+ /* eslint-disable @typescript-eslint/no-explicit-any -- This constraint must infer a concrete contravariant handler input. */
1611
1638
  /**
1612
1639
  * Define a play with a name and function.
1613
1640
  *
@@ -1616,11 +1643,17 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
1616
1643
  * @param bindings - Play configuration, including runtime limits and triggers.
1617
1644
  * @returns Play handle.
1618
1645
  */
1619
- export function definePlay<TInput, TOutput extends PlayReturnObject>(
1646
+ export function definePlay<
1647
+ THandler extends (
1648
+ context: DeeplinePlayRuntimeContext,
1649
+ input: any,
1650
+ ) => Promise<PlayReturnObject>,
1651
+ >(
1620
1652
  name: string,
1621
- fn: (ctx: DeeplinePlayRuntimeContext, input: TInput) => Promise<TOutput>,
1622
- bindings?: PlayBindings,
1623
- ): DefinedPlay<TInput, TOutput>;
1653
+ fn: THandler,
1654
+ bindings?: PlayBindings<NoInfer<PlayHandlerInput<THandler>>>,
1655
+ ): DefinedPlay<PlayHandlerInput<THandler>, PlayHandlerOutput<THandler>>;
1656
+ /* eslint-enable @typescript-eslint/no-explicit-any */
1624
1657
  /**
1625
1658
  * @sdkReference runtime 010
1626
1659
  */
@@ -1630,7 +1663,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
1630
1663
  ctx: DeeplinePlayRuntimeContext,
1631
1664
  input: TInput,
1632
1665
  ) => Promise<TOutput>,
1633
- maybeBindings?: PlayBindings,
1666
+ maybeBindings?: PlayBindings<TInput>,
1634
1667
  ): DefinedPlay<TInput, TOutput> {
1635
1668
  const config =
1636
1669
  typeof nameOrConfig === 'string'
@@ -1690,7 +1723,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
1690
1723
  );
1691
1724
  }
1692
1725
 
1693
- const metadata: PlayMetadata = {
1726
+ const metadata: PlayMetadata<TInput> = {
1694
1727
  name,
1695
1728
  ...(description ? { description } : {}),
1696
1729
  ...(bindings ? { bindings } : {}),
@@ -199,7 +199,7 @@ export const SDK_RELEASE = {
199
199
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
- version: '0.3.59',
202
+ version: '0.3.60',
203
203
  updateSummary:
204
204
  'Automatic CLI updates are now enabled by default. To opt out, run `deepline settings autoupdate off`; use `deepline settings autoupdate on` to re-enable updates or `deepline settings autoupdate pin <version>` to hold an exact release. This release also adds raw-v2 tool responses at toolResponse.rawV2 while preserving existing toolResponse.raw and declared getters.',
205
205
  packageCapabilities: {
@@ -1089,6 +1089,20 @@ export interface PlayDefinitionDetail {
1089
1089
  liveRevision?: PlayRevisionSummary | null;
1090
1090
  /** `true` if the working revision differs from the live revision. */
1091
1091
  isDraftDirty?: boolean;
1092
+ /** Live trigger state. This is operational state, not a source declaration. */
1093
+ triggerStatus?: PlayTriggerStatus;
1094
+ /**
1095
+ * Additive scheduler metadata for human/UI rendering. Values are absent for
1096
+ * plays with no cron binding and must never be inferred from source alone.
1097
+ */
1098
+ triggerMetadata?: {
1099
+ cronSchedule?: string | null;
1100
+ cronTimezone?: string | null;
1101
+ nextScheduledAt?: number | null;
1102
+ lastScheduledAt?: number | null;
1103
+ blockedReason?: string | null;
1104
+ [key: string]: unknown;
1105
+ } | null;
1092
1106
  /** Effective sandbox limit from the live revision, or the working draft before first publish. */
1093
1107
  runtimeLimit?: PlayRuntimeLimit | null;
1094
1108
  /** Present for a Play with a cron trigger. Advisory only; publish remains authoritative. */
@@ -1125,11 +1139,14 @@ export interface PlayListItem {
1125
1139
  currentRevision?: PlayRevisionSummary | null;
1126
1140
  liveRevision?: PlayRevisionSummary | null;
1127
1141
  aliases?: string[];
1128
- triggerStatus?: {
1129
- cron: string | null;
1130
- webhook: string | null;
1131
- blockedReason: string | null;
1132
- };
1142
+ triggerStatus?: PlayTriggerStatus;
1143
+ }
1144
+
1145
+ /** Additive live trigger state shared by list, get, and describe. */
1146
+ export interface PlayTriggerStatus {
1147
+ cron: string | null;
1148
+ webhook: string | null;
1149
+ blockedReason: string | null;
1133
1150
  }
1134
1151
 
1135
1152
  export interface ProductNotificationEventDefinition {
@@ -1202,11 +1219,9 @@ export interface PlayDescription {
1202
1219
  */
1203
1220
  liveVersion?: number | null;
1204
1221
  /** Whether this play's cron and webhook triggers are armed. */
1205
- triggerStatus?: {
1206
- cron: string | null;
1207
- webhook: string | null;
1208
- blockedReason: string | null;
1209
- };
1222
+ triggerStatus?: PlayTriggerStatus;
1223
+ /** Additive cron timing metadata for an operational description. */
1224
+ triggerMetadata?: PlayDefinitionDetail['triggerMetadata'];
1210
1225
  isDraftDirty?: boolean;
1211
1226
  latestRunId?: string | null;
1212
1227
  /** Effective sandbox limit from the revision named runs use. */
@@ -1512,7 +1527,12 @@ export interface PlayCheckSqlListenerEventSummary {
1512
1527
  export interface PlayCheckTriggersSummary {
1513
1528
  sqlListeners?: PlayCheckSqlListenerTrigger[];
1514
1529
  sqlListenerEvent?: PlayCheckSqlListenerEventSummary;
1515
- cron?: { schedule: string; timezone?: string };
1530
+ cron?: {
1531
+ schedule: string;
1532
+ timezone?: string;
1533
+ /** Static input supplied to every run started by this cron binding. */
1534
+ input?: Record<string, unknown>;
1535
+ };
1516
1536
  webhook?: true;
1517
1537
  }
1518
1538
 
@@ -113,6 +113,7 @@ import {
113
113
  import {
114
114
  isProviderUnavailable,
115
115
  serializeToolExecutionFailure,
116
+ ToolExecutionError,
116
117
  TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
117
118
  TOOL_EXECUTION_ERROR_SCHEMA_HEADER,
118
119
  type ToolExecutionErrorSchemaVersion,
@@ -2036,6 +2037,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2036
2037
  private readonly toolDispatchQueuedAtByLane = new Map<string, number>();
2037
2038
  private readonly toolDispatcherWakeWaiters = new Set<() => void>();
2038
2039
  private toolDispatcherFailure: unknown | null = null;
2040
+ /**
2041
+ * Deepline's own zero-credit denial is run-fatal. Keep it independently of
2042
+ * customer control flow so a broad `try/catch` cannot turn it into a
2043
+ * successful Play result. Provider account capacity remains a normal typed
2044
+ * waterfall miss.
2045
+ */
2046
+ private deeplineInsufficientCreditsFailure: ToolExecutionError | null = null;
2039
2047
  private toolCallResolvers = new Map<
2040
2048
  string,
2041
2049
  { resolve: (value: unknown) => void; reject: (reason: unknown) => void }
@@ -2218,10 +2226,30 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2218
2226
  : {}),
2219
2227
  }
2220
2228
  : undefined,
2221
- ) as Promise<TOutput>;
2229
+ ).catch((error: unknown) => {
2230
+ this.recordDeeplineInsufficientCreditsFailure(error);
2231
+ throw error;
2232
+ }) as Promise<TOutput>;
2222
2233
  },
2223
2234
  };
2224
2235
 
2236
+ private recordDeeplineInsufficientCreditsFailure(error: unknown): void {
2237
+ if (
2238
+ error instanceof ToolExecutionError &&
2239
+ error.origin === 'deepline' &&
2240
+ error.code === 'INSUFFICIENT_CREDITS'
2241
+ ) {
2242
+ this.deeplineInsufficientCreditsFailure ??= error;
2243
+ }
2244
+ }
2245
+
2246
+ /** Called by the runner after customer code returns, before a success settles. */
2247
+ assertNoDeeplineInsufficientCreditsFailure(): void {
2248
+ if (this.deeplineInsufficientCreditsFailure) {
2249
+ throw this.deeplineInsufficientCreditsFailure;
2250
+ }
2251
+ }
2252
+
2225
2253
  async tool<TOutput = unknown>(
2226
2254
  key: string,
2227
2255
  toolId: string,
@@ -237,29 +237,6 @@ function isHardBillingFailurePayload(
237
237
  );
238
238
  }
239
239
 
240
- /**
241
- * A normalized provider 402 is fatal only when the integration boundary has
242
- * declared it account-level capacity. A raw 402 never reaches this function as
243
- * proof by itself: providers use that status inconsistently.
244
- */
245
- function isProviderAccountCapacityFailurePayload(
246
- payload: Record<string, unknown> | null,
247
- ): payload is Record<string, unknown> {
248
- if (!payload) return false;
249
- const code = String(payload.code ?? payload.error_code ?? '').toUpperCase();
250
- const category = String(
251
- payload.error_category ?? payload.errorCategory ?? '',
252
- ).toLowerCase();
253
- const origin = String(
254
- payload.failure_origin ?? payload.failureOrigin ?? '',
255
- ).toLowerCase();
256
- return (
257
- code === 'PROVIDER_ACCOUNT_CAPACITY' &&
258
- category === 'provider_account' &&
259
- (origin === 'provider' || origin === 'provider_account')
260
- );
261
- }
262
-
263
240
  function normalizeHardBillingPayload(
264
241
  payload: Record<string, unknown>,
265
242
  ): Record<string, unknown> {
@@ -299,18 +276,11 @@ function formatHardBillingFailureMessage(input: {
299
276
  maxAttempts: number;
300
277
  }): string {
301
278
  const code = getStringField(input.billing, 'code');
302
- const providerCapacity = isProviderAccountCapacityFailurePayload(
303
- input.billing,
304
- );
305
279
  const message =
306
280
  getStringField(input.billing, 'message') ??
307
281
  getStringField(input.billing, 'error') ??
308
- (providerCapacity
309
- ? 'Provider account capacity blocked execution.'
310
- : 'Deepline billing cap exceeded.');
311
- const headline = providerCapacity
312
- ? 'Provider account capacity blocked execution.'
313
- : 'Deepline billing cap exceeded.';
282
+ 'Deepline billing cap exceeded.';
283
+ const headline = 'Deepline billing cap exceeded.';
314
284
  return `tool ${input.toolId} ${input.status} attempt ${input.attempt}/${input.maxAttempts}: ${headline} Run halted before marking remaining rows processed. ${code ? `code=${code}. ` : ''}${message}`;
315
285
  }
316
286
 
@@ -478,12 +448,8 @@ export function normalizeToolHttpErrorMessage(input: {
478
448
  ? normalizeHardBillingPayload(billing)
479
449
  : isHardBillingFailurePayload(parsed)
480
450
  ? normalizeHardBillingPayload(parsed)
481
- : isProviderAccountCapacityFailurePayload(parsed)
482
- ? parsed
483
- : null;
451
+ : null;
484
452
  if (hardBillingPayload) {
485
- const providerCapacity =
486
- isProviderAccountCapacityFailurePayload(hardBillingPayload);
487
453
  return createToolHttpError(
488
454
  schemaVersion,
489
455
  formatHardBillingFailureMessage({
@@ -498,15 +464,9 @@ export function normalizeToolHttpErrorMessage(input: {
498
464
  'terminal',
499
465
  {
500
466
  ...publicOptions,
501
- origin: providerCapacity ? 'provider' : 'deepline',
502
- // `provider_account` is the integration-boundary category. The
503
- // portable ToolExecutionError taxonomy canonically represents it as
504
- // provider-owned authentication; the code retains the precise
505
- // account-capacity reason.
506
- category: providerCapacity ? 'authentication' : 'billing',
507
- code: providerCapacity
508
- ? 'PROVIDER_ACCOUNT_CAPACITY'
509
- : publicOptions.code,
467
+ origin: 'deepline',
468
+ category: 'billing',
469
+ code: publicOptions.code,
510
470
  retryable: false,
511
471
  },
512
472
  );
@@ -536,19 +496,15 @@ export function isHardBillingToolHttpError(error: unknown): boolean {
536
496
  if (
537
497
  error instanceof ToolHttpError &&
538
498
  (isInsufficientCreditsBilling(error.billing) ||
539
- isHardBillingFailurePayload(error.billing) ||
540
- isProviderAccountCapacityFailurePayload(error.billing))
499
+ isHardBillingFailurePayload(error.billing))
541
500
  ) {
542
501
  return true;
543
502
  }
544
503
  return (
545
504
  error instanceof ToolExecutionError &&
546
- ((error.origin === 'deepline' &&
547
- error.category === 'billing' &&
548
- error.code !== 'BILLING_UNAVAILABLE') ||
549
- (error.origin === 'provider' &&
550
- error.category === 'authentication' &&
551
- error.code === 'PROVIDER_ACCOUNT_CAPACITY'))
505
+ error.origin === 'deepline' &&
506
+ error.category === 'billing' &&
507
+ error.code !== 'BILLING_UNAVAILABLE'
552
508
  );
553
509
  }
554
510