deepline 0.1.33 → 0.1.36
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/cli/index.js +991 -219
- package/dist/cli/index.mjs +924 -152
- package/dist/index.d.mts +124 -62
- package/dist/index.d.ts +124 -62
- package/dist/index.js +522 -45
- package/dist/index.mjs +522 -45
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +212 -1
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +129 -2
- package/dist/repo/apps/play-runner-workers/src/entry.ts +186 -75
- package/dist/repo/apps/play-runner-workers/src/runtime/receipts.ts +138 -0
- package/dist/repo/apps/play-runner-workers/src/workflow-retry.ts +50 -0
- package/dist/repo/sdk/src/client.ts +43 -32
- package/dist/repo/sdk/src/http.ts +44 -4
- package/dist/repo/sdk/src/index.ts +8 -5
- package/dist/repo/sdk/src/play.ts +126 -45
- package/dist/repo/sdk/src/plays/harness-stub.ts +2 -2
- package/dist/repo/sdk/src/tool-output.ts +22 -7
- package/dist/repo/sdk/src/types.ts +45 -11
- package/dist/repo/sdk/src/version.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/run-failure.ts +49 -0
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +154 -35
- package/package.json +1 -1
|
@@ -67,6 +67,8 @@ import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-mani
|
|
|
67
67
|
|
|
68
68
|
const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
69
69
|
const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
|
|
70
|
+
const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
|
|
71
|
+
const V2_EXECUTE_RESPONSE_CONTRACT = 'v2-tool-response';
|
|
70
72
|
const COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1_000];
|
|
71
73
|
|
|
72
74
|
function sleep(ms: number): Promise<void> {
|
|
@@ -95,10 +97,13 @@ type ExecuteToolRawOptions = {
|
|
|
95
97
|
export type ToolExecution<TData = unknown, TMeta = Record<string, unknown>> = {
|
|
96
98
|
status: string;
|
|
97
99
|
job_id?: string;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
meta?: Record<string, unknown>;
|
|
101
|
+
toolResponse: {
|
|
102
|
+
raw: TData;
|
|
100
103
|
meta?: TMeta;
|
|
101
104
|
};
|
|
105
|
+
extractedLists?: Record<string, unknown>;
|
|
106
|
+
extractedValues?: Record<string, unknown>;
|
|
102
107
|
billing?: Record<string, unknown>;
|
|
103
108
|
[key: string]: unknown;
|
|
104
109
|
};
|
|
@@ -552,19 +557,22 @@ export class DeeplineClient {
|
|
|
552
557
|
/**
|
|
553
558
|
* Execute a tool and return the standard execution envelope.
|
|
554
559
|
*
|
|
555
|
-
* The `
|
|
556
|
-
* contains provider
|
|
560
|
+
* The `toolResponse.raw` field contains the raw tool response.
|
|
561
|
+
* `toolResponse.meta` contains tool/provider metadata.
|
|
557
562
|
* Top-level fields such as `status`, `job_id`, and `billing` describe the
|
|
558
|
-
* Deepline execution.
|
|
563
|
+
* Deepline execution envelope.
|
|
559
564
|
*/
|
|
560
565
|
async executeTool<TData = unknown, TMeta = Record<string, unknown>>(
|
|
561
566
|
toolId: string,
|
|
562
567
|
input: Record<string, unknown>,
|
|
563
568
|
options?: ExecuteToolRawOptions,
|
|
564
569
|
): Promise<ToolExecution<TData, TMeta>> {
|
|
565
|
-
const headers =
|
|
566
|
-
|
|
567
|
-
|
|
570
|
+
const headers = {
|
|
571
|
+
[EXECUTE_RESPONSE_CONTRACT_HEADER]: V2_EXECUTE_RESPONSE_CONTRACT,
|
|
572
|
+
...(options?.includeToolMetadata
|
|
573
|
+
? { [INCLUDE_TOOL_METADATA_HEADER]: 'true' }
|
|
574
|
+
: {}),
|
|
575
|
+
};
|
|
568
576
|
return this.http.post<ToolExecution<TData, TMeta>>(
|
|
569
577
|
`/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
|
|
570
578
|
{ payload: input },
|
|
@@ -1029,34 +1037,37 @@ export class DeeplineClient {
|
|
|
1029
1037
|
bytes: number;
|
|
1030
1038
|
}>,
|
|
1031
1039
|
): Promise<PlayStagedFileRef[]> {
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
'metadata',
|
|
1035
|
-
JSON.stringify({
|
|
1036
|
-
files: files.map((file, index) => ({
|
|
1037
|
-
index,
|
|
1038
|
-
logicalPath: file.logicalPath,
|
|
1039
|
-
contentHash: file.contentHash,
|
|
1040
|
-
contentType: file.contentType,
|
|
1041
|
-
bytes: file.bytes,
|
|
1042
|
-
})),
|
|
1043
|
-
}),
|
|
1044
|
-
);
|
|
1045
|
-
for (const [index, file] of files.entries()) {
|
|
1046
|
-
const bytes = decodeBase64Bytes(file.contentBase64);
|
|
1047
|
-
const body = bytes.buffer.slice(
|
|
1048
|
-
bytes.byteOffset,
|
|
1049
|
-
bytes.byteOffset + bytes.byteLength,
|
|
1050
|
-
) as ArrayBuffer;
|
|
1040
|
+
const buildFormData = () => {
|
|
1041
|
+
const formData = new FormData();
|
|
1051
1042
|
formData.set(
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1043
|
+
'metadata',
|
|
1044
|
+
JSON.stringify({
|
|
1045
|
+
files: files.map((file, index) => ({
|
|
1046
|
+
index,
|
|
1047
|
+
logicalPath: file.logicalPath,
|
|
1048
|
+
contentHash: file.contentHash,
|
|
1049
|
+
contentType: file.contentType,
|
|
1050
|
+
bytes: file.bytes,
|
|
1051
|
+
})),
|
|
1052
|
+
}),
|
|
1055
1053
|
);
|
|
1056
|
-
|
|
1054
|
+
for (const [index, file] of files.entries()) {
|
|
1055
|
+
const bytes = decodeBase64Bytes(file.contentBase64);
|
|
1056
|
+
const body = bytes.buffer.slice(
|
|
1057
|
+
bytes.byteOffset,
|
|
1058
|
+
bytes.byteOffset + bytes.byteLength,
|
|
1059
|
+
) as ArrayBuffer;
|
|
1060
|
+
formData.set(
|
|
1061
|
+
`file:${index}`,
|
|
1062
|
+
new Blob([body], { type: file.contentType }),
|
|
1063
|
+
file.logicalPath,
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
return formData;
|
|
1067
|
+
};
|
|
1057
1068
|
const response = await this.http.postFormData<{ files: PlayStagedFileRef[] }>(
|
|
1058
1069
|
'/api/v2/plays/files/stage',
|
|
1059
|
-
|
|
1070
|
+
buildFormData,
|
|
1060
1071
|
);
|
|
1061
1072
|
return response.files;
|
|
1062
1073
|
}
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
interface RequestOptions {
|
|
32
32
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
33
33
|
body?: unknown;
|
|
34
|
-
formData?: FormData;
|
|
34
|
+
formData?: FormData | (() => FormData);
|
|
35
35
|
headers?: Record<string, string>;
|
|
36
36
|
/** Per-request timeout override in milliseconds. */
|
|
37
37
|
timeout?: number;
|
|
@@ -153,7 +153,9 @@ export class HttpClient {
|
|
|
153
153
|
headers,
|
|
154
154
|
body:
|
|
155
155
|
options?.formData !== undefined
|
|
156
|
-
? options.formData
|
|
156
|
+
? typeof options.formData === 'function'
|
|
157
|
+
? options.formData()
|
|
158
|
+
: options.formData
|
|
157
159
|
: options?.body !== undefined
|
|
158
160
|
? JSON.stringify(options.body)
|
|
159
161
|
: undefined,
|
|
@@ -277,10 +279,13 @@ export class HttpClient {
|
|
|
277
279
|
throw new AuthError();
|
|
278
280
|
}
|
|
279
281
|
if (!response.ok) {
|
|
282
|
+
const body = await response.text();
|
|
283
|
+
const parsed = parseResponseBody(body);
|
|
280
284
|
throw new DeeplineError(
|
|
281
|
-
|
|
285
|
+
apiErrorMessage(parsed, response.status),
|
|
282
286
|
response.status,
|
|
283
287
|
'API_ERROR',
|
|
288
|
+
{ response: parsed },
|
|
284
289
|
);
|
|
285
290
|
}
|
|
286
291
|
if (!response.body) {
|
|
@@ -325,7 +330,7 @@ export class HttpClient {
|
|
|
325
330
|
|
|
326
331
|
async postFormData<T = unknown>(
|
|
327
332
|
path: string,
|
|
328
|
-
formData: FormData,
|
|
333
|
+
formData: FormData | (() => FormData),
|
|
329
334
|
headers?: Record<string, string>,
|
|
330
335
|
): Promise<T> {
|
|
331
336
|
return this.request<T>(path, {
|
|
@@ -346,6 +351,41 @@ export class HttpClient {
|
|
|
346
351
|
}
|
|
347
352
|
}
|
|
348
353
|
|
|
354
|
+
function parseResponseBody(body: string): unknown {
|
|
355
|
+
try {
|
|
356
|
+
return JSON.parse(body);
|
|
357
|
+
} catch {
|
|
358
|
+
return body;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function apiErrorMessage(parsed: unknown, status: number): string {
|
|
363
|
+
const errorValue =
|
|
364
|
+
typeof parsed === 'object' && parsed && 'error' in parsed
|
|
365
|
+
? (parsed as Record<string, unknown>).error
|
|
366
|
+
: undefined;
|
|
367
|
+
if (typeof errorValue === 'string') {
|
|
368
|
+
return errorValue;
|
|
369
|
+
}
|
|
370
|
+
if (
|
|
371
|
+
errorValue &&
|
|
372
|
+
typeof errorValue === 'object' &&
|
|
373
|
+
'message' in errorValue &&
|
|
374
|
+
typeof (errorValue as Record<string, unknown>).message === 'string'
|
|
375
|
+
) {
|
|
376
|
+
return (errorValue as Record<string, string>).message;
|
|
377
|
+
}
|
|
378
|
+
if (
|
|
379
|
+
typeof parsed === 'object' &&
|
|
380
|
+
parsed &&
|
|
381
|
+
'message' in parsed &&
|
|
382
|
+
typeof (parsed as Record<string, unknown>).message === 'string'
|
|
383
|
+
) {
|
|
384
|
+
return (parsed as Record<string, string>).message;
|
|
385
|
+
}
|
|
386
|
+
return `HTTP ${status}`;
|
|
387
|
+
}
|
|
388
|
+
|
|
349
389
|
/** Parse the `Retry-After` header as milliseconds. Falls back to 5000ms. */
|
|
350
390
|
function parseRetryAfter(response: Response): number {
|
|
351
391
|
const header = response.headers.get('retry-after');
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* import { Deepline, definePlay } from 'deepline';
|
|
8
8
|
*
|
|
9
9
|
* // Connect (auto-resolves API key from env / CLI config)
|
|
10
|
-
* const
|
|
10
|
+
* const deepline = await Deepline.connect();
|
|
11
11
|
*
|
|
12
12
|
* // Execute a tool
|
|
13
|
-
* const result = await
|
|
14
|
-
* const company = result.
|
|
13
|
+
* const result = await deepline.tools.execute('test_company_search', { domain: 'stripe.com' });
|
|
14
|
+
* const company = result.toolResponse.raw;
|
|
15
15
|
*
|
|
16
16
|
* // Run a named play
|
|
17
|
-
* const job = await
|
|
17
|
+
* const job = await deepline.play('email-waterfall').run({ domain: 'stripe.com' });
|
|
18
18
|
* const result = await job.get();
|
|
19
19
|
* ```
|
|
20
20
|
*
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
*
|
|
26
26
|
* export default definePlay('my-play', async (ctx, input: { domain: string }) => {
|
|
27
27
|
* ctx.log(`Looking up ${input.domain}`);
|
|
28
|
-
* const company = await ctx.tools.execute(
|
|
28
|
+
* const company = await ctx.tools.execute({
|
|
29
|
+
* id: 'company_search',
|
|
30
|
+
* tool: 'test_company_search',
|
|
31
|
+
* input: { domain: input.domain },
|
|
29
32
|
* description: 'Look up company details by domain.',
|
|
30
33
|
* });
|
|
31
34
|
* return company;
|
|
@@ -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(
|
|
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
|
|
46
|
-
* const job = await
|
|
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(
|
|
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(
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
|
399
|
+
* Execute a single tool with a keyword-style request object.
|
|
383
400
|
*
|
|
384
|
-
* @param
|
|
385
|
-
* @param
|
|
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(
|
|
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
|
|
903
|
+
* const deepline = await Deepline.connect();
|
|
890
904
|
*
|
|
891
905
|
* // Tools
|
|
892
|
-
* const tools = await
|
|
893
|
-
* const result = await
|
|
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
|
|
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
|
|
913
|
-
* const meta = await
|
|
914
|
-
* const companyLookup = await
|
|
915
|
-
* const company = companyLookup.
|
|
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.toolResponse.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
|
-
)
|
|
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
|
|
1027
|
-
* const tools = await
|
|
1028
|
-
* const result = await
|
|
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,62 @@ 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.toolResponse?.raw ?? null;
|
|
1100
|
+
const meta = response.toolResponse?.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
|
+
jobId: typeof response.job_id === 'string' ? response.job_id : undefined,
|
|
1109
|
+
result: {
|
|
1110
|
+
data: raw,
|
|
1111
|
+
...(isRecord(meta) ? { meta } : {}),
|
|
1112
|
+
},
|
|
1113
|
+
metadata: {
|
|
1114
|
+
toolId:
|
|
1115
|
+
typeof toolMetadata.toolId === 'string'
|
|
1116
|
+
? toolMetadata.toolId
|
|
1117
|
+
: fallbackToolId,
|
|
1118
|
+
resultIdentityGetters: stringArrayRecord(
|
|
1119
|
+
toolMetadata.resultIdentityGetters,
|
|
1120
|
+
),
|
|
1121
|
+
listExtractorPaths: stringArray(toolMetadata.listExtractorPaths),
|
|
1122
|
+
listIdentityGetters: stringArrayRecord(toolMetadata.listIdentityGetters),
|
|
1123
|
+
},
|
|
1124
|
+
execution: {
|
|
1125
|
+
idempotent: true,
|
|
1126
|
+
cached: false,
|
|
1127
|
+
source: 'live',
|
|
1128
|
+
},
|
|
1129
|
+
meta: isRecord(response.meta) ? response.meta : undefined,
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1061
1133
|
export function defineInput<TInput>(
|
|
1062
1134
|
schema: Record<string, unknown>,
|
|
1063
1135
|
): PlayInputContract<TInput> {
|
|
@@ -1092,7 +1164,10 @@ export function defineInput<TInput>(
|
|
|
1092
1164
|
*
|
|
1093
1165
|
* export default definePlay('company-lookup', async (ctx, input: { domain: string }) => {
|
|
1094
1166
|
* ctx.log(`Searching for ${input.domain}`);
|
|
1095
|
-
* const company = await ctx.tools.execute(
|
|
1167
|
+
* const company = await ctx.tools.execute({
|
|
1168
|
+
* id: 'company_search',
|
|
1169
|
+
* tool: 'test_company_search',
|
|
1170
|
+
* input: { domain: input.domain },
|
|
1096
1171
|
* description: 'Look up company details by domain.',
|
|
1097
1172
|
* });
|
|
1098
1173
|
* return company;
|
|
@@ -1107,7 +1182,10 @@ export function defineInput<TInput>(
|
|
|
1107
1182
|
* const results = await ctx
|
|
1108
1183
|
* .map('companies', leads)
|
|
1109
1184
|
* .step('company', (row, ctx) =>
|
|
1110
|
-
* ctx.tools.execute(
|
|
1185
|
+
* ctx.tools.execute({
|
|
1186
|
+
* id: 'company_search',
|
|
1187
|
+
* tool: 'test_company_search',
|
|
1188
|
+
* input: { domain: row.domain },
|
|
1111
1189
|
* description: 'Look up company details by domain.',
|
|
1112
1190
|
* }))
|
|
1113
1191
|
* .run({ description: 'Enrich lead companies.' });
|
|
@@ -1118,7 +1196,10 @@ export function defineInput<TInput>(
|
|
|
1118
1196
|
* @example With cron binding
|
|
1119
1197
|
* ```typescript
|
|
1120
1198
|
* export default definePlay('daily-report', async (ctx) => {
|
|
1121
|
-
* const data = await ctx.tools.execute(
|
|
1199
|
+
* const data = await ctx.tools.execute({
|
|
1200
|
+
* id: 'crm_export',
|
|
1201
|
+
* tool: 'crm_export',
|
|
1202
|
+
* input: { since: 'yesterday' },
|
|
1122
1203
|
* description: 'Export yesterday CRM records.',
|
|
1123
1204
|
* });
|
|
1124
1205
|
* 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 `{
|
|
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', '
|
|
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. `"
|
|
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 `"
|
|
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,25 @@ 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 toolResponse.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.toolResponse)) {
|
|
117
|
+
roots.push({ path: 'toolResponse', value: payload.toolResponse });
|
|
118
|
+
if (Object.prototype.hasOwnProperty.call(payload.toolResponse, 'raw')) {
|
|
119
|
+
roots.push({
|
|
120
|
+
path: 'toolResponse.raw',
|
|
121
|
+
value: payload.toolResponse.raw,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (isPlainObject(payload) && isPlainObject(payload.output)) {
|
|
126
|
+
roots.push({ path: 'output', value: payload.output });
|
|
127
|
+
if (Object.prototype.hasOwnProperty.call(payload.output, 'body')) {
|
|
128
|
+
roots.push({ path: 'output.body', value: payload.output.body });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
116
131
|
if (isPlainObject(payload) && isPlainObject(payload.result)) {
|
|
117
132
|
roots.push({ path: 'result', value: payload.result });
|
|
118
133
|
if (isPlainObject(payload.result.data)) {
|
|
@@ -167,7 +182,7 @@ function findBestArrayCandidate(
|
|
|
167
182
|
* ## Extraction strategy
|
|
168
183
|
*
|
|
169
184
|
* 1. **Configured paths** — If `listExtractorPaths` is provided, each path is
|
|
170
|
-
* tried against multiple candidate roots (raw payload, `.result`, `.result.data`).
|
|
185
|
+
* tried against multiple candidate roots (raw payload, `.output.body`, legacy `.result`, legacy `.result.data`).
|
|
171
186
|
* First match wins.
|
|
172
187
|
*
|
|
173
188
|
* 2. **Auto-detection** — If no configured path matches, recursively searches
|
|
@@ -344,7 +359,7 @@ export function writeCsvOutputFile(
|
|
|
344
359
|
/**
|
|
345
360
|
* Extract scalar (non-nested) fields from a tool response for summary display.
|
|
346
361
|
*
|
|
347
|
-
* Searches through candidate roots (raw → `.result` → `.result.data`) and
|
|
362
|
+
* Searches through candidate roots (raw → `.output.body` → legacy `.result` → legacy `.result.data`) and
|
|
348
363
|
* returns the first set of scalar fields found. Useful for displaying a
|
|
349
364
|
* quick summary of single-record responses.
|
|
350
365
|
*
|