deepline 0.1.32 → 0.1.35

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.
@@ -28,7 +28,10 @@
28
28
  *
29
29
  * export default definePlay('my-play', async (ctx, input: { domain: string }) => {
30
30
  * ctx.log(`Looking up ${input.domain}`);
31
- * const company = await ctx.tools.execute('company_search', 'test_company_search', { domain: input.domain }, {
31
+ * const company = await ctx.tools.execute({
32
+ * id: 'company_search',
33
+ * tool: 'test_company_search',
34
+ * input: { domain: input.domain },
32
35
  * description: 'Look up company details by domain.',
33
36
  * });
34
37
  * return company;
@@ -42,8 +45,8 @@
42
45
  * ```typescript
43
46
  * import { Deepline } from 'deepline';
44
47
  *
45
- * const ctx = await Deepline.connect();
46
- * const job = await ctx.play('my-play').run({ domain: 'stripe.com' });
48
+ * const deepline = await Deepline.connect();
49
+ * const job = await deepline.play('my-play').run({ domain: 'stripe.com' });
47
50
  * const result = await job.get(); // Polls until complete
48
51
  * ```
49
52
  *
@@ -53,7 +56,10 @@
53
56
  * import { definePlay } from 'deepline';
54
57
  *
55
58
  * export default definePlay('daily-sync', async (ctx) => {
56
- * const data = await ctx.tools.execute('crm_export', 'crm_export', {}, {
59
+ * const data = await ctx.tools.execute({
60
+ * id: 'crm_export',
61
+ * tool: 'crm_export',
62
+ * input: {},
57
63
  * description: 'Export CRM records for the daily sync.',
58
64
  * });
59
65
  * return data;
@@ -66,6 +72,7 @@
66
72
  */
67
73
  import { DeeplineClient } from './client.js';
68
74
  import { DeeplineError } from './errors.js';
75
+ import { createToolExecuteResult } from '../../shared_libs/play-runtime/tool-result.js';
69
76
  import type {
70
77
  PlayDataset,
71
78
  PlayDatasetInput,
@@ -83,6 +90,7 @@ import type {
83
90
  ToolDefinition,
84
91
  ToolMetadata,
85
92
  } from './types.js';
93
+ import type { ToolExecution } from './client.js';
86
94
 
87
95
  /**
88
96
  * Optional trigger bindings for a play.
@@ -275,7 +283,10 @@ export type CsvOptions = {
275
283
  * ```typescript
276
284
  * definePlay('example', async (ctx, input: { domain: string }) => {
277
285
  * // Call a tool
278
- * const company = await ctx.tools.execute('company_search', 'test_company_search', { domain: input.domain }, {
286
+ * const company = await ctx.tools.execute({
287
+ * id: 'company_search',
288
+ * tool: 'test_company_search',
289
+ * input: { domain: input.domain },
279
290
  * description: 'Look up company details by domain.',
280
291
  * });
281
292
  *
@@ -348,24 +359,30 @@ export interface DeeplinePlayRuntimeContext {
348
359
  *
349
360
  * @example Single tool per row
350
361
  * ```typescript
351
- * const results = await ctx
352
- * .map('companies', leads)
353
- * .step('company', (row, ctx) =>
354
- * ctx.tools.execute('company_search', 'test_company_search', { domain: row.domain }, {
355
- * description: 'Look up company details by domain.',
356
- * }))
362
+ * const results = await ctx
363
+ * .map('companies', leads)
364
+ * .step('company', (row, ctx) =>
365
+ * ctx.tools.execute({
366
+ * id: 'company_search',
367
+ * tool: 'test_company_search',
368
+ * input: { domain: row.domain },
369
+ * description: 'Look up company details by domain.',
370
+ * }))
357
371
  * .run({ description: 'Look up companies.' });
358
372
  * // [{ domain: 'stripe.com', company: { name: 'Stripe', ... } }, ...]
359
373
  * ```
360
374
  *
361
375
  * @example Multiple columns with pre/post logic
362
376
  * ```typescript
363
- * const results = await ctx
364
- * .map('leads', leads)
365
- * .step('company', (row, ctx) =>
366
- * ctx.tools.execute('company_search', 'test_company_search', { domain: row.domain }, {
367
- * description: 'Look up company details by domain.',
368
- * }))
377
+ * const results = await ctx
378
+ * .map('leads', leads)
379
+ * .step('company', (row, ctx) =>
380
+ * ctx.tools.execute({
381
+ * id: 'company_search',
382
+ * tool: 'test_company_search',
383
+ * input: { domain: row.domain },
384
+ * description: 'Look up company details by domain.',
385
+ * }))
369
386
  * .step('score', (row) =>
370
387
  * row.company?.employeeCount > 100 ? 'enterprise' : 'smb')
371
388
  * .run({ description: 'Enrich leads.' });
@@ -379,22 +396,16 @@ export interface DeeplinePlayRuntimeContext {
379
396
  /** Tool execution namespace. */
380
397
  tools: {
381
398
  /**
382
- * Execute a single tool by stable step key and tool ID.
399
+ * Execute a single tool with a keyword-style request object.
383
400
  *
384
- * @param key - Stable step key for idempotent execution
385
- * @param toolId - Tool identifier (e.g. `'test_company_search'`)
386
- * @param input - Tool-specific input parameters
401
+ * @param request.id - Stable step key for idempotent execution
402
+ * @param request.tool - Tool identifier (e.g. `'test_company_search'`)
403
+ * @param request.input - Tool-specific input parameters
387
404
  * @returns The tool's output
388
405
  */
389
406
  execute<TOutput = LoosePlayObject>(
390
407
  request: ToolExecutionRequest,
391
408
  ): Promise<ToolExecuteResult<TOutput>>;
392
- execute<TOutput = LoosePlayObject>(
393
- key: string,
394
- toolId: string,
395
- input: Record<string, unknown>,
396
- options?: { description?: string },
397
- ): Promise<ToolExecuteResult<TOutput>>;
398
409
  };
399
410
  /**
400
411
  * Execute a single tool by stable step key and tool ID.
@@ -736,7 +747,10 @@ export function when<Row, Value>(
736
747
  * import { definePlay } from 'deepline';
737
748
  *
738
749
  * const myPlay = definePlay('my-play', async (ctx, input: { domain: string }) => {
739
- * return await ctx.tools.execute('company_search', 'test_company_search', { domain: input.domain }, {
750
+ * return await ctx.tools.execute({
751
+ * id: 'company_search',
752
+ * tool: 'test_company_search',
753
+ * input: { domain: input.domain },
740
754
  * description: 'Look up company details by domain.',
741
755
  * });
742
756
  * });
@@ -886,14 +900,14 @@ function createNamedPlayHandle<
886
900
  *
887
901
  * @example
888
902
  * ```typescript
889
- * const ctx = await Deepline.connect();
903
+ * const deepline = await Deepline.connect();
890
904
  *
891
905
  * // Tools
892
- * const tools = await ctx.tools.list();
893
- * const result = await ctx.tools.execute('test_company_search', { domain: 'stripe.com' });
906
+ * const tools = await deepline.tools.list();
907
+ * const result = await deepline.tools.execute('test_company_search', { domain: 'stripe.com' });
894
908
  *
895
909
  * // Plays
896
- * const job = await ctx.play('email-waterfall').run({ domain: 'stripe.com' });
910
+ * const job = await deepline.play('email-waterfall').run({ domain: 'stripe.com' });
897
911
  * const output = await job.get();
898
912
  * ```
899
913
  */
@@ -909,10 +923,10 @@ export class DeeplineContext {
909
923
  *
910
924
  * @example
911
925
  * ```typescript
912
- * const tools = await ctx.tools.list();
913
- * const meta = await ctx.tools.get('apollo_people_search');
914
- * const companyLookup = await ctx.tools.execute('test_company_search', { domain: 'stripe.com' });
915
- * const company = companyLookup.result.data;
926
+ * const tools = await deepline.tools.list();
927
+ * const meta = await deepline.tools.get('apollo_people_search');
928
+ * const companyLookup = await deepline.tools.execute('test_company_search', { domain: 'stripe.com' });
929
+ * const company = companyLookup.toolOutput.raw;
916
930
  * ```
917
931
  */
918
932
  get tools() {
@@ -926,12 +940,14 @@ export class DeeplineContext {
926
940
  execute: async (
927
941
  toolId: string,
928
942
  input: Record<string, unknown>,
929
- ): Promise<ToolExecuteResult> =>
930
- this.client.executeTool(
943
+ ): Promise<ToolExecuteResult> => {
944
+ const response = await this.client.executeTool(
931
945
  toolId,
932
946
  input,
933
947
  { includeToolMetadata: true },
934
- ) as unknown as Promise<ToolExecuteResult>,
948
+ );
949
+ return toolExecutionEnvelopeToResult(toolId, response);
950
+ },
935
951
  };
936
952
  }
937
953
 
@@ -1023,9 +1039,9 @@ export class DeeplineContext {
1023
1039
  * ```typescript
1024
1040
  * import { Deepline } from 'deepline';
1025
1041
  *
1026
- * const ctx = await Deepline.connect();
1027
- * const tools = await ctx.tools.list();
1028
- * const result = await ctx.tools.execute('test_company_search', { domain: 'stripe.com' });
1042
+ * const deepline = await Deepline.connect();
1043
+ * const tools = await deepline.tools.list();
1044
+ * const result = await deepline.tools.execute('test_company_search', { domain: 'stripe.com' });
1029
1045
  * ```
1030
1046
  */
1031
1047
  export class Deepline {
@@ -1058,6 +1074,60 @@ export class Deepline {
1058
1074
  }
1059
1075
  }
1060
1076
 
1077
+ function isRecord(value: unknown): value is Record<string, unknown> {
1078
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
1079
+ }
1080
+
1081
+ function stringArrayRecord(value: unknown): Record<string, string[]> {
1082
+ if (!isRecord(value)) return {};
1083
+ return Object.fromEntries(
1084
+ Object.entries(value).map(([key, paths]) => [
1085
+ key,
1086
+ Array.isArray(paths) ? paths.map(String) : [],
1087
+ ]),
1088
+ );
1089
+ }
1090
+
1091
+ function stringArray(value: unknown): string[] {
1092
+ return Array.isArray(value) ? value.map(String) : [];
1093
+ }
1094
+
1095
+ function toolExecutionEnvelopeToResult(
1096
+ fallbackToolId: string,
1097
+ response: ToolExecution,
1098
+ ): ToolExecuteResult {
1099
+ const raw = response.toolExecutionResult?.toolOutput?.raw ?? null;
1100
+ const meta = response.toolExecutionResult?.toolOutput?.meta;
1101
+ const metadata = isRecord(response._metadata)
1102
+ ? response._metadata.tool
1103
+ : null;
1104
+ const toolMetadata = isRecord(metadata) ? metadata : {};
1105
+
1106
+ return createToolExecuteResult({
1107
+ status: typeof response.status === 'string' ? response.status : 'completed',
1108
+ result: {
1109
+ data: raw,
1110
+ ...(isRecord(meta) ? { meta } : {}),
1111
+ },
1112
+ metadata: {
1113
+ toolId:
1114
+ typeof toolMetadata.toolId === 'string'
1115
+ ? toolMetadata.toolId
1116
+ : fallbackToolId,
1117
+ resultIdentityGetters: stringArrayRecord(
1118
+ toolMetadata.resultIdentityGetters,
1119
+ ),
1120
+ listExtractorPaths: stringArray(toolMetadata.listExtractorPaths),
1121
+ listIdentityGetters: stringArrayRecord(toolMetadata.listIdentityGetters),
1122
+ },
1123
+ execution: {
1124
+ idempotent: true,
1125
+ cached: false,
1126
+ source: 'live',
1127
+ },
1128
+ });
1129
+ }
1130
+
1061
1131
  export function defineInput<TInput>(
1062
1132
  schema: Record<string, unknown>,
1063
1133
  ): PlayInputContract<TInput> {
@@ -1092,7 +1162,10 @@ export function defineInput<TInput>(
1092
1162
  *
1093
1163
  * export default definePlay('company-lookup', async (ctx, input: { domain: string }) => {
1094
1164
  * ctx.log(`Searching for ${input.domain}`);
1095
- * const company = await ctx.tools.execute('company_search', 'test_company_search', { domain: input.domain }, {
1165
+ * const company = await ctx.tools.execute({
1166
+ * id: 'company_search',
1167
+ * tool: 'test_company_search',
1168
+ * input: { domain: input.domain },
1096
1169
  * description: 'Look up company details by domain.',
1097
1170
  * });
1098
1171
  * return company;
@@ -1107,7 +1180,10 @@ export function defineInput<TInput>(
1107
1180
  * const results = await ctx
1108
1181
  * .map('companies', leads)
1109
1182
  * .step('company', (row, ctx) =>
1110
- * ctx.tools.execute('company_search', 'test_company_search', { domain: row.domain }, {
1183
+ * ctx.tools.execute({
1184
+ * id: 'company_search',
1185
+ * tool: 'test_company_search',
1186
+ * input: { domain: row.domain },
1111
1187
  * description: 'Look up company details by domain.',
1112
1188
  * }))
1113
1189
  * .run({ description: 'Enrich lead companies.' });
@@ -1118,7 +1194,10 @@ export function defineInput<TInput>(
1118
1194
  * @example With cron binding
1119
1195
  * ```typescript
1120
1196
  * export default definePlay('daily-report', async (ctx) => {
1121
- * const data = await ctx.tools.execute('crm_export', 'crm_export', { since: 'yesterday' }, {
1197
+ * const data = await ctx.tools.execute({
1198
+ * id: 'crm_export',
1199
+ * tool: 'crm_export',
1200
+ * input: { since: 'yesterday' },
1122
1201
  * description: 'Export yesterday CRM records.',
1123
1202
  * });
1124
1203
  * return data;
@@ -155,6 +155,7 @@ export async function harnessPrewarmPostgresSessions(input: {
155
155
  export async function harnessStartSheetDataset(input: {
156
156
  baseUrl: string;
157
157
  executorToken: string;
158
+ preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
158
159
  playName: string;
159
160
  tableNamespace: string;
160
161
  sheetContract: unknown;
@@ -162,7 +163,6 @@ export async function harnessStartSheetDataset(input: {
162
163
  runId: string;
163
164
  inputOffset?: number;
164
165
  userEmail?: string | null;
165
- preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
166
166
  }): Promise<{
167
167
  inserted: number;
168
168
  skipped: number;
@@ -181,6 +181,7 @@ export async function harnessStartSheetDataset(input: {
181
181
  export async function harnessPersistCompletedSheetRows(input: {
182
182
  baseUrl: string;
183
183
  executorToken: string;
184
+ preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
184
185
  playName: string;
185
186
  tableNamespace: string;
186
187
  sheetContract: unknown;
@@ -188,7 +189,6 @@ export async function harnessPersistCompletedSheetRows(input: {
188
189
  outputFields: string[];
189
190
  runId: string;
190
191
  userEmail?: string | null;
191
- preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
192
192
  }): Promise<{ ok: true; rowsWritten: number; tableNamespace: string }> {
193
193
  return requireBinding().persistCompletedMapRows(input);
194
194
  }
@@ -2,7 +2,7 @@
2
2
  * Tool output processing utilities.
3
3
  *
4
4
  * Tools return data in varied shapes — some return flat objects, others
5
- * wrap results in `{ result: { data: [...] } }` envelopes, and list
5
+ * wrap results in `{ output: { body: [...] } }` envelopes, and list
6
6
  * responses can be nested at different depths. This module provides
7
7
  * utilities to normalize, extract, and persist tool outputs.
8
8
  *
@@ -33,7 +33,7 @@ import { join } from 'node:path';
33
33
  * @example
34
34
  * ```typescript
35
35
  * const conversion = tryConvertToList(toolResponse, {
36
- * listExtractorPaths: ['people', 'result.data'],
36
+ * listExtractorPaths: ['people', 'output.body'],
37
37
  * });
38
38
  * if (conversion) {
39
39
  * console.log(`Found ${conversion.rows.length} rows via ${conversion.strategy}`);
@@ -50,7 +50,7 @@ export type ListConversionResult = {
50
50
  * - `'auto_detected'` — found via recursive DFS (longest array wins)
51
51
  */
52
52
  strategy: 'configured_paths' | 'auto_detected';
53
- /** Dotted path to where the list was found (e.g. `"result.data"`, `"people"`). */
53
+ /** Dotted path to where the list was found (e.g. `"output.body"`, `"people"`). */
54
54
  sourcePath: string | null;
55
55
  };
56
56
 
@@ -75,7 +75,7 @@ function normalizeScalarString(value: unknown): string | null {
75
75
  * Traverse a nested object by a dotted path string.
76
76
  *
77
77
  * @param root - Object to traverse
78
- * @param dottedPath - Path like `"result.data.items"`
78
+ * @param dottedPath - Path like `"output.body.items"`
79
79
  * @returns Value at the path, or `null` if not found
80
80
  *
81
81
  * @example
@@ -109,10 +109,29 @@ function normalizeRows(value: unknown): Array<Record<string, unknown>> | null {
109
109
 
110
110
  /**
111
111
  * Generate candidate root objects to search for lists.
112
- * Tries: raw payload → payload.result → payload.result.data.
112
+ * Tries: raw payload → V2 toolExecutionResult.toolOutput.raw → legacy payload.output.body → legacy payload.result → legacy payload.result.data.
113
113
  */
114
114
  function candidateRoots(payload: unknown): Array<{ path: string | null; value: unknown }> {
115
115
  const roots: Array<{ path: string | null; value: unknown }> = [{ path: null, value: payload }];
116
+ if (isPlainObject(payload) && isPlainObject(payload.toolExecutionResult)) {
117
+ roots.push({ path: 'toolExecutionResult', value: payload.toolExecutionResult });
118
+ const toolOutput = payload.toolExecutionResult.toolOutput;
119
+ if (isPlainObject(toolOutput)) {
120
+ roots.push({ path: 'toolExecutionResult.toolOutput', value: toolOutput });
121
+ if (Object.prototype.hasOwnProperty.call(toolOutput, 'raw')) {
122
+ roots.push({
123
+ path: 'toolExecutionResult.toolOutput.raw',
124
+ value: toolOutput.raw,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ if (isPlainObject(payload) && isPlainObject(payload.output)) {
130
+ roots.push({ path: 'output', value: payload.output });
131
+ if (Object.prototype.hasOwnProperty.call(payload.output, 'body')) {
132
+ roots.push({ path: 'output.body', value: payload.output.body });
133
+ }
134
+ }
116
135
  if (isPlainObject(payload) && isPlainObject(payload.result)) {
117
136
  roots.push({ path: 'result', value: payload.result });
118
137
  if (isPlainObject(payload.result.data)) {
@@ -167,7 +186,7 @@ function findBestArrayCandidate(
167
186
  * ## Extraction strategy
168
187
  *
169
188
  * 1. **Configured paths** — If `listExtractorPaths` is provided, each path is
170
- * tried against multiple candidate roots (raw payload, `.result`, `.result.data`).
189
+ * tried against multiple candidate roots (raw payload, `.output.body`, legacy `.result`, legacy `.result.data`).
171
190
  * First match wins.
172
191
  *
173
192
  * 2. **Auto-detection** — If no configured path matches, recursively searches
@@ -344,7 +363,7 @@ export function writeCsvOutputFile(
344
363
  /**
345
364
  * Extract scalar (non-nested) fields from a tool response for summary display.
346
365
  *
347
- * Searches through candidate roots (raw → `.result` → `.result.data`) and
366
+ * Searches through candidate roots (raw → `.output.body` → legacy `.result` → legacy `.result.data`) and
348
367
  * returns the first set of scalar fields found. Useful for displaying a
349
368
  * quick summary of single-record responses.
350
369
  *
@@ -30,7 +30,7 @@ import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-mani
30
30
  export interface DeeplineClientOptions {
31
31
  /** API key. Overrides `DEEPLINE_API_KEY` env var and CLI-stored keys. */
32
32
  apiKey?: string;
33
- /** Base URL of the Deepline API. Overrides `DEEPLINE_ORIGIN_URL` / `DEEPLINE_API_BASE_URL`. */
33
+ /** Base URL of the Deepline API. Overrides `DEEPLINE_HOST_URL`. */
34
34
  baseUrl?: string;
35
35
  /** Per-request timeout in milliseconds. Default: `60_000` (60 seconds). */
36
36
  timeout?: number;
@@ -103,17 +103,51 @@ export interface ToolDefinition {
103
103
  inputSchema?: Record<string, unknown>;
104
104
  /** JSON Schema describing the tool's output shape. */
105
105
  outputSchema?: Record<string, unknown>;
106
- /**
107
- * Dotted paths for extracting list data from the tool's response.
108
- *
109
- * Used by {@link tryConvertToList} to automatically find the array of results
110
- * in a nested response. Example: `["people", "result.data"]`.
111
- */
112
- listExtractorPaths?: string[];
113
- /** Field mappings for extracting identity fields (email, phone, etc.) from results. */
114
- resultIdentityGetters?: Record<string, string[]>;
115
- /** Whether the output is a direct object or wrapped in a `result` envelope. */
116
- rowContextShape?: 'direct' | 'result_envelope';
106
+ /** Copyable play-runtime guidance for V2 tool execution results. */
107
+ usageGuidance?: {
108
+ execute?: string;
109
+ toolExecutionResult?: {
110
+ type?: 'ToolExecutionResult';
111
+ toolOutput?: {
112
+ raw?: string | { path?: string; schema?: Record<string, unknown> };
113
+ meta?: string | { path?: string; schema?: Record<string, unknown> };
114
+ };
115
+ meta?: string;
116
+ extractedLists?: Array<{
117
+ name: string;
118
+ expression: string;
119
+ details?: {
120
+ strategy?: string;
121
+ rawToolOutputPaths?: string[];
122
+ candidatePaths?: string[];
123
+ };
124
+ }> | Record<string, {
125
+ expression: string;
126
+ details?: {
127
+ strategy?: string;
128
+ rawToolOutputPaths?: string[];
129
+ candidatePaths?: string[];
130
+ };
131
+ }>;
132
+ extractedValues?: Array<{
133
+ name: string;
134
+ expression: string;
135
+ details?: {
136
+ strategy?: string;
137
+ rawToolOutputPaths?: string[];
138
+ candidatePaths?: string[];
139
+ };
140
+ }> | Record<string, {
141
+ expression: string;
142
+ details?: {
143
+ strategy?: string;
144
+ rawToolOutputPaths?: string[];
145
+ candidatePaths?: string[];
146
+ };
147
+ }>;
148
+ [key: string]: unknown;
149
+ };
150
+ };
117
151
  /** Search relevance score returned by ranked tool search. */
118
152
  search_score?: number;
119
153
  /** Search match snippets returned by ranked tool search. */
@@ -251,6 +285,8 @@ export interface PlayStatus {
251
285
  name?: string;
252
286
  /** Alias for `name` used by run/result APIs. */
253
287
  playName?: string;
288
+ /** Dashboard URL for inspecting the play and its run output in the app. */
289
+ dashboardUrl?: string;
254
290
  /** Product-level play-run state. */
255
291
  status:
256
292
  | 'queued'
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.1.32";
2
- export const SDK_API_CONTRACT = "2026-05-generic-play-input-flags";
1
+ export const SDK_VERSION = "0.1.35";
2
+ export const SDK_API_CONTRACT = "2026-05-v2-tool-result-contract";
@@ -0,0 +1,49 @@
1
+ export const CLOUDFLARE_DURABLE_OBJECT_CODE_UPDATED_ERROR =
2
+ 'Durable Object reset because its code was updated.';
3
+
4
+ export const PLATFORM_DEPLOY_INTERRUPTED_MESSAGE =
5
+ 'Run interrupted by a platform deploy and was not retried automatically. Re-run the same command; the input is unchanged.';
6
+
7
+ export type PlayRunFailureDetails = {
8
+ code: string;
9
+ phase: string;
10
+ message: string;
11
+ retryable: boolean | null;
12
+ cause?: string;
13
+ };
14
+
15
+ function toErrorText(error: unknown): string {
16
+ if (error instanceof Error) {
17
+ return error.message;
18
+ }
19
+ return String(error);
20
+ }
21
+
22
+ export function isCloudflareDurableObjectCodeUpdatedError(
23
+ error: unknown,
24
+ ): boolean {
25
+ return toErrorText(error).includes(
26
+ CLOUDFLARE_DURABLE_OBJECT_CODE_UPDATED_ERROR,
27
+ );
28
+ }
29
+
30
+ export function normalizePlayRunFailure(
31
+ error: unknown,
32
+ ): PlayRunFailureDetails {
33
+ const cause = toErrorText(error);
34
+ if (isCloudflareDurableObjectCodeUpdatedError(cause)) {
35
+ return {
36
+ code: 'PLATFORM_DEPLOY_INTERRUPTED',
37
+ phase: 'runtime',
38
+ message: PLATFORM_DEPLOY_INTERRUPTED_MESSAGE,
39
+ retryable: true,
40
+ cause: CLOUDFLARE_DURABLE_OBJECT_CODE_UPDATED_ERROR,
41
+ };
42
+ }
43
+ return {
44
+ code: 'RUN_FAILED',
45
+ phase: 'runtime',
46
+ message: cause,
47
+ retryable: null,
48
+ };
49
+ }
@@ -116,7 +116,7 @@ export type PlaySchedulerRunHandle = {
116
116
  * Stream live progress events. Implementations may use SSE, polling, etc.
117
117
  * The contract: yields events in order until terminal status.
118
118
  */
119
- observe(): AsyncIterable<PlaySchedulerProgressEvent>;
119
+ observe(options?: { signal?: AbortSignal }): AsyncIterable<PlaySchedulerProgressEvent>;
120
120
  /** Cooperatively cancel the run. */
121
121
  cancel(): Promise<void>;
122
122
  /** Inject an external event (HITL, webhook). */