deepline 0.1.274 → 0.1.276

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.
@@ -71,6 +71,7 @@ import type {
71
71
  PublishSharePageRequest,
72
72
  UpdateSharePageRequest,
73
73
  ToolDefinition,
74
+ ProviderDefinition,
74
75
  ToolSearchOptions,
75
76
  ToolSearchResult,
76
77
  ToolMetadata,
@@ -1640,6 +1641,17 @@ export class DeeplineClient {
1640
1641
  return res.tools;
1641
1642
  }
1642
1643
 
1644
+ /** List discoverable providers without requiring a local plugin catalog. */
1645
+ async listProviders(options?: {
1646
+ changed?: boolean;
1647
+ }): Promise<ProviderDefinition[]> {
1648
+ const suffix = options?.changed ? '?changed=true' : '';
1649
+ const res = await this.http.get<{ providers: ProviderDefinition[] }>(
1650
+ `/api/v2/tools/providers${suffix}`,
1651
+ );
1652
+ return res.providers;
1653
+ }
1654
+
1643
1655
  /**
1644
1656
  * Search available tools using Deepline's ranked backend search.
1645
1657
  *
@@ -168,6 +168,10 @@ export type {
168
168
  PlayRunActionPackage,
169
169
  PlayRunDatasetActions,
170
170
  PlayRunPackage,
171
+ PlayActivityObservation,
172
+ PlayActivityState,
173
+ PlayActivityTarget,
174
+ PlayRunActivityProjection,
171
175
  PlayRunStart,
172
176
  ClearPlayHistoryRequest,
173
177
  ClearPlayHistoryResult,
@@ -155,7 +155,7 @@ export const SDK_RELEASE = {
155
155
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
156
156
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
157
157
  // Operators use the checkout-local deepline-admin binary instead.
158
- version: '0.1.274',
158
+ version: '0.1.276',
159
159
  contracts: {
160
160
  api: {
161
161
  name: 'sdk-http-api',
@@ -1,5 +1,17 @@
1
1
  import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest';
2
2
  import type { PlayRuntimeSelection } from '../../shared_libs/play-runtime/runtime-environment';
3
+ import type {
4
+ PlayActivityObservation,
5
+ PlayActivityState,
6
+ PlayActivityTarget,
7
+ PlayRunActivityProjection,
8
+ } from '../../shared_libs/play-runtime/activity-observation';
9
+ export type {
10
+ PlayActivityObservation,
11
+ PlayActivityState,
12
+ PlayActivityTarget,
13
+ PlayRunActivityProjection,
14
+ };
3
15
  import type {
4
16
  DeeplineToolCategory,
5
17
  PlayBootstrapFinderKind,
@@ -112,6 +124,18 @@ export interface ToolPricingSummary {
112
124
  details: string[];
113
125
  }
114
126
 
127
+ /** A discoverable provider, returned by {@link DeeplineClient.listProviders}. */
128
+ export interface ProviderDefinition {
129
+ provider: string;
130
+ displayName: string;
131
+ toolCount: number;
132
+ categories: string[];
133
+ /** Present for providers in the recent-additions ledger. */
134
+ version?: string;
135
+ /** ISO-8601 date on which this provider was added. */
136
+ releasedAt?: string;
137
+ }
138
+
115
139
  /**
116
140
  * Summary definition of a tool, returned by {@link DeeplineClient.listTools}.
117
141
  *
@@ -568,6 +592,8 @@ export interface PlayRunPackage {
568
592
  finishedAt?: number | null;
569
593
  durationMs?: number | null;
570
594
  error?: string;
595
+ /** Canonical explanation of what this run is doing or waiting on. */
596
+ activity?: PlayRunActivityProjection | null;
571
597
  };
572
598
  /** Bounded customer-safe warnings about output projection or availability. */
573
599
  warnings?: string[];
@@ -0,0 +1,588 @@
1
+ /**
2
+ * Canonical, producer-facing description of what a play is doing.
3
+ *
4
+ * Producers report facts. They do not choose log copy, UI placement, polling
5
+ * intervals, or whether a run is "healthy". The activity router and projector
6
+ * own those decisions centrally.
7
+ */
8
+ export type PlayActivityTarget =
9
+ | {
10
+ kind: 'provider';
11
+ provider: string;
12
+ operation: string;
13
+ label?: string;
14
+ }
15
+ | {
16
+ kind: 'dataset';
17
+ tableNamespace: string;
18
+ operation: 'materializing' | 'persisting' | 'indexing' | 'reading';
19
+ label?: string;
20
+ }
21
+ | {
22
+ kind: 'lease';
23
+ resourceKind: 'run' | 'dataset' | 'row' | 'runner';
24
+ resourceLabel: string;
25
+ }
26
+ | {
27
+ kind: 'runner';
28
+ backend: string;
29
+ label?: string;
30
+ }
31
+ | {
32
+ kind: 'step';
33
+ stepId: string;
34
+ label?: string;
35
+ }
36
+ | {
37
+ kind: 'capacity';
38
+ pool: 'worker' | 'provider' | 'dataset' | 'runner';
39
+ label?: string;
40
+ };
41
+
42
+ export type PlayActivityProgress = {
43
+ completed?: number;
44
+ total?: number;
45
+ failed?: number;
46
+ message?: string;
47
+ };
48
+
49
+ export type PlayActivityState =
50
+ | {
51
+ kind: 'active';
52
+ progress?: PlayActivityProgress;
53
+ }
54
+ | {
55
+ kind: 'queued';
56
+ reason: 'capacity';
57
+ }
58
+ | {
59
+ kind: 'waiting';
60
+ reason:
61
+ | {
62
+ kind: 'external_event';
63
+ provider?: string;
64
+ eventKey?: string;
65
+ remaining?: number;
66
+ deadlineAt?: number;
67
+ }
68
+ | {
69
+ kind: 'dataset';
70
+ tableNamespace: string;
71
+ requiredPhase: 'available' | 'persisted' | 'indexed';
72
+ }
73
+ | {
74
+ kind: 'lease';
75
+ resourceKind: 'run' | 'dataset' | 'row' | 'runner';
76
+ resourceLabel: string;
77
+ expiresAt?: number;
78
+ }
79
+ | {
80
+ /** Mixed-version fallback only; new producers must choose a typed reason. */
81
+ kind: 'unknown';
82
+ };
83
+ }
84
+ | {
85
+ kind: 'retrying';
86
+ reason: 'rate_limit' | 'provider_error' | 'transport_error';
87
+ retryAt: number;
88
+ attempt?: number;
89
+ }
90
+ | {
91
+ kind: 'scheduled';
92
+ reason: 'sleep';
93
+ resumeAt: number;
94
+ }
95
+ | {
96
+ kind: 'completed';
97
+ }
98
+ | {
99
+ kind: 'failed';
100
+ code?: string;
101
+ };
102
+
103
+ export type PlayActivityObservation = {
104
+ schemaVersion: 1;
105
+ /** Stable within one run, for example `tool:discover_ctos` or `dataset:leads`. */
106
+ activityId: string;
107
+ /** Optional owning play step. */
108
+ stepId?: string;
109
+ target: PlayActivityTarget;
110
+ state: PlayActivityState;
111
+ /** Producer clock. The runtime envelope supplies run identity and ordering. */
112
+ observedAt: number;
113
+ };
114
+
115
+ export type PlayActivityEvent = {
116
+ type: 'activity.observed';
117
+ observation: PlayActivityObservation;
118
+ };
119
+
120
+ function isRecord(value: unknown): value is Record<string, unknown> {
121
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
122
+ }
123
+
124
+ export function isPlayActivityObservation(
125
+ value: unknown,
126
+ ): value is PlayActivityObservation {
127
+ if (
128
+ !isRecord(value) ||
129
+ value.schemaVersion !== 1 ||
130
+ typeof value.activityId !== 'string' ||
131
+ !value.activityId.trim() ||
132
+ typeof value.observedAt !== 'number' ||
133
+ !Number.isFinite(value.observedAt) ||
134
+ !isRecord(value.target) ||
135
+ !isRecord(value.state)
136
+ ) {
137
+ return false;
138
+ }
139
+ const targetKinds = new Set([
140
+ 'provider',
141
+ 'dataset',
142
+ 'lease',
143
+ 'runner',
144
+ 'step',
145
+ 'capacity',
146
+ ]);
147
+ const stateKinds = new Set([
148
+ 'active',
149
+ 'queued',
150
+ 'waiting',
151
+ 'retrying',
152
+ 'scheduled',
153
+ 'completed',
154
+ 'failed',
155
+ ]);
156
+ return (
157
+ typeof value.target.kind === 'string' &&
158
+ targetKinds.has(value.target.kind) &&
159
+ typeof value.state.kind === 'string' &&
160
+ stateKinds.has(value.state.kind)
161
+ );
162
+ }
163
+
164
+ export type PlayRunActivityProjection = {
165
+ observation: PlayActivityObservation;
166
+ summary: string;
167
+ visibility: PlayActivityVisibility;
168
+ /** False only when old payloads lacked enough typed facts to classify. */
169
+ classified: boolean;
170
+ };
171
+
172
+ /**
173
+ * Persistence lanes are a routing concern, not a producer concern:
174
+ *
175
+ * - durable: append a meaningful transition to the run ledger;
176
+ * - snapshot: replace current progress for this activity;
177
+ * - liveness: update last-seen evidence without growing the event ledger;
178
+ * - drop: stale or duplicate observation.
179
+ */
180
+ export type PlayActivityRoutingLane =
181
+ | 'durable'
182
+ | 'snapshot'
183
+ | 'liveness'
184
+ | 'drop';
185
+
186
+ function stableJson(value: unknown): string {
187
+ return JSON.stringify(value);
188
+ }
189
+
190
+ function targetIdentity(target: PlayActivityTarget): string {
191
+ return stableJson(target);
192
+ }
193
+
194
+ export function routePlayActivityObservation(input: {
195
+ previous?: PlayActivityObservation | null;
196
+ next: PlayActivityObservation;
197
+ }): PlayActivityRoutingLane {
198
+ const { previous, next } = input;
199
+ if (!previous) return 'durable';
200
+ if (next.observedAt <= previous.observedAt) return 'drop';
201
+ if (targetIdentity(previous.target) !== targetIdentity(next.target)) {
202
+ return 'durable';
203
+ }
204
+ if (previous.state.kind !== next.state.kind) return 'durable';
205
+ if (next.state.kind !== 'active') {
206
+ return stableJson(previous.state) === stableJson(next.state)
207
+ ? 'liveness'
208
+ : 'durable';
209
+ }
210
+ return stableJson(previous.state) === stableJson(next.state)
211
+ ? 'liveness'
212
+ : 'snapshot';
213
+ }
214
+
215
+ export type PlayActivityVisibility =
216
+ | 'hidden'
217
+ | 'timeline'
218
+ | 'headline'
219
+ | 'action';
220
+
221
+ /**
222
+ * Central presentation policy. Short, self-healing retries do not become
223
+ * alarming UI/log messages; blockers and failures do.
224
+ */
225
+ export function derivePlayActivityVisibility(input: {
226
+ observation: PlayActivityObservation;
227
+ now: number;
228
+ }): PlayActivityVisibility {
229
+ const { state } = input.observation;
230
+ if (state.kind === 'failed') return 'action';
231
+ if (state.kind === 'waiting') {
232
+ return state.reason.kind === 'external_event' ? 'action' : 'headline';
233
+ }
234
+ if (state.kind === 'queued' || state.kind === 'scheduled') return 'headline';
235
+ if (state.kind === 'retrying') {
236
+ return state.retryAt - input.now <= 30_000 ? 'hidden' : 'timeline';
237
+ }
238
+ if (state.kind === 'active') return 'headline';
239
+ return 'timeline';
240
+ }
241
+
242
+ function targetLabel(target: PlayActivityTarget): string {
243
+ switch (target.kind) {
244
+ case 'provider':
245
+ return target.label ?? `${target.provider} ${target.operation}`;
246
+ case 'dataset':
247
+ return target.label ?? `dataset ${target.tableNamespace}`;
248
+ case 'lease':
249
+ return `${target.resourceKind} ${target.resourceLabel}`;
250
+ case 'runner':
251
+ return target.label ?? `${target.backend} runner`;
252
+ case 'step':
253
+ return target.label ?? target.stepId;
254
+ case 'capacity':
255
+ return target.label ?? `${target.pool} capacity`;
256
+ }
257
+ }
258
+
259
+ export function summarizePlayActivity(
260
+ observation: PlayActivityObservation,
261
+ ): string {
262
+ const label = targetLabel(observation.target);
263
+ const { state } = observation;
264
+ switch (state.kind) {
265
+ case 'active': {
266
+ const progress = state.progress;
267
+ const counts =
268
+ typeof progress?.completed === 'number' &&
269
+ typeof progress.total === 'number'
270
+ ? ` (${progress.completed}/${progress.total})`
271
+ : '';
272
+ return `Active: ${label}${counts}.`;
273
+ }
274
+ case 'queued':
275
+ return `Queued: waiting for ${label}.`;
276
+ case 'waiting':
277
+ if (state.reason.kind === 'external_event') {
278
+ const provider = state.reason.provider
279
+ ? ` from ${state.reason.provider}`
280
+ : '';
281
+ return `Blocked: waiting for an external event${provider}.`;
282
+ }
283
+ if (state.reason.kind === 'dataset') {
284
+ return `Blocked: waiting for dataset ${state.reason.tableNamespace} to become ${state.reason.requiredPhase}.`;
285
+ }
286
+ if (state.reason.kind === 'lease') {
287
+ return `Contended: waiting for ${state.reason.resourceKind} lease ${state.reason.resourceLabel}.`;
288
+ }
289
+ return `Paused: ${label}; the legacy producer did not report a typed reason.`;
290
+ case 'retrying':
291
+ return `Delayed: ${label} will retry automatically.`;
292
+ case 'scheduled':
293
+ return `Scheduled: ${label} will resume automatically.`;
294
+ case 'completed':
295
+ return `Completed: ${label}.`;
296
+ case 'failed':
297
+ return `Failed: ${label}.`;
298
+ }
299
+ }
300
+
301
+ export type PlayActivityReporter = {
302
+ observe(
303
+ input: Omit<PlayActivityObservation, 'schemaVersion' | 'observedAt'> & {
304
+ observedAt?: number;
305
+ },
306
+ ): void;
307
+ };
308
+
309
+ export function createPlayActivityReporter(input: {
310
+ emit: (event: PlayActivityEvent) => void;
311
+ now?: () => number;
312
+ }): PlayActivityReporter {
313
+ return {
314
+ observe(observation) {
315
+ input.emit({
316
+ type: 'activity.observed',
317
+ observation: {
318
+ ...observation,
319
+ schemaVersion: 1,
320
+ observedAt: observation.observedAt ?? input.now?.() ?? Date.now(),
321
+ },
322
+ });
323
+ },
324
+ };
325
+ }
326
+
327
+ type ProjectionStep = {
328
+ nodeId: string;
329
+ status?: string;
330
+ label?: string;
331
+ updatedAt?: number | null;
332
+ progress?: PlayActivityProgress | null;
333
+ };
334
+
335
+ type ProjectionDataset = {
336
+ datasetId?: string;
337
+ tableNamespace: string;
338
+ phase?: string;
339
+ persistedRows?: number;
340
+ succeededRows?: number;
341
+ failedRows?: number;
342
+ complete?: boolean;
343
+ updatedAt?: number;
344
+ };
345
+
346
+ function isTerminalActivityState(state: PlayActivityState): boolean {
347
+ return state.kind === 'completed' || state.kind === 'failed';
348
+ }
349
+
350
+ function projection(
351
+ observation: PlayActivityObservation,
352
+ now: number,
353
+ classified = true,
354
+ ): PlayRunActivityProjection {
355
+ return {
356
+ observation,
357
+ summary: summarizePlayActivity(observation),
358
+ visibility: derivePlayActivityVisibility({ observation, now }),
359
+ classified,
360
+ };
361
+ }
362
+
363
+ /**
364
+ * Read-side compatibility projector. Explicit typed observations win; existing
365
+ * suspension, dataset, and step facts are adapted without requiring every
366
+ * legacy producer to migrate in one deploy.
367
+ */
368
+ export function projectPlayRunActivity(input: {
369
+ runId: string;
370
+ playName?: string | null;
371
+ status: string;
372
+ updatedAt?: number | null;
373
+ waitKind?: string | null;
374
+ waitUntil?: number | null;
375
+ eventKey?: string | null;
376
+ runtimeBackend?: string | null;
377
+ activeNodeId?: string | null;
378
+ nodeStates?: ProjectionStep[] | null;
379
+ datasets?: ProjectionDataset[] | null;
380
+ explicit?: PlayActivityObservation[] | null;
381
+ now?: number;
382
+ }): PlayRunActivityProjection | null {
383
+ const now = input.now ?? Date.now();
384
+ const observedAt = input.updatedAt ?? now;
385
+ const normalizedStatus = input.status.trim().toLowerCase();
386
+ if (
387
+ normalizedStatus === 'completed' ||
388
+ normalizedStatus === 'failed' ||
389
+ normalizedStatus === 'cancelled' ||
390
+ normalizedStatus === 'terminated' ||
391
+ normalizedStatus === 'timed_out'
392
+ ) {
393
+ return null;
394
+ }
395
+
396
+ const explicit = [...(input.explicit ?? [])]
397
+ .filter((candidate) => !isTerminalActivityState(candidate.state))
398
+ .sort((left, right) => right.observedAt - left.observedAt)[0];
399
+ if (explicit) return projection(explicit, now);
400
+
401
+ if (input.waitKind === 'detached_runner') {
402
+ return projection(
403
+ {
404
+ schemaVersion: 1,
405
+ activityId: 'runner:managed',
406
+ target: {
407
+ kind: 'runner',
408
+ backend: input.runtimeBackend ?? 'managed',
409
+ label: 'managed play runner',
410
+ },
411
+ state: { kind: 'active' },
412
+ observedAt,
413
+ },
414
+ now,
415
+ );
416
+ }
417
+ if (input.waitKind === 'sleep') {
418
+ return projection(
419
+ {
420
+ schemaVersion: 1,
421
+ activityId: 'run:sleep',
422
+ target: {
423
+ kind: 'step',
424
+ stepId: input.activeNodeId ?? 'run',
425
+ label: input.playName ?? 'play',
426
+ },
427
+ state: {
428
+ kind: 'scheduled',
429
+ reason: 'sleep',
430
+ resumeAt: input.waitUntil ?? observedAt,
431
+ },
432
+ observedAt,
433
+ },
434
+ now,
435
+ );
436
+ }
437
+ if (
438
+ input.waitKind === 'integration_event' ||
439
+ input.waitKind === 'integration_event_batch'
440
+ ) {
441
+ return projection(
442
+ {
443
+ schemaVersion: 1,
444
+ activityId: `event:${input.activeNodeId ?? 'run'}`,
445
+ stepId: input.activeNodeId ?? undefined,
446
+ target: {
447
+ kind: 'step',
448
+ stepId: input.activeNodeId ?? 'run',
449
+ label: input.playName ?? 'play',
450
+ },
451
+ state: {
452
+ kind: 'waiting',
453
+ reason: {
454
+ kind: 'external_event',
455
+ eventKey: input.eventKey ?? undefined,
456
+ deadlineAt: input.waitUntil ?? undefined,
457
+ },
458
+ },
459
+ observedAt,
460
+ },
461
+ now,
462
+ );
463
+ }
464
+ // Mixed-version readers may see the durable WAITING status before the
465
+ // scheduler's typed wait projection is available. Do not let a stale
466
+ // running step overwrite that stronger lifecycle fact.
467
+ if (normalizedStatus === 'waiting') {
468
+ return projection(
469
+ {
470
+ schemaVersion: 1,
471
+ activityId: 'run:legacy-wait',
472
+ target: {
473
+ kind: 'step',
474
+ stepId: input.activeNodeId ?? 'run',
475
+ label: input.playName ?? 'play',
476
+ },
477
+ state: { kind: 'waiting', reason: { kind: 'unknown' } },
478
+ observedAt,
479
+ },
480
+ now,
481
+ false,
482
+ );
483
+ }
484
+
485
+ const pendingDataset = [...(input.datasets ?? [])]
486
+ .filter(
487
+ (dataset) =>
488
+ dataset.phase === 'registered' ||
489
+ (dataset.complete !== true && dataset.phase !== 'failed'),
490
+ )
491
+ .sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
492
+ if (pendingDataset) {
493
+ return projection(
494
+ {
495
+ schemaVersion: 1,
496
+ activityId: `dataset:${pendingDataset.datasetId ?? pendingDataset.tableNamespace}`,
497
+ target: {
498
+ kind: 'dataset',
499
+ tableNamespace: pendingDataset.tableNamespace,
500
+ operation: 'materializing',
501
+ },
502
+ state: {
503
+ kind: 'active',
504
+ progress: {
505
+ completed: pendingDataset.persistedRows,
506
+ total:
507
+ typeof pendingDataset.succeededRows === 'number' ||
508
+ typeof pendingDataset.failedRows === 'number'
509
+ ? (pendingDataset.succeededRows ?? 0) +
510
+ (pendingDataset.failedRows ?? 0)
511
+ : undefined,
512
+ failed: pendingDataset.failedRows,
513
+ },
514
+ },
515
+ observedAt: pendingDataset.updatedAt ?? observedAt,
516
+ },
517
+ now,
518
+ );
519
+ }
520
+
521
+ const activeStep =
522
+ input.nodeStates?.find(
523
+ (candidate) => candidate.nodeId === input.activeNodeId,
524
+ ) ??
525
+ input.nodeStates?.find((candidate) => candidate.status === 'running') ??
526
+ null;
527
+ if (activeStep) {
528
+ const toolId = activeStep.nodeId.startsWith('tool:')
529
+ ? activeStep.nodeId.split(':').at(-1)?.trim() || null
530
+ : null;
531
+ const provider = toolId?.split(/[._]/)[0]?.trim() || null;
532
+ return projection(
533
+ {
534
+ schemaVersion: 1,
535
+ activityId: `step:${activeStep.nodeId}`,
536
+ stepId: activeStep.nodeId,
537
+ target:
538
+ toolId && provider
539
+ ? {
540
+ kind: 'provider',
541
+ provider,
542
+ operation: toolId,
543
+ label: activeStep.label ?? toolId,
544
+ }
545
+ : {
546
+ kind: 'step',
547
+ stepId: activeStep.nodeId,
548
+ label: activeStep.label,
549
+ },
550
+ state: {
551
+ kind: 'active',
552
+ progress: activeStep.progress ?? undefined,
553
+ },
554
+ observedAt: activeStep.updatedAt ?? observedAt,
555
+ },
556
+ now,
557
+ );
558
+ }
559
+
560
+ if (normalizedStatus === 'queued') {
561
+ return projection(
562
+ {
563
+ schemaVersion: 1,
564
+ activityId: 'capacity:worker',
565
+ target: { kind: 'capacity', pool: 'worker' },
566
+ state: { kind: 'queued', reason: 'capacity' },
567
+ observedAt,
568
+ },
569
+ now,
570
+ );
571
+ }
572
+
573
+ const fallback: PlayActivityObservation = {
574
+ schemaVersion: 1,
575
+ activityId: 'run:legacy',
576
+ target: {
577
+ kind: 'step',
578
+ stepId: input.activeNodeId ?? 'run',
579
+ label: input.playName ?? 'play',
580
+ },
581
+ state:
582
+ normalizedStatus === 'waiting'
583
+ ? { kind: 'waiting', reason: { kind: 'unknown' } }
584
+ : { kind: 'active' },
585
+ observedAt,
586
+ };
587
+ return projection(fallback, now, normalizedStatus !== 'waiting');
588
+ }