@mondaydotcomorg/atp-compiler 0.19.23 → 0.19.26
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/index.cjs +177 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +177 -50
- package/dist/index.js.map +1 -1
- package/dist/transformer/array-transformer-batch-reconstruct.d.ts +3 -2
- package/dist/transformer/array-transformer-batch-reconstruct.d.ts.map +1 -1
- package/dist/transformer/array-transformer-batch-reconstruct.js +61 -41
- package/dist/transformer/array-transformer-batch-reconstruct.js.map +1 -1
- package/dist/transformer/array-transformer-utils.d.ts +29 -1
- package/dist/transformer/array-transformer-utils.d.ts.map +1 -1
- package/dist/transformer/array-transformer-utils.js +159 -8
- package/dist/transformer/array-transformer-utils.js.map +1 -1
- package/dist/transformer/array-transformer.d.ts.map +1 -1
- package/dist/transformer/array-transformer.js +15 -4
- package/dist/transformer/array-transformer.js.map +1 -1
- package/package.json +5 -5
- package/src/transformer/array-transformer-batch-reconstruct.ts +94 -70
- package/src/transformer/array-transformer-utils.ts +187 -7
- package/src/transformer/array-transformer.ts +20 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -3,14 +3,15 @@ import _traverse from '@babel/traverse';
|
|
|
3
3
|
const traverse = typeof (_traverse as any).default === 'function' ? (_traverse as any).default : _traverse;
|
|
4
4
|
import { generateUniqueId } from '../runtime/context.js';
|
|
5
5
|
import { BatchParallelDetector } from './batch-detector.js';
|
|
6
|
-
import {
|
|
6
|
+
import { findAllLLMCallExpressions } from './array-transformer-utils.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Transform array method to batch LLM calls while preserving callback logic.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Supports multiple LLM calls per callback.
|
|
11
|
+
*
|
|
12
|
+
* This creates a multi-step transformation:
|
|
13
|
+
* 1. Batch all LLM calls in parallel (one batch per unique type:operation)
|
|
14
|
+
* 2. Reconstruct objects using the batched results
|
|
14
15
|
*/
|
|
15
16
|
export function transformToBatchWithReconstruction(
|
|
16
17
|
path: any,
|
|
@@ -28,74 +29,117 @@ export function transformToBatchWithReconstruction(
|
|
|
28
29
|
if (!t.isIdentifier(paramName)) {
|
|
29
30
|
return false;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
32
|
const param = paramName.name;
|
|
33
33
|
const array = (node.callee as t.MemberExpression).object;
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// Find ALL LLM calls
|
|
36
|
+
const llmCalls = findAllLLMCallExpressions(callback.body, batchDetector);
|
|
37
|
+
if (llmCalls.length === 0) {
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
-
if (!callInfo) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
41
|
+
const methodId = generateUniqueId(`${methodName}_batch_reconstruct`);
|
|
44
42
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
const originalIndexParam = callback.params[1];
|
|
44
|
+
const indexVar =
|
|
45
|
+
originalIndexParam && t.isIdentifier(originalIndexParam) ? originalIndexParam.name : '__idx';
|
|
46
|
+
|
|
47
|
+
// Create batch declarations - one per LLM call (in order of appearance)
|
|
48
|
+
// This ensures each call gets its own result array
|
|
49
|
+
const batchDeclarations: t.Statement[] = [];
|
|
50
|
+
const resultVarByCallIndex = new Map<number, string>();
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < llmCalls.length; i++) {
|
|
53
|
+
const call = llmCalls[i]!;
|
|
54
|
+
const resultsVar = `__batch_results_${i}_${methodId.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
55
|
+
resultVarByCallIndex.set(i, resultsVar);
|
|
56
|
+
|
|
57
|
+
const payloadMapper = t.arrowFunctionExpression(
|
|
58
|
+
[t.identifier(param)],
|
|
59
|
+
t.objectExpression([
|
|
60
|
+
t.objectProperty(t.identifier('type'), t.stringLiteral(call.callInfo.type)),
|
|
61
|
+
t.objectProperty(
|
|
62
|
+
t.identifier('operation'),
|
|
63
|
+
t.stringLiteral(call.callInfo.operation)
|
|
64
|
+
),
|
|
65
|
+
t.objectProperty(t.identifier('payload'), t.cloneNode(call.payloadNode, true)),
|
|
66
|
+
])
|
|
67
|
+
);
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
const batchCall = t.awaitExpression(
|
|
70
|
+
t.callExpression(
|
|
71
|
+
t.memberExpression(t.identifier('__runtime'), t.identifier('batchParallel')),
|
|
72
|
+
[
|
|
73
|
+
t.callExpression(
|
|
74
|
+
t.memberExpression(t.cloneNode(array, true), t.identifier('map')),
|
|
75
|
+
[payloadMapper]
|
|
76
|
+
),
|
|
77
|
+
t.stringLiteral(`${methodId}_${i}`),
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
);
|
|
62
81
|
|
|
82
|
+
batchDeclarations.push(
|
|
83
|
+
t.variableDeclaration('const', [
|
|
84
|
+
t.variableDeclarator(t.identifier(resultsVar), batchCall),
|
|
85
|
+
])
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Clone the callback body for reconstruction
|
|
63
90
|
const clonedBody = t.cloneNode(callback.body, true);
|
|
64
|
-
|
|
65
|
-
const resultAccess = t.memberExpression(
|
|
66
|
-
t.identifier(resultsVar),
|
|
67
|
-
t.identifier(indexVar),
|
|
68
|
-
true
|
|
69
|
-
);
|
|
70
91
|
|
|
71
92
|
let traversableNode: t.Statement;
|
|
72
93
|
if (t.isBlockStatement(clonedBody)) {
|
|
73
|
-
traversableNode = t.functionDeclaration(
|
|
74
|
-
t.identifier('__temp'),
|
|
75
|
-
[],
|
|
76
|
-
clonedBody
|
|
77
|
-
);
|
|
94
|
+
traversableNode = t.functionDeclaration(t.identifier('__temp'), [], clonedBody);
|
|
78
95
|
} else {
|
|
79
96
|
traversableNode = t.expressionStatement(clonedBody as t.Expression);
|
|
80
97
|
}
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
// Replace each await expression with the corresponding result access
|
|
100
|
+
// We match calls by comparing their structure to the original calls
|
|
101
|
+
let replacementCount = 0;
|
|
102
|
+
|
|
83
103
|
traverse(t.file(t.program([traversableNode])), {
|
|
84
104
|
AwaitExpression(awaitPath: any) {
|
|
85
|
-
if (replaced) return;
|
|
86
105
|
const arg = awaitPath.node.argument;
|
|
87
|
-
if (t.isCallExpression(arg))
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
if (!t.isCallExpression(arg)) return;
|
|
107
|
+
|
|
108
|
+
const info = batchDetector.extractCallInfo(arg);
|
|
109
|
+
if (!info) return;
|
|
110
|
+
|
|
111
|
+
const key = `${info.type}:${info.operation}`;
|
|
112
|
+
|
|
113
|
+
// Find first unused call with matching key
|
|
114
|
+
let matchedIndex = -1;
|
|
115
|
+
for (let i = 0; i < llmCalls.length; i++) {
|
|
116
|
+
const original = llmCalls[i]!;
|
|
117
|
+
if (original.key === key && resultVarByCallIndex.has(i)) {
|
|
118
|
+
matchedIndex = i;
|
|
119
|
+
break;
|
|
92
120
|
}
|
|
93
121
|
}
|
|
122
|
+
|
|
123
|
+
if (matchedIndex === -1) return;
|
|
124
|
+
|
|
125
|
+
const resultsVar = resultVarByCallIndex.get(matchedIndex);
|
|
126
|
+
if (!resultsVar) return;
|
|
127
|
+
// Remove from map so we don't reuse it
|
|
128
|
+
resultVarByCallIndex.delete(matchedIndex);
|
|
129
|
+
|
|
130
|
+
const resultAccess = t.memberExpression(
|
|
131
|
+
t.identifier(resultsVar),
|
|
132
|
+
t.identifier(indexVar),
|
|
133
|
+
true
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
awaitPath.replaceWith(resultAccess);
|
|
137
|
+
replacementCount++;
|
|
94
138
|
},
|
|
95
139
|
noScope: true,
|
|
96
140
|
});
|
|
97
141
|
|
|
98
|
-
if (
|
|
142
|
+
if (replacementCount === 0) {
|
|
99
143
|
return false;
|
|
100
144
|
}
|
|
101
145
|
|
|
@@ -112,36 +156,17 @@ export function transformToBatchWithReconstruction(
|
|
|
112
156
|
);
|
|
113
157
|
reconstructMapper.async = false;
|
|
114
158
|
|
|
115
|
-
const batchCall = t.awaitExpression(
|
|
116
|
-
t.callExpression(
|
|
117
|
-
t.memberExpression(t.identifier('__runtime'), t.identifier('batchParallel')),
|
|
118
|
-
[
|
|
119
|
-
t.callExpression(
|
|
120
|
-
t.memberExpression(t.cloneNode(array, true), t.identifier('map')),
|
|
121
|
-
[payloadMapper]
|
|
122
|
-
),
|
|
123
|
-
t.stringLiteral(methodId),
|
|
124
|
-
]
|
|
125
|
-
)
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const resultsDeclaration = t.variableDeclaration('const', [
|
|
129
|
-
t.variableDeclarator(t.identifier(resultsVar), batchCall),
|
|
130
|
-
]);
|
|
131
|
-
|
|
132
159
|
const reconstructCall = t.callExpression(
|
|
133
160
|
t.memberExpression(t.cloneNode(array, true), t.identifier('map')),
|
|
134
161
|
[reconstructMapper]
|
|
135
162
|
);
|
|
136
163
|
|
|
164
|
+
// Build the IIFE with all batch declarations followed by reconstruction
|
|
137
165
|
const iife = t.callExpression(
|
|
138
166
|
t.arrowFunctionExpression(
|
|
139
167
|
[],
|
|
140
|
-
t.blockStatement([
|
|
141
|
-
|
|
142
|
-
t.returnStatement(reconstructCall),
|
|
143
|
-
]),
|
|
144
|
-
true
|
|
168
|
+
t.blockStatement([...batchDeclarations, t.returnStatement(reconstructCall)]),
|
|
169
|
+
true // async
|
|
145
170
|
),
|
|
146
171
|
[]
|
|
147
172
|
);
|
|
@@ -152,4 +177,3 @@ export function transformToBatchWithReconstruction(
|
|
|
152
177
|
onTransform();
|
|
153
178
|
return true;
|
|
154
179
|
}
|
|
155
|
-
|
|
@@ -1,23 +1,168 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
2
|
import { isArrayMethod } from './utils.js';
|
|
3
|
+
import type { BatchParallelDetector } from './batch-detector.js';
|
|
4
|
+
import type { BatchCallInfo } from '../types.js';
|
|
5
|
+
|
|
6
|
+
export interface LLMCallInfo {
|
|
7
|
+
callNode: t.CallExpression;
|
|
8
|
+
callInfo: BatchCallInfo;
|
|
9
|
+
payloadNode: t.Expression;
|
|
10
|
+
// Unique key for grouping: "type:operation"
|
|
11
|
+
key: string;
|
|
12
|
+
}
|
|
3
13
|
|
|
4
14
|
/**
|
|
5
|
-
*
|
|
15
|
+
* Collect all identifiers referenced in an expression
|
|
6
16
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
function collectReferencedIdentifiers(node: t.Node): Set<string> {
|
|
18
|
+
const identifiers = new Set<string>();
|
|
19
|
+
|
|
20
|
+
const visit = (n: t.Node) => {
|
|
21
|
+
if (t.isIdentifier(n)) {
|
|
22
|
+
identifiers.add(n.name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Continue traversing
|
|
26
|
+
Object.keys(n).forEach((key) => {
|
|
27
|
+
// Skip 'type' and other metadata fields
|
|
28
|
+
if (key === 'type' || key === 'loc' || key === 'start' || key === 'end') return;
|
|
29
|
+
const value = (n as any)[key];
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
value.forEach((item) => {
|
|
32
|
+
if (item && typeof item === 'object' && item.type) {
|
|
33
|
+
visit(item);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
} else if (value && typeof value === 'object' && value.type) {
|
|
37
|
+
visit(value);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
visit(node);
|
|
43
|
+
return identifiers;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Collect all variable names declared inside a callback body.
|
|
48
|
+
* This includes: const/let/var declarations, function parameters, etc.
|
|
49
|
+
*/
|
|
50
|
+
function collectLocalVariables(body: t.Node): Set<string> {
|
|
51
|
+
const locals = new Set<string>();
|
|
9
52
|
|
|
10
53
|
const visit = (node: t.Node) => {
|
|
11
|
-
|
|
54
|
+
// Variable declarations: const x = ..., let y = ..., var z = ...
|
|
55
|
+
if (t.isVariableDeclaration(node)) {
|
|
56
|
+
for (const decl of node.declarations) {
|
|
57
|
+
if (t.isIdentifier(decl.id)) {
|
|
58
|
+
locals.add(decl.id.name);
|
|
59
|
+
} else if (t.isObjectPattern(decl.id)) {
|
|
60
|
+
// Destructuring: const { a, b } = ...
|
|
61
|
+
for (const prop of decl.id.properties) {
|
|
62
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
|
|
63
|
+
locals.add(prop.value.name);
|
|
64
|
+
} else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
|
|
65
|
+
locals.add(prop.argument.name);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else if (t.isArrayPattern(decl.id)) {
|
|
69
|
+
// Destructuring: const [a, b] = ...
|
|
70
|
+
for (const elem of decl.id.elements) {
|
|
71
|
+
if (t.isIdentifier(elem)) {
|
|
72
|
+
locals.add(elem.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
12
78
|
|
|
79
|
+
// Continue traversing (but don't descend into nested functions - their locals are their own scope)
|
|
80
|
+
if (t.isFunction(node)) {
|
|
81
|
+
return; // Don't traverse into nested functions
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
Object.keys(node).forEach((key) => {
|
|
85
|
+
if (key === 'type' || key === 'loc' || key === 'start' || key === 'end') return;
|
|
86
|
+
const value = (node as any)[key];
|
|
87
|
+
if (Array.isArray(value)) {
|
|
88
|
+
value.forEach((item) => {
|
|
89
|
+
if (item && typeof item === 'object' && item.type) {
|
|
90
|
+
visit(item);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} else if (value && typeof value === 'object' && value.type) {
|
|
94
|
+
visit(value);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
visit(body);
|
|
100
|
+
return locals;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if LLM call payloads depend on variables that are local to the callback.
|
|
105
|
+
* This includes:
|
|
106
|
+
* - Variables computed from other expressions (const x = item.name.toUpperCase())
|
|
107
|
+
* - Results from previous LLM calls (const title = await atp.llm.call(...))
|
|
108
|
+
* - Any other locally-defined variable
|
|
109
|
+
*
|
|
110
|
+
* When batch transforming, the payload mapper only has access to the array item parameter
|
|
111
|
+
* and outer scope variables - NOT local variables defined inside the callback.
|
|
112
|
+
*
|
|
113
|
+
* @param body - The callback body
|
|
114
|
+
* @param itemParamName - The name of the array item parameter (e.g., "item")
|
|
115
|
+
* @param batchDetector - The batch detector for extracting LLM call info
|
|
116
|
+
* @returns true if there are dependencies that prevent batch transformation
|
|
117
|
+
*/
|
|
118
|
+
export function hasLLMCallDependencies(
|
|
119
|
+
body: t.Node,
|
|
120
|
+
batchDetector: BatchParallelDetector,
|
|
121
|
+
itemParamName?: string
|
|
122
|
+
): boolean {
|
|
123
|
+
// Find all locally-defined variables in the callback body
|
|
124
|
+
const localVariables = collectLocalVariables(body);
|
|
125
|
+
|
|
126
|
+
// If no local variables, no dependencies possible
|
|
127
|
+
if (localVariables.size === 0) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Now check if any LLM call payload references these local variables
|
|
132
|
+
const allCalls = findAllAwaitedMemberCalls(body);
|
|
133
|
+
|
|
134
|
+
for (const call of allCalls) {
|
|
135
|
+
const payloadNode = batchDetector.extractPayloadNode(call);
|
|
136
|
+
if (payloadNode) {
|
|
137
|
+
const referencedIds = collectReferencedIdentifiers(payloadNode);
|
|
138
|
+
// Check if any referenced identifier is a local variable
|
|
139
|
+
// (excluding the item parameter which is passed to the payload mapper)
|
|
140
|
+
for (const id of referencedIds) {
|
|
141
|
+
if (localVariables.has(id) && id !== itemParamName) {
|
|
142
|
+
return true; // Found a dependency on a local variable
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Find all awaited member expression calls in AST node
|
|
153
|
+
*/
|
|
154
|
+
function findAllAwaitedMemberCalls(body: t.Node): t.CallExpression[] {
|
|
155
|
+
const calls: t.CallExpression[] = [];
|
|
156
|
+
|
|
157
|
+
const visit = (node: t.Node) => {
|
|
13
158
|
if (t.isAwaitExpression(node) && t.isCallExpression(node.argument)) {
|
|
14
159
|
const call = node.argument;
|
|
15
160
|
if (t.isMemberExpression(call.callee)) {
|
|
16
|
-
|
|
17
|
-
return;
|
|
161
|
+
calls.push(call);
|
|
18
162
|
}
|
|
19
163
|
}
|
|
20
164
|
|
|
165
|
+
// Continue traversing
|
|
21
166
|
Object.keys(node).forEach((key) => {
|
|
22
167
|
const value = (node as any)[key];
|
|
23
168
|
if (Array.isArray(value)) {
|
|
@@ -33,7 +178,42 @@ export function findLLMCallExpression(body: t.Node): t.CallExpression | null {
|
|
|
33
178
|
};
|
|
34
179
|
|
|
35
180
|
visit(body);
|
|
36
|
-
return
|
|
181
|
+
return calls;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Find ALL LLM call expressions in AST node with batch info
|
|
186
|
+
*/
|
|
187
|
+
export function findAllLLMCallExpressions(
|
|
188
|
+
body: t.Node,
|
|
189
|
+
batchDetector: BatchParallelDetector
|
|
190
|
+
): LLMCallInfo[] {
|
|
191
|
+
const allCalls = findAllAwaitedMemberCalls(body);
|
|
192
|
+
const llmCalls: LLMCallInfo[] = [];
|
|
193
|
+
|
|
194
|
+
for (const call of allCalls) {
|
|
195
|
+
const callInfo = batchDetector.extractCallInfo(call);
|
|
196
|
+
const payloadNode = batchDetector.extractPayloadNode(call);
|
|
197
|
+
|
|
198
|
+
if (callInfo && payloadNode) {
|
|
199
|
+
llmCalls.push({
|
|
200
|
+
callNode: call,
|
|
201
|
+
callInfo,
|
|
202
|
+
payloadNode,
|
|
203
|
+
key: `${callInfo.type}:${callInfo.operation}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return llmCalls;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Find first LLM call expression in AST node
|
|
213
|
+
*/
|
|
214
|
+
export function findLLMCallExpression(body: t.Node): t.CallExpression | null {
|
|
215
|
+
const calls = findAllAwaitedMemberCalls(body);
|
|
216
|
+
return calls[0] ?? null;
|
|
37
217
|
}
|
|
38
218
|
|
|
39
219
|
/**
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getArrayMethodName,
|
|
6
6
|
canUseBatchParallel,
|
|
7
7
|
findLLMCallExpression,
|
|
8
|
+
hasLLMCallDependencies,
|
|
8
9
|
} from './array-transformer-utils.js';
|
|
9
10
|
import { transformToBatchParallel } from './array-transformer-batch.js';
|
|
10
11
|
import { transformToSequential } from './array-transformer-sequential.js';
|
|
@@ -36,15 +37,29 @@ export class ArrayTransformer {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const batchResult = this.batchOptimizer.canBatchArrayMethod(callback);
|
|
39
|
-
|
|
40
40
|
if (!batchResult.canBatch && methodName === 'map') {
|
|
41
41
|
const reason = batchResult.reason || '';
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Try batch-with-reconstruction for:
|
|
43
|
+
// 1. Object/array returns (would lose structure with simple batch)
|
|
44
|
+
// 2. Multiple pausable calls (can batch each call type separately)
|
|
45
|
+
const canTryBatchReconstruct =
|
|
46
|
+
reason.includes('object expression') ||
|
|
47
|
+
reason.includes('array expression') ||
|
|
48
|
+
reason.includes('Multiple pausable calls');
|
|
44
49
|
|
|
45
|
-
if (
|
|
50
|
+
if (canTryBatchReconstruct) {
|
|
46
51
|
const llmCall = findLLMCallExpression(callback.body);
|
|
47
|
-
|
|
52
|
+
// Get the item parameter name (e.g., "item" in items.map(async (item) => ...))
|
|
53
|
+
const itemParam = callback.params[0];
|
|
54
|
+
const itemParamName = t.isIdentifier(itemParam) ? itemParam.name : undefined;
|
|
55
|
+
// Check for dependencies on local variables (e.g., computed values, previous LLM results)
|
|
56
|
+
// If dependencies exist, we can't batch because payload mapper only has access to item + outer scope
|
|
57
|
+
const hasDependencies = hasLLMCallDependencies(
|
|
58
|
+
callback.body,
|
|
59
|
+
this.batchDetector,
|
|
60
|
+
itemParamName
|
|
61
|
+
);
|
|
62
|
+
if (llmCall && !hasDependencies) {
|
|
48
63
|
const success = transformToBatchWithReconstruction(
|
|
49
64
|
path,
|
|
50
65
|
node,
|