deepline 0.1.149 → 0.1.151
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/bundling-sources/apps/play-runner-workers/src/entry.ts +157 -140
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/csv-rows.ts +2 -19
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/row-isolation.ts +5 -53
- package/dist/bundling-sources/sdk/src/client.ts +5 -0
- package/dist/bundling-sources/sdk/src/config.ts +2 -2
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/tool-output.ts +63 -17
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +100 -158
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-row-outcome.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/pacing.ts +79 -0
- package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +39 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +19 -86
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-transition.ts +90 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-session.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +142 -11
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result-types.ts +17 -4
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +343 -26
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +20 -23
- package/dist/cli/index.js +186 -105
- package/dist/cli/index.mjs +193 -106
- package/dist/index.d.mts +12 -9
- package/dist/index.d.ts +12 -9
- package/dist/index.js +33 -20
- package/dist/index.mjs +40 -21
- package/dist/plays/bundle-play-file.mjs +22 -19
- package/package.json +1 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
isHardBillingToolHttpError,
|
|
3
|
+
normalizeToolHttpErrorMessage,
|
|
4
|
+
type ToolHttpError,
|
|
5
|
+
} from './tool-http-errors';
|
|
6
|
+
|
|
7
|
+
export const TOOL_EXECUTE_TRANSIENT_HTTP_MAX_ATTEMPTS = 2;
|
|
2
8
|
export const TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS = 8;
|
|
3
9
|
export const TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS = 3;
|
|
4
10
|
export const TOOL_EXECUTE_TRANSPORT_RETRY_DELAY_MS = 1_000;
|
|
11
|
+
export const TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS = 1_000;
|
|
12
|
+
export const TOOL_EXECUTE_RETRY_DELAY_MAX_MS = 5_000;
|
|
5
13
|
|
|
6
14
|
export type ToolExecuteHttpRetryDecision = {
|
|
7
15
|
retryable: boolean;
|
|
@@ -9,15 +17,33 @@ export type ToolExecuteHttpRetryDecision = {
|
|
|
9
17
|
reason:
|
|
10
18
|
| 'rate_limit'
|
|
11
19
|
| 'retry_safe_transient_5xx'
|
|
12
|
-
| 'hard_billing_error'
|
|
13
20
|
| 'unsafe_transient_5xx'
|
|
21
|
+
| 'hard_billing_error'
|
|
14
22
|
| 'non_retryable_status';
|
|
15
23
|
};
|
|
16
24
|
|
|
17
|
-
export
|
|
18
|
-
|
|
25
|
+
export type ToolExecuteHttpFailureOutcome = ToolExecuteHttpRetryDecision & {
|
|
26
|
+
error: ToolHttpError;
|
|
27
|
+
shouldRetry: boolean;
|
|
28
|
+
isRateLimit: boolean;
|
|
29
|
+
fate: 'retry' | 'settle_row_failure' | 'fail_run';
|
|
30
|
+
retryDelayMs: number;
|
|
31
|
+
backpressureDelayMs: number | null;
|
|
32
|
+
chargeRetryBudget: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ToolExecuteHttpFailureAttemptTracker = {
|
|
36
|
+
next(input: {
|
|
37
|
+
toolId: string;
|
|
38
|
+
status: number;
|
|
39
|
+
transientHttpRetrySafe?: boolean;
|
|
40
|
+
}): number;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function decideToolExecuteHttpRetry(input: {
|
|
19
44
|
status: number;
|
|
20
45
|
hardBillingFailure?: boolean;
|
|
46
|
+
transientHttpRetrySafe?: boolean;
|
|
21
47
|
}): ToolExecuteHttpRetryDecision {
|
|
22
48
|
if (input.status === 429) {
|
|
23
49
|
if (input.hardBillingFailure) {
|
|
@@ -34,17 +60,17 @@ export function decideToolExecuteHttpRetry(input: {
|
|
|
34
60
|
};
|
|
35
61
|
}
|
|
36
62
|
if (input.status >= 500 && input.status < 600) {
|
|
37
|
-
if (input.
|
|
63
|
+
if (!input.transientHttpRetrySafe) {
|
|
38
64
|
return {
|
|
39
|
-
retryable:
|
|
40
|
-
attemptCap:
|
|
41
|
-
reason: '
|
|
65
|
+
retryable: false,
|
|
66
|
+
attemptCap: 1,
|
|
67
|
+
reason: 'unsafe_transient_5xx',
|
|
42
68
|
};
|
|
43
69
|
}
|
|
44
70
|
return {
|
|
45
|
-
retryable:
|
|
46
|
-
attemptCap:
|
|
47
|
-
reason: '
|
|
71
|
+
retryable: true,
|
|
72
|
+
attemptCap: TOOL_EXECUTE_TRANSIENT_HTTP_MAX_ATTEMPTS,
|
|
73
|
+
reason: 'retry_safe_transient_5xx',
|
|
48
74
|
};
|
|
49
75
|
}
|
|
50
76
|
return {
|
|
@@ -53,3 +79,108 @@ export function decideToolExecuteHttpRetry(input: {
|
|
|
53
79
|
reason: 'non_retryable_status',
|
|
54
80
|
};
|
|
55
81
|
}
|
|
82
|
+
|
|
83
|
+
export function createToolExecuteHttpFailureAttemptTracker(): ToolExecuteHttpFailureAttemptTracker {
|
|
84
|
+
const attemptsByReason: Record<
|
|
85
|
+
ToolExecuteHttpRetryDecision['reason'],
|
|
86
|
+
number
|
|
87
|
+
> = {
|
|
88
|
+
rate_limit: 0,
|
|
89
|
+
retry_safe_transient_5xx: 0,
|
|
90
|
+
unsafe_transient_5xx: 0,
|
|
91
|
+
hard_billing_error: 0,
|
|
92
|
+
non_retryable_status: 0,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
next(input) {
|
|
97
|
+
const decision = decideToolExecuteHttpRetry({
|
|
98
|
+
status: input.status,
|
|
99
|
+
transientHttpRetrySafe: input.transientHttpRetrySafe === true,
|
|
100
|
+
});
|
|
101
|
+
attemptsByReason[decision.reason] += 1;
|
|
102
|
+
return attemptsByReason[decision.reason];
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function parseToolExecuteRetryAfterMs(
|
|
108
|
+
header: string | null | undefined,
|
|
109
|
+
nowMs = Date.now(),
|
|
110
|
+
): number {
|
|
111
|
+
if (!header) return TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
|
|
112
|
+
const seconds = Number(header);
|
|
113
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
114
|
+
return Math.ceil(seconds * 1000);
|
|
115
|
+
}
|
|
116
|
+
const retryAt = Date.parse(header);
|
|
117
|
+
if (Number.isFinite(retryAt)) {
|
|
118
|
+
return Math.max(1, retryAt - nowMs);
|
|
119
|
+
}
|
|
120
|
+
return TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function classifyToolExecuteHttpFailure(input: {
|
|
124
|
+
toolId: string;
|
|
125
|
+
status: number;
|
|
126
|
+
attempt: number;
|
|
127
|
+
bodyText: string;
|
|
128
|
+
retryAfterHeader?: string | null;
|
|
129
|
+
transientHttpRetrySafe?: boolean;
|
|
130
|
+
nowMs?: number;
|
|
131
|
+
}): ToolExecuteHttpFailureOutcome {
|
|
132
|
+
const transientHttpRetrySafe = input.transientHttpRetrySafe === true;
|
|
133
|
+
const initialRetryDecision = decideToolExecuteHttpRetry({
|
|
134
|
+
status: input.status,
|
|
135
|
+
transientHttpRetrySafe,
|
|
136
|
+
});
|
|
137
|
+
const error = normalizeToolHttpErrorMessage({
|
|
138
|
+
toolId: input.toolId,
|
|
139
|
+
status: input.status,
|
|
140
|
+
attempt: input.attempt,
|
|
141
|
+
maxAttempts: initialRetryDecision.attemptCap,
|
|
142
|
+
bodyText: input.bodyText,
|
|
143
|
+
});
|
|
144
|
+
const retryDecision = decideToolExecuteHttpRetry({
|
|
145
|
+
status: input.status,
|
|
146
|
+
hardBillingFailure: isHardBillingToolHttpError(error),
|
|
147
|
+
transientHttpRetrySafe,
|
|
148
|
+
});
|
|
149
|
+
const shouldRetry =
|
|
150
|
+
retryDecision.retryable && input.attempt < retryDecision.attemptCap;
|
|
151
|
+
const retryAfterMs = parseToolExecuteRetryAfterMs(
|
|
152
|
+
input.retryAfterHeader,
|
|
153
|
+
input.nowMs,
|
|
154
|
+
);
|
|
155
|
+
const retryDelayMs =
|
|
156
|
+
input.status === 429
|
|
157
|
+
? Math.min(
|
|
158
|
+
TOOL_EXECUTE_RETRY_DELAY_MAX_MS,
|
|
159
|
+
Math.max(
|
|
160
|
+
retryAfterMs,
|
|
161
|
+
TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS * input.attempt,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
: retryAfterMs > 0
|
|
165
|
+
? Math.min(TOOL_EXECUTE_RETRY_DELAY_MAX_MS, retryAfterMs)
|
|
166
|
+
: TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS;
|
|
167
|
+
return {
|
|
168
|
+
...retryDecision,
|
|
169
|
+
error,
|
|
170
|
+
shouldRetry,
|
|
171
|
+
isRateLimit: input.status === 429,
|
|
172
|
+
fate: shouldRetry
|
|
173
|
+
? 'retry'
|
|
174
|
+
: retryDecision.reason === 'hard_billing_error'
|
|
175
|
+
? 'fail_run'
|
|
176
|
+
: 'settle_row_failure',
|
|
177
|
+
retryDelayMs,
|
|
178
|
+
backpressureDelayMs:
|
|
179
|
+
input.status === 429
|
|
180
|
+
? retryAfterMs > 0
|
|
181
|
+
? retryAfterMs
|
|
182
|
+
: TOOL_EXECUTE_RETRY_DELAY_FALLBACK_MS
|
|
183
|
+
: null,
|
|
184
|
+
chargeRetryBudget: shouldRetry,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -240,8 +240,9 @@ export function isHardBillingToolHttpError(error: unknown): boolean {
|
|
|
240
240
|
|
|
241
241
|
/**
|
|
242
242
|
* A tool call that ultimately failed with HTTP 429 — provider or
|
|
243
|
-
* Deepline-internal rate-limit pushback
|
|
244
|
-
*
|
|
243
|
+
* Deepline-internal rate-limit pushback. While the local retry budget is
|
|
244
|
+
* active it feeds provider pacing; after exhaustion it becomes a row-scoped
|
|
245
|
+
* Map Row Outcome unless the payload is a hard Deepline billing failure.
|
|
245
246
|
*/
|
|
246
247
|
export function isRateLimitToolHttpError(error: unknown): boolean {
|
|
247
248
|
return error instanceof ToolHttpError && error.status === 429;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { EmailStatusExtractorConfig } from './email-status';
|
|
2
2
|
import type { DeeplineGetterValueMap } from './extractor-targets';
|
|
3
|
-
import type { PlayDataset } from '../plays/dataset';
|
|
3
|
+
import type { PlayDataset, SerializedPlayDataset } from '../plays/dataset';
|
|
4
4
|
|
|
5
5
|
export type ToolResultExecutionMetadata = {
|
|
6
6
|
idempotent: true;
|
|
@@ -73,10 +73,22 @@ export type ToolResultEnvelope<
|
|
|
73
73
|
export type SerializedToolExecuteResult = {
|
|
74
74
|
__kind: 'deepline.tool_execute_result.v1';
|
|
75
75
|
status: string;
|
|
76
|
+
job_id?: string;
|
|
77
|
+
/** Deepline-owned execution/result metadata. */
|
|
78
|
+
meta?: Record<string, unknown>;
|
|
76
79
|
toolResponse: {
|
|
80
|
+
/**
|
|
81
|
+
* Raw provider/tool data with declared row-list payloads clipped to bounded
|
|
82
|
+
* previews. Use list getters for row-shaped data.
|
|
83
|
+
*/
|
|
77
84
|
raw: unknown;
|
|
78
85
|
meta?: Record<string, unknown>;
|
|
79
86
|
};
|
|
87
|
+
listDatasets?: Record<string, SerializedPlayDataset<Record<string, unknown>>>;
|
|
88
|
+
/** Full declared list rows used to rehydrate list Dataset Handles. */
|
|
89
|
+
listRows?: Record<string, Array<Record<string, unknown>>>;
|
|
90
|
+
/** Computed scalar/object getters preserved before raw list previews are clipped. */
|
|
91
|
+
targetValues?: Record<string, ToolResultTargetMetadata>;
|
|
80
92
|
metadata: ToolResultMetadataInput;
|
|
81
93
|
execution: ToolResultExecutionMetadata;
|
|
82
94
|
};
|
|
@@ -142,9 +154,10 @@ export type ToolExecuteResultAccessors<
|
|
|
142
154
|
* getters live under `extractedValues.<name>.get()`, and list getters live
|
|
143
155
|
* under `extractedLists.<name>.get()`.
|
|
144
156
|
*
|
|
145
|
-
* Use extractors first when a tool contract exposes them.
|
|
146
|
-
* `toolResponse.raw`
|
|
147
|
-
*
|
|
157
|
+
* Use extractors first when a tool contract exposes them. Use list getters for
|
|
158
|
+
* row-shaped data. Drop to `toolResponse.raw` only for provider-specific scalar
|
|
159
|
+
* fields or bounded debugging context; persisted rows may clip declared lists to
|
|
160
|
+
* previews.
|
|
148
161
|
*
|
|
149
162
|
* @sdkReference runtime 200
|
|
150
163
|
*/
|
|
@@ -38,12 +38,21 @@ import type {
|
|
|
38
38
|
ToolResultTargetAccessor,
|
|
39
39
|
ToolResultTargetMetadata,
|
|
40
40
|
} from './tool-result-types';
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
createDeferredPlayDataset,
|
|
43
|
+
createPlayDataset,
|
|
44
|
+
isSerializedPlayDataset,
|
|
45
|
+
trimSerializedPlayDatasetPreview,
|
|
46
|
+
type PlayDataset,
|
|
47
|
+
type SerializedPlayDataset,
|
|
48
|
+
} from '../plays/dataset';
|
|
42
49
|
import { normalizeTableNamespace, sha256Hex } from '../plays/row-identity';
|
|
43
50
|
|
|
44
51
|
type PathSegment = string | number | '*';
|
|
45
52
|
|
|
46
53
|
const SERIALIZED_TOOL_EXECUTE_RESULT_KIND = 'deepline.tool_execute_result.v1';
|
|
54
|
+
const SERIALIZED_TOOL_RESULT_LIST_PREVIEW_LIMIT = 5;
|
|
55
|
+
const SERIALIZED_TOOL_LIST_ROWS = Symbol('deepline.serialized_tool_list_rows');
|
|
47
56
|
|
|
48
57
|
const TARGET_FALLBACK_KEYS: Record<string, readonly RegExp[]> = {
|
|
49
58
|
email: [/^email$/i, /^address$/i, /email/i],
|
|
@@ -949,6 +958,23 @@ function buildExtractedAccessors(
|
|
|
949
958
|
);
|
|
950
959
|
}
|
|
951
960
|
|
|
961
|
+
function serializedTargetValuesFromResult(
|
|
962
|
+
value: ToolExecuteResult,
|
|
963
|
+
): Record<string, ToolResultTargetMetadata> | undefined {
|
|
964
|
+
const targets = value._metadata.targets;
|
|
965
|
+
return Object.keys(targets).length > 0 ? targets : undefined;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function applySerializedTargetValues(
|
|
969
|
+
result: ToolExecuteResult,
|
|
970
|
+
targetValues: Record<string, ToolResultTargetMetadata> | undefined,
|
|
971
|
+
): ToolExecuteResult {
|
|
972
|
+
if (!targetValues || Object.keys(targetValues).length === 0) return result;
|
|
973
|
+
result._metadata.targets = targetValues;
|
|
974
|
+
result.extractedValues = buildExtractedAccessors(targetValues);
|
|
975
|
+
return result;
|
|
976
|
+
}
|
|
977
|
+
|
|
952
978
|
function buildListAccessors(
|
|
953
979
|
resolved: Record<string, { path: string; rows: Record<string, unknown>[] }>,
|
|
954
980
|
lists: Record<string, ToolResultListMetadata>,
|
|
@@ -1142,13 +1168,286 @@ export function readList(
|
|
|
1142
1168
|
): PlayDataset<Record<string, unknown>> | Record<string, unknown>[] {
|
|
1143
1169
|
if (selector) {
|
|
1144
1170
|
const paths = Array.isArray(selector) ? selector : [selector];
|
|
1171
|
+
const declaredList = findDeclaredListAccessor(result, paths);
|
|
1145
1172
|
const found = findFirstTargetByPath(resultRootOf(result), paths)?.value;
|
|
1146
1173
|
const rows = normalizeRows(found);
|
|
1147
|
-
if (rows)
|
|
1174
|
+
if (rows) {
|
|
1175
|
+
if (
|
|
1176
|
+
declaredList &&
|
|
1177
|
+
typeof declaredList.count === 'number' &&
|
|
1178
|
+
declaredList.count > rows.length
|
|
1179
|
+
) {
|
|
1180
|
+
return declaredList.get();
|
|
1181
|
+
}
|
|
1182
|
+
return rows;
|
|
1183
|
+
}
|
|
1184
|
+
if (declaredList) return declaredList.get();
|
|
1148
1185
|
}
|
|
1149
1186
|
return Object.values(result.extractedLists)[0]?.get() ?? [];
|
|
1150
1187
|
}
|
|
1151
1188
|
|
|
1189
|
+
function normalizeListPathForMatch(path: string): string {
|
|
1190
|
+
return path.replace(/\[(?:\*|\d+)\]$/g, '');
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function findDeclaredListAccessor(
|
|
1194
|
+
result: ToolExecuteResult,
|
|
1195
|
+
paths: readonly string[],
|
|
1196
|
+
): ToolResultListAccessor | undefined {
|
|
1197
|
+
const candidates = new Set(
|
|
1198
|
+
paths.flatMap((path) =>
|
|
1199
|
+
candidateResultPaths(path).flatMap((candidate) => [
|
|
1200
|
+
candidate,
|
|
1201
|
+
normalizeListPathForMatch(candidate),
|
|
1202
|
+
]),
|
|
1203
|
+
),
|
|
1204
|
+
);
|
|
1205
|
+
return Object.values(result.extractedLists).find((list) =>
|
|
1206
|
+
candidates.has(normalizeListPathForMatch(list.path)),
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function replaceAtPath(
|
|
1211
|
+
root: unknown,
|
|
1212
|
+
path: string,
|
|
1213
|
+
replacement: unknown,
|
|
1214
|
+
): unknown {
|
|
1215
|
+
const segments = parsePath(path);
|
|
1216
|
+
if (segments.length === 0 || segments.includes('*')) return root;
|
|
1217
|
+
const replace = (current: unknown, index: number): unknown => {
|
|
1218
|
+
if (index >= segments.length) return replacement;
|
|
1219
|
+
const segment = segments[index]!;
|
|
1220
|
+
if (typeof segment === 'number') {
|
|
1221
|
+
if (!Array.isArray(current)) return current;
|
|
1222
|
+
const copy = [...current];
|
|
1223
|
+
copy[segment] = replace(copy[segment], index + 1);
|
|
1224
|
+
return copy;
|
|
1225
|
+
}
|
|
1226
|
+
if (!isRecord(current)) return current;
|
|
1227
|
+
return {
|
|
1228
|
+
...current,
|
|
1229
|
+
[segment]: replace(current[segment], index + 1),
|
|
1230
|
+
};
|
|
1231
|
+
};
|
|
1232
|
+
return replace(root, 0);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function serializedListDatasetsFromResult(
|
|
1236
|
+
value: ToolExecuteResult,
|
|
1237
|
+
): Record<string, SerializedPlayDataset<Record<string, unknown>>> | undefined {
|
|
1238
|
+
const entries = Object.entries(value.extractedLists).flatMap(
|
|
1239
|
+
([name, accessor]) => {
|
|
1240
|
+
const serialized = accessor.get().toJSON();
|
|
1241
|
+
if (!isSerializedPlayDataset<Record<string, unknown>>(serialized)) {
|
|
1242
|
+
return [];
|
|
1243
|
+
}
|
|
1244
|
+
return [
|
|
1245
|
+
[
|
|
1246
|
+
name,
|
|
1247
|
+
trimSerializedPlayDatasetPreview(
|
|
1248
|
+
serialized,
|
|
1249
|
+
SERIALIZED_TOOL_RESULT_LIST_PREVIEW_LIMIT,
|
|
1250
|
+
),
|
|
1251
|
+
],
|
|
1252
|
+
] as const;
|
|
1253
|
+
},
|
|
1254
|
+
);
|
|
1255
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function serializedListRowsFromRaw(input: {
|
|
1259
|
+
raw: unknown;
|
|
1260
|
+
metadata: ToolResultMetadataInput;
|
|
1261
|
+
}): Record<string, Array<Record<string, unknown>>> | undefined {
|
|
1262
|
+
const resolved = resolveListRows(
|
|
1263
|
+
{ toolResponse: { raw: input.raw } },
|
|
1264
|
+
input.metadata.listExtractorPaths,
|
|
1265
|
+
);
|
|
1266
|
+
const entries = Object.entries(resolved).map(([name, list]) => [
|
|
1267
|
+
name,
|
|
1268
|
+
list.rows,
|
|
1269
|
+
]);
|
|
1270
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function preservedSerializedListRows(
|
|
1274
|
+
value: ToolExecuteResult,
|
|
1275
|
+
): Record<string, Array<Record<string, unknown>>> | undefined {
|
|
1276
|
+
const rows = (value as ToolExecuteResult & {
|
|
1277
|
+
[SERIALIZED_TOOL_LIST_ROWS]?:
|
|
1278
|
+
| Record<string, Array<Record<string, unknown>>>
|
|
1279
|
+
| undefined;
|
|
1280
|
+
})[SERIALIZED_TOOL_LIST_ROWS];
|
|
1281
|
+
return rows && Object.keys(rows).length > 0 ? rows : undefined;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function serializedListRowsFromResult(input: {
|
|
1285
|
+
value: ToolExecuteResult;
|
|
1286
|
+
metadata: ToolResultMetadataInput;
|
|
1287
|
+
listDatasets:
|
|
1288
|
+
| Record<string, SerializedPlayDataset<Record<string, unknown>>>
|
|
1289
|
+
| undefined;
|
|
1290
|
+
}): Record<string, Array<Record<string, unknown>>> | undefined {
|
|
1291
|
+
const preserved = preservedSerializedListRows(input.value);
|
|
1292
|
+
const rawRows = serializedListRowsFromRaw({
|
|
1293
|
+
raw: input.value.toolResponse.raw,
|
|
1294
|
+
metadata: input.metadata,
|
|
1295
|
+
});
|
|
1296
|
+
const names = new Set([
|
|
1297
|
+
...Object.keys(input.listDatasets ?? {}),
|
|
1298
|
+
...Object.keys(preserved ?? {}),
|
|
1299
|
+
...Object.keys(rawRows ?? {}),
|
|
1300
|
+
]);
|
|
1301
|
+
const entries = [...names].flatMap((name) => {
|
|
1302
|
+
const dataset = input.listDatasets?.[name];
|
|
1303
|
+
const preservedRows = preserved?.[name];
|
|
1304
|
+
if (
|
|
1305
|
+
preservedRows &&
|
|
1306
|
+
(!dataset || preservedRows.length >= dataset.count)
|
|
1307
|
+
) {
|
|
1308
|
+
return [[name, preservedRows]] as const;
|
|
1309
|
+
}
|
|
1310
|
+
const rows = rawRows?.[name];
|
|
1311
|
+
if (rows && (!dataset || rows.length >= dataset.count)) {
|
|
1312
|
+
return [[name, rows]] as const;
|
|
1313
|
+
}
|
|
1314
|
+
return [];
|
|
1315
|
+
});
|
|
1316
|
+
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
function serializedRawWithListPreviews(input: {
|
|
1320
|
+
raw: unknown;
|
|
1321
|
+
metadata: ToolResultMetadataInput;
|
|
1322
|
+
listDatasets:
|
|
1323
|
+
| Record<string, SerializedPlayDataset<Record<string, unknown>>>
|
|
1324
|
+
| undefined;
|
|
1325
|
+
}): unknown {
|
|
1326
|
+
if (!input.listDatasets) return input.raw;
|
|
1327
|
+
let root: unknown = { toolResponse: { raw: input.raw } };
|
|
1328
|
+
const datasetsBySource = new Map(
|
|
1329
|
+
Object.values(input.listDatasets).flatMap((dataset) =>
|
|
1330
|
+
dataset.sourceLabel ? [[dataset.sourceLabel, dataset]] : [],
|
|
1331
|
+
),
|
|
1332
|
+
);
|
|
1333
|
+
for (const rawPath of input.metadata.listExtractorPaths ?? []) {
|
|
1334
|
+
const candidates = [...candidateResultPaths(rawPath)].filter(
|
|
1335
|
+
(candidate, index, all) => all.indexOf(candidate) === index,
|
|
1336
|
+
);
|
|
1337
|
+
const dataset =
|
|
1338
|
+
candidates
|
|
1339
|
+
.map((candidate) => datasetsBySource.get(candidate))
|
|
1340
|
+
.find(Boolean) ?? Object.values(input.listDatasets)[0];
|
|
1341
|
+
if (!dataset) continue;
|
|
1342
|
+
for (const candidate of candidates) {
|
|
1343
|
+
if (!Array.isArray(getAtPath(root, candidate))) continue;
|
|
1344
|
+
root = replaceAtPath(root, candidate, dataset.preview);
|
|
1345
|
+
break;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return isRecord(root) &&
|
|
1349
|
+
isRecord(root.toolResponse) &&
|
|
1350
|
+
Object.prototype.hasOwnProperty.call(root.toolResponse, 'raw')
|
|
1351
|
+
? root.toolResponse.raw
|
|
1352
|
+
: input.raw;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function createDatasetFromSerializedToolList(
|
|
1356
|
+
serialized: SerializedPlayDataset<Record<string, unknown>>,
|
|
1357
|
+
rows?: Array<Record<string, unknown>>,
|
|
1358
|
+
): PlayDataset<Record<string, unknown>> {
|
|
1359
|
+
const preview = serialized.preview;
|
|
1360
|
+
const sourceRows = rows ?? preview;
|
|
1361
|
+
const isPartialPreview =
|
|
1362
|
+
rows === undefined && serialized.count > preview.length;
|
|
1363
|
+
const partialPreviewError = (): Error =>
|
|
1364
|
+
new Error(
|
|
1365
|
+
`Serialized tool list ${serialized.datasetId} only carries ${preview.length} preview row(s) for ${serialized.count} total row(s). ` +
|
|
1366
|
+
'Return the Dataset Handle from play code or export rows through runs export for full data.',
|
|
1367
|
+
);
|
|
1368
|
+
const collectPreview = (limit?: number): Record<string, unknown>[] => {
|
|
1369
|
+
if (limit === undefined && isPartialPreview) {
|
|
1370
|
+
throw partialPreviewError();
|
|
1371
|
+
}
|
|
1372
|
+
const requested =
|
|
1373
|
+
limit === undefined ? sourceRows.length : Math.max(0, Math.floor(limit));
|
|
1374
|
+
if (requested > preview.length && isPartialPreview) {
|
|
1375
|
+
throw partialPreviewError();
|
|
1376
|
+
}
|
|
1377
|
+
return sourceRows.slice(0, requested);
|
|
1378
|
+
};
|
|
1379
|
+
return createDeferredPlayDataset({
|
|
1380
|
+
datasetKind: serialized.datasetKind,
|
|
1381
|
+
datasetId: serialized.datasetId,
|
|
1382
|
+
count: serialized.count,
|
|
1383
|
+
backing: serialized.backing,
|
|
1384
|
+
previewRows: preview,
|
|
1385
|
+
sourceLabel: serialized.sourceLabel ?? null,
|
|
1386
|
+
tableNamespace: serialized.tableNamespace ?? null,
|
|
1387
|
+
workProgress: serialized._metadata?.workProgress,
|
|
1388
|
+
resolvers: {
|
|
1389
|
+
count: async () => serialized.count,
|
|
1390
|
+
peek: async (limit) => collectPreview(limit),
|
|
1391
|
+
materialize: async (limit) => collectPreview(limit),
|
|
1392
|
+
iterate: () =>
|
|
1393
|
+
({
|
|
1394
|
+
async *[Symbol.asyncIterator]() {
|
|
1395
|
+
if (isPartialPreview) {
|
|
1396
|
+
throw partialPreviewError();
|
|
1397
|
+
}
|
|
1398
|
+
yield* sourceRows;
|
|
1399
|
+
},
|
|
1400
|
+
}) as AsyncIterable<Record<string, unknown>>,
|
|
1401
|
+
},
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function applySerializedListDatasets(
|
|
1406
|
+
result: ToolExecuteResult,
|
|
1407
|
+
listDatasets:
|
|
1408
|
+
| Record<string, SerializedPlayDataset<Record<string, unknown>>>
|
|
1409
|
+
| undefined,
|
|
1410
|
+
listRows: Record<string, Array<Record<string, unknown>>> | undefined,
|
|
1411
|
+
): ToolExecuteResult {
|
|
1412
|
+
if (!listDatasets) return result;
|
|
1413
|
+
if (listRows && Object.keys(listRows).length > 0) {
|
|
1414
|
+
Object.defineProperty(result, SERIALIZED_TOOL_LIST_ROWS, {
|
|
1415
|
+
value: listRows,
|
|
1416
|
+
enumerable: false,
|
|
1417
|
+
configurable: false,
|
|
1418
|
+
writable: false,
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
for (const [name, serialized] of Object.entries(listDatasets)) {
|
|
1422
|
+
if (!isSerializedPlayDataset<Record<string, unknown>>(serialized)) {
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
const dataset = createDatasetFromSerializedToolList(
|
|
1426
|
+
serialized,
|
|
1427
|
+
listRows?.[name],
|
|
1428
|
+
);
|
|
1429
|
+
const existingMetadata = result._metadata.lists[name];
|
|
1430
|
+
result._metadata.lists[name] = {
|
|
1431
|
+
path: serialized.sourceLabel ?? existingMetadata?.path ?? name,
|
|
1432
|
+
count: serialized.count,
|
|
1433
|
+
keys: existingMetadata?.keys ?? {},
|
|
1434
|
+
};
|
|
1435
|
+
const accessor = {
|
|
1436
|
+
path: result._metadata.lists[name].path,
|
|
1437
|
+
count: serialized.count,
|
|
1438
|
+
keys: result._metadata.lists[name].keys,
|
|
1439
|
+
} as ToolResultListAccessor;
|
|
1440
|
+
Object.defineProperty(accessor, 'get', {
|
|
1441
|
+
value() {
|
|
1442
|
+
return dataset;
|
|
1443
|
+
},
|
|
1444
|
+
enumerable: false,
|
|
1445
|
+
});
|
|
1446
|
+
result.extractedLists[name] = accessor;
|
|
1447
|
+
}
|
|
1448
|
+
return result;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1152
1451
|
function metadataInputFromToolExecuteResult(
|
|
1153
1452
|
value: ToolExecuteResult,
|
|
1154
1453
|
): ToolResultMetadataInput {
|
|
@@ -1177,14 +1476,31 @@ function metadataInputFromToolExecuteResult(
|
|
|
1177
1476
|
export function serializeToolExecuteResult(
|
|
1178
1477
|
value: ToolExecuteResult,
|
|
1179
1478
|
): SerializedToolExecuteResult {
|
|
1479
|
+
const metadata = metadataInputFromToolExecuteResult(value);
|
|
1480
|
+
const listDatasets = serializedListDatasetsFromResult(value);
|
|
1481
|
+
const listRows = serializedListRowsFromResult({
|
|
1482
|
+
value,
|
|
1483
|
+
listDatasets,
|
|
1484
|
+
metadata,
|
|
1485
|
+
});
|
|
1486
|
+
const targetValues = serializedTargetValuesFromResult(value);
|
|
1180
1487
|
return {
|
|
1181
1488
|
__kind: SERIALIZED_TOOL_EXECUTE_RESULT_KIND,
|
|
1182
1489
|
status: value.status,
|
|
1490
|
+
...(typeof value.job_id === 'string' ? { job_id: value.job_id } : {}),
|
|
1491
|
+
...(isRecord(value.meta) ? { meta: value.meta } : {}),
|
|
1183
1492
|
toolResponse: {
|
|
1184
|
-
raw:
|
|
1493
|
+
raw: serializedRawWithListPreviews({
|
|
1494
|
+
raw: value.toolResponse.raw,
|
|
1495
|
+
metadata,
|
|
1496
|
+
listDatasets,
|
|
1497
|
+
}),
|
|
1185
1498
|
...(value.toolResponse.meta ? { meta: value.toolResponse.meta } : {}),
|
|
1186
1499
|
},
|
|
1187
|
-
|
|
1500
|
+
...(listDatasets ? { listDatasets } : {}),
|
|
1501
|
+
...(listRows ? { listRows } : {}),
|
|
1502
|
+
...(targetValues ? { targetValues } : {}),
|
|
1503
|
+
metadata,
|
|
1188
1504
|
execution: value._metadata.execution,
|
|
1189
1505
|
};
|
|
1190
1506
|
}
|
|
@@ -1205,33 +1521,34 @@ export function isSerializedToolExecuteResult(
|
|
|
1205
1521
|
export function deserializeToolExecuteResult(
|
|
1206
1522
|
value: SerializedToolExecuteResult,
|
|
1207
1523
|
): ToolExecuteResult {
|
|
1208
|
-
return
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1524
|
+
return applySerializedListDatasets(
|
|
1525
|
+
applySerializedTargetValues(
|
|
1526
|
+
createToolExecuteResult({
|
|
1527
|
+
status: value.status,
|
|
1528
|
+
jobId: value.job_id,
|
|
1529
|
+
result: {
|
|
1530
|
+
data: value.toolResponse.raw,
|
|
1531
|
+
...(value.toolResponse.meta
|
|
1532
|
+
? { meta: value.toolResponse.meta }
|
|
1533
|
+
: {}),
|
|
1534
|
+
},
|
|
1535
|
+
metadata: value.metadata,
|
|
1536
|
+
execution: value.execution,
|
|
1537
|
+
meta: value.meta,
|
|
1538
|
+
}),
|
|
1539
|
+
value.targetValues,
|
|
1540
|
+
),
|
|
1541
|
+
value.listDatasets,
|
|
1542
|
+
value.listRows,
|
|
1543
|
+
);
|
|
1217
1544
|
}
|
|
1218
1545
|
|
|
1219
1546
|
export function cloneToolExecuteResultWithExecution<TResult>(
|
|
1220
1547
|
value: ToolExecuteResult<TResult>,
|
|
1221
1548
|
execution: ToolResultExecutionMetadata,
|
|
1222
1549
|
): ToolExecuteResult<TResult> {
|
|
1223
|
-
return
|
|
1224
|
-
|
|
1225
|
-
jobId:
|
|
1226
|
-
typeof (value as unknown as { job_id?: unknown }).job_id === 'string'
|
|
1227
|
-
? (value as unknown as { job_id: string }).job_id
|
|
1228
|
-
: undefined,
|
|
1229
|
-
result: {
|
|
1230
|
-
data: value.toolResponse.raw,
|
|
1231
|
-
...(value.toolResponse.meta ? { meta: value.toolResponse.meta } : {}),
|
|
1232
|
-
} as TResult,
|
|
1233
|
-
metadata: metadataInputFromToolExecuteResult(value),
|
|
1550
|
+
return deserializeToolExecuteResult({
|
|
1551
|
+
...serializeToolExecuteResult(value),
|
|
1234
1552
|
execution,
|
|
1235
|
-
|
|
1236
|
-
});
|
|
1553
|
+
}) as ToolExecuteResult<TResult>;
|
|
1237
1554
|
}
|