@animalabs/membrane 0.5.23 → 0.5.25
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/membrane.d.ts +37 -0
- package/dist/membrane.d.ts.map +1 -1
- package/dist/membrane.js +554 -0
- package/dist/membrane.js.map +1 -1
- package/dist/providers/mock.d.ts +8 -0
- package/dist/providers/mock.d.ts.map +1 -1
- package/dist/providers/mock.js +39 -2
- package/dist/providers/mock.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +7 -2
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +7 -2
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +7 -2
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/yielding-stream.d.ts +167 -0
- package/dist/types/yielding-stream.d.ts.map +1 -0
- package/dist/types/yielding-stream.js +34 -0
- package/dist/types/yielding-stream.js.map +1 -0
- package/dist/yielding-stream.d.ts +60 -0
- package/dist/yielding-stream.d.ts.map +1 -0
- package/dist/yielding-stream.js +204 -0
- package/dist/yielding-stream.js.map +1 -0
- package/package.json +1 -1
- package/src/membrane.ts +691 -1
- package/src/providers/mock.ts +47 -2
- package/src/providers/openai-compatible.ts +10 -4
- package/src/providers/openai.ts +10 -4
- package/src/providers/openrouter.ts +10 -4
- package/src/types/index.ts +23 -0
- package/src/types/yielding-stream.ts +228 -0
- package/src/yielding-stream.ts +271 -0
package/dist/membrane.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* A selective boundary that transforms what passes through.
|
|
5
5
|
*/
|
|
6
6
|
import type { NormalizedRequest, NormalizedResponse, AbortedResponse, ProviderAdapter, MembraneConfig, StreamOptions, CompleteOptions } from './types/index.js';
|
|
7
|
+
import type { YieldingStream, YieldingStreamOptions } from './types/yielding-stream.js';
|
|
7
8
|
export declare class Membrane {
|
|
8
9
|
private adapter;
|
|
9
10
|
private registry?;
|
|
@@ -97,5 +98,41 @@ export declare class Membrane {
|
|
|
97
98
|
* Build an AbortedResponse from current execution state
|
|
98
99
|
*/
|
|
99
100
|
private buildAbortedResponse;
|
|
101
|
+
/**
|
|
102
|
+
* Stream inference with yielding control for tool execution.
|
|
103
|
+
*
|
|
104
|
+
* Unlike `stream()` which uses callbacks for tool execution, this method
|
|
105
|
+
* returns an async iterator that yields control back to the caller when
|
|
106
|
+
* tool calls are detected. The caller provides results via `provideToolResults()`.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const stream = membrane.streamYielding(request, options);
|
|
111
|
+
*
|
|
112
|
+
* for await (const event of stream) {
|
|
113
|
+
* switch (event.type) {
|
|
114
|
+
* case 'tokens':
|
|
115
|
+
* process.stdout.write(event.content);
|
|
116
|
+
* break;
|
|
117
|
+
* case 'tool-calls':
|
|
118
|
+
* const results = await executeTools(event.calls);
|
|
119
|
+
* stream.provideToolResults(results);
|
|
120
|
+
* break;
|
|
121
|
+
* case 'complete':
|
|
122
|
+
* console.log('Done:', event.response);
|
|
123
|
+
* break;
|
|
124
|
+
* }
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
streamYielding(request: NormalizedRequest, options?: YieldingStreamOptions): YieldingStream;
|
|
129
|
+
/**
|
|
130
|
+
* Run XML-based tool execution with yielding stream.
|
|
131
|
+
*/
|
|
132
|
+
private runXmlToolsYielding;
|
|
133
|
+
/**
|
|
134
|
+
* Run native tool execution with yielding stream.
|
|
135
|
+
*/
|
|
136
|
+
private runNativeToolsYielding;
|
|
100
137
|
}
|
|
101
138
|
//# sourceMappingURL=membrane.d.ts.map
|
package/dist/membrane.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membrane.d.ts","sourceRoot":"","sources":["../src/membrane.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAEf,eAAe,EAEf,cAAc,EACd,aAAa,EACb,eAAe,EAYhB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"membrane.d.ts","sourceRoot":"","sources":["../src/membrane.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAEf,eAAe,EAEf,cAAc,EACd,aAAa,EACb,eAAe,EAYhB,MAAM,kBAAkB,CAAC;AAmB1B,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EAGtB,MAAM,4BAA4B,CAAC;AASpC,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,QAAQ,CAAC,CAAgB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAAmB;gBAGlC,OAAO,EAAE,eAAe,EACxB,MAAM,GAAE,cAAmB;IAc7B;;OAEG;IACG,QAAQ,CACZ,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,kBAAkB,CAAC;IAqE9B;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,kBAAkB,GAAG,eAAe,CAAC;IAWhD;;OAEG;IACH,OAAO,CAAC,eAAe;IAiBvB;;;;;;OAMG;YACW,kBAAkB;IA0ahC;;OAEG;YACW,qBAAqB;IA+NnC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAiF9B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqC5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;YAqDV,UAAU;IAQxB,OAAO,CAAC,wBAAwB;IAyChC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,kCAAkC;IAyD1C,OAAO,CAAC,iBAAiB;IA2HzB,OAAO,CAAC,kBAAkB;IA+E1B,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,sBAAsB;IAO9B,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA4B5B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,cAAc,CACZ,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,qBAA0B,GAClC,cAAc;IAWjB;;OAEG;YACW,mBAAmB;IAqajC;;OAEG;YACW,sBAAsB;CAkNrC"}
|
package/dist/membrane.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { DEFAULT_RETRY_CONFIG, MembraneError, classifyError, } from './types/index.js';
|
|
7
7
|
import { parseToolCalls, formatToolResults, parseAccumulatedIntoBlocks, hasImageInToolResults, formatToolResultsForSplitTurn, } from './utils/tool-parser.js';
|
|
8
8
|
import { AnthropicXmlFormatter } from './formatters/anthropic-xml.js';
|
|
9
|
+
import { YieldingStreamImpl } from './yielding-stream.js';
|
|
9
10
|
// ============================================================================
|
|
10
11
|
// Membrane Class
|
|
11
12
|
// ============================================================================
|
|
@@ -1126,5 +1127,558 @@ export class Membrane {
|
|
|
1126
1127
|
toolResults: toolResults.length > 0 ? toolResults : undefined,
|
|
1127
1128
|
};
|
|
1128
1129
|
}
|
|
1130
|
+
// ============================================================================
|
|
1131
|
+
// Yielding Stream API
|
|
1132
|
+
// ============================================================================
|
|
1133
|
+
/**
|
|
1134
|
+
* Stream inference with yielding control for tool execution.
|
|
1135
|
+
*
|
|
1136
|
+
* Unlike `stream()` which uses callbacks for tool execution, this method
|
|
1137
|
+
* returns an async iterator that yields control back to the caller when
|
|
1138
|
+
* tool calls are detected. The caller provides results via `provideToolResults()`.
|
|
1139
|
+
*
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```typescript
|
|
1142
|
+
* const stream = membrane.streamYielding(request, options);
|
|
1143
|
+
*
|
|
1144
|
+
* for await (const event of stream) {
|
|
1145
|
+
* switch (event.type) {
|
|
1146
|
+
* case 'tokens':
|
|
1147
|
+
* process.stdout.write(event.content);
|
|
1148
|
+
* break;
|
|
1149
|
+
* case 'tool-calls':
|
|
1150
|
+
* const results = await executeTools(event.calls);
|
|
1151
|
+
* stream.provideToolResults(results);
|
|
1152
|
+
* break;
|
|
1153
|
+
* case 'complete':
|
|
1154
|
+
* console.log('Done:', event.response);
|
|
1155
|
+
* break;
|
|
1156
|
+
* }
|
|
1157
|
+
* }
|
|
1158
|
+
* ```
|
|
1159
|
+
*/
|
|
1160
|
+
streamYielding(request, options = {}) {
|
|
1161
|
+
const toolMode = this.resolveToolMode(request);
|
|
1162
|
+
// Create the yielding stream with the appropriate inference runner
|
|
1163
|
+
const runInference = toolMode === 'native'
|
|
1164
|
+
? (stream) => this.runNativeToolsYielding(request, options, stream)
|
|
1165
|
+
: (stream) => this.runXmlToolsYielding(request, options, stream);
|
|
1166
|
+
return new YieldingStreamImpl(options, runInference);
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Run XML-based tool execution with yielding stream.
|
|
1170
|
+
*/
|
|
1171
|
+
async runXmlToolsYielding(request, options, stream) {
|
|
1172
|
+
const startTime = Date.now();
|
|
1173
|
+
const { maxToolDepth = 10, emitTokens = true, emitBlocks = true, emitUsage = true, } = options;
|
|
1174
|
+
// Initialize parser from formatter for format-specific tracking
|
|
1175
|
+
const formatter = this.formatter;
|
|
1176
|
+
const parser = formatter.createStreamParser();
|
|
1177
|
+
let toolDepth = 0;
|
|
1178
|
+
let totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1179
|
+
const contentBlocks = [];
|
|
1180
|
+
let lastStopReason = 'end_turn';
|
|
1181
|
+
let rawRequest;
|
|
1182
|
+
let rawResponse;
|
|
1183
|
+
// Track executed tool calls and results
|
|
1184
|
+
const executedToolCalls = [];
|
|
1185
|
+
const executedToolResults = [];
|
|
1186
|
+
// Transform initial request using the formatter
|
|
1187
|
+
let { providerRequest, prefillResult } = this.transformRequest(request, formatter);
|
|
1188
|
+
// Initialize parser with prefill content
|
|
1189
|
+
let initialPrefillLength = 0;
|
|
1190
|
+
let initialBlockType = null;
|
|
1191
|
+
if (prefillResult.assistantPrefill) {
|
|
1192
|
+
parser.push(prefillResult.assistantPrefill);
|
|
1193
|
+
initialPrefillLength = prefillResult.assistantPrefill.length;
|
|
1194
|
+
if (parser.isInsideBlock()) {
|
|
1195
|
+
const blockType = parser.getCurrentBlockType();
|
|
1196
|
+
if (blockType === 'thinking' || blockType === 'tool_call' || blockType === 'tool_result') {
|
|
1197
|
+
initialBlockType = blockType;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
// Tool execution loop
|
|
1203
|
+
while (toolDepth <= maxToolDepth) {
|
|
1204
|
+
// Check for cancellation
|
|
1205
|
+
if (stream.isCancelled) {
|
|
1206
|
+
const fullAccumulated = parser.getAccumulated();
|
|
1207
|
+
const newContent = fullAccumulated.slice(initialPrefillLength);
|
|
1208
|
+
stream.emit({
|
|
1209
|
+
type: 'aborted',
|
|
1210
|
+
reason: 'user',
|
|
1211
|
+
partialContent: parseAccumulatedIntoBlocks(newContent).blocks,
|
|
1212
|
+
rawAssistantText: newContent,
|
|
1213
|
+
toolCalls: executedToolCalls,
|
|
1214
|
+
toolResults: executedToolResults,
|
|
1215
|
+
});
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
// Track if we manually detected a stop sequence
|
|
1219
|
+
let detectedStopSequence = null;
|
|
1220
|
+
let truncatedAccumulated = null;
|
|
1221
|
+
const checkFromIndex = parser.getAccumulated().length;
|
|
1222
|
+
// Stream from provider
|
|
1223
|
+
const streamResult = await this.streamOnce(providerRequest, {
|
|
1224
|
+
onChunk: (chunk) => {
|
|
1225
|
+
if (detectedStopSequence || stream.isCancelled) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
// Process chunk with enriched streaming API
|
|
1229
|
+
const { emissions } = parser.processChunk(chunk);
|
|
1230
|
+
// Check for stop sequences only in NEW content
|
|
1231
|
+
const accumulated = parser.getAccumulated();
|
|
1232
|
+
const newContent = accumulated.slice(checkFromIndex);
|
|
1233
|
+
for (const stopSeq of prefillResult.stopSequences) {
|
|
1234
|
+
const idx = newContent.indexOf(stopSeq);
|
|
1235
|
+
if (idx !== -1) {
|
|
1236
|
+
const absoluteIdx = checkFromIndex + idx;
|
|
1237
|
+
detectedStopSequence = stopSeq;
|
|
1238
|
+
truncatedAccumulated = accumulated.slice(0, absoluteIdx);
|
|
1239
|
+
// Emit only the portion up to stop sequence
|
|
1240
|
+
const alreadyEmitted = accumulated.length - chunk.length;
|
|
1241
|
+
if (emitTokens && absoluteIdx > alreadyEmitted) {
|
|
1242
|
+
const truncatedChunk = accumulated.slice(alreadyEmitted, absoluteIdx);
|
|
1243
|
+
const meta = {
|
|
1244
|
+
type: parser.getCurrentBlockType(),
|
|
1245
|
+
visible: parser.getCurrentBlockType() === 'text',
|
|
1246
|
+
blockIndex: 0,
|
|
1247
|
+
};
|
|
1248
|
+
stream.emit({ type: 'tokens', content: truncatedChunk, meta });
|
|
1249
|
+
}
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
// Emit in correct interleaved order
|
|
1254
|
+
for (const emission of emissions) {
|
|
1255
|
+
if (emission.kind === 'blockEvent') {
|
|
1256
|
+
if (emitBlocks) {
|
|
1257
|
+
stream.emit({ type: 'block', event: emission.event });
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
if (emitTokens) {
|
|
1262
|
+
stream.emit({ type: 'tokens', content: emission.text, meta: emission.meta });
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
onContentBlock: undefined,
|
|
1268
|
+
}, {
|
|
1269
|
+
signal: stream.signal,
|
|
1270
|
+
timeoutMs: options.timeoutMs,
|
|
1271
|
+
onRequest: (req) => { rawRequest = req; },
|
|
1272
|
+
});
|
|
1273
|
+
// If we detected stop sequence manually, fix up the parser and result
|
|
1274
|
+
if (detectedStopSequence && truncatedAccumulated !== null) {
|
|
1275
|
+
parser.reset();
|
|
1276
|
+
parser.push(truncatedAccumulated);
|
|
1277
|
+
streamResult.stopReason = 'stop_sequence';
|
|
1278
|
+
streamResult.stopSequence = detectedStopSequence;
|
|
1279
|
+
}
|
|
1280
|
+
rawResponse = streamResult.raw;
|
|
1281
|
+
lastStopReason = this.mapStopReason(streamResult.stopReason);
|
|
1282
|
+
// Accumulate usage
|
|
1283
|
+
totalUsage.inputTokens += streamResult.usage.inputTokens;
|
|
1284
|
+
totalUsage.outputTokens += streamResult.usage.outputTokens;
|
|
1285
|
+
if (emitUsage) {
|
|
1286
|
+
stream.emit({ type: 'usage', usage: { ...totalUsage } });
|
|
1287
|
+
}
|
|
1288
|
+
// Flush the parser
|
|
1289
|
+
const flushResult = parser.flush();
|
|
1290
|
+
for (const emission of flushResult.emissions) {
|
|
1291
|
+
if (emission.kind === 'blockEvent' && emitBlocks) {
|
|
1292
|
+
stream.emit({ type: 'block', event: emission.event });
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
// Check for tool calls
|
|
1296
|
+
if (streamResult.stopSequence === '</function_calls>') {
|
|
1297
|
+
const closeTag = '</function_calls>';
|
|
1298
|
+
parser.push(closeTag);
|
|
1299
|
+
const parsed = parseToolCalls(parser.getAccumulated());
|
|
1300
|
+
if (parsed && parsed.calls.length > 0) {
|
|
1301
|
+
// Emit block events for each tool call
|
|
1302
|
+
if (emitBlocks) {
|
|
1303
|
+
for (const call of parsed.calls) {
|
|
1304
|
+
const toolCallBlockIndex = parser.getBlockIndex();
|
|
1305
|
+
stream.emit({
|
|
1306
|
+
type: 'block',
|
|
1307
|
+
event: {
|
|
1308
|
+
event: 'block_start',
|
|
1309
|
+
index: toolCallBlockIndex,
|
|
1310
|
+
block: { type: 'tool_call' },
|
|
1311
|
+
},
|
|
1312
|
+
});
|
|
1313
|
+
stream.emit({
|
|
1314
|
+
type: 'block',
|
|
1315
|
+
event: {
|
|
1316
|
+
event: 'block_complete',
|
|
1317
|
+
index: toolCallBlockIndex,
|
|
1318
|
+
block: {
|
|
1319
|
+
type: 'tool_call',
|
|
1320
|
+
toolId: call.id,
|
|
1321
|
+
toolName: call.name,
|
|
1322
|
+
input: call.input,
|
|
1323
|
+
},
|
|
1324
|
+
},
|
|
1325
|
+
});
|
|
1326
|
+
parser.incrementBlockIndex();
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
// Track the tool calls
|
|
1330
|
+
executedToolCalls.push(...parsed.calls);
|
|
1331
|
+
// Build tool context
|
|
1332
|
+
const context = {
|
|
1333
|
+
rawText: parsed.fullMatch,
|
|
1334
|
+
preamble: parsed.beforeText,
|
|
1335
|
+
depth: toolDepth,
|
|
1336
|
+
previousResults: executedToolResults,
|
|
1337
|
+
accumulated: parser.getAccumulated(),
|
|
1338
|
+
};
|
|
1339
|
+
// Yield control for tool execution
|
|
1340
|
+
const toolCallsEvent = {
|
|
1341
|
+
type: 'tool-calls',
|
|
1342
|
+
calls: parsed.calls,
|
|
1343
|
+
context,
|
|
1344
|
+
};
|
|
1345
|
+
const results = await stream.requestToolExecution(toolCallsEvent);
|
|
1346
|
+
// Track the tool results
|
|
1347
|
+
executedToolResults.push(...results);
|
|
1348
|
+
// Check if results contain images
|
|
1349
|
+
if (hasImageInToolResults(results)) {
|
|
1350
|
+
const splitContent = formatToolResultsForSplitTurn(results);
|
|
1351
|
+
// Emit block events for tool results
|
|
1352
|
+
if (emitBlocks) {
|
|
1353
|
+
stream.emit({
|
|
1354
|
+
type: 'block',
|
|
1355
|
+
event: {
|
|
1356
|
+
event: 'block_start',
|
|
1357
|
+
index: parser.getBlockIndex(),
|
|
1358
|
+
block: { type: 'tool_result' },
|
|
1359
|
+
},
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
parser.push(splitContent.beforeImageXml);
|
|
1363
|
+
// Emit tool result content
|
|
1364
|
+
for (const result of results) {
|
|
1365
|
+
const resultContent = typeof result.content === 'string'
|
|
1366
|
+
? result.content
|
|
1367
|
+
: JSON.stringify(result.content);
|
|
1368
|
+
if (emitTokens) {
|
|
1369
|
+
const toolResultMeta = {
|
|
1370
|
+
type: 'tool_result',
|
|
1371
|
+
visible: false,
|
|
1372
|
+
blockIndex: parser.getBlockIndex(),
|
|
1373
|
+
toolId: result.toolUseId,
|
|
1374
|
+
};
|
|
1375
|
+
stream.emit({ type: 'tokens', content: resultContent, meta: toolResultMeta });
|
|
1376
|
+
}
|
|
1377
|
+
if (emitBlocks) {
|
|
1378
|
+
stream.emit({
|
|
1379
|
+
type: 'block',
|
|
1380
|
+
event: {
|
|
1381
|
+
event: 'block_complete',
|
|
1382
|
+
index: parser.getBlockIndex(),
|
|
1383
|
+
block: {
|
|
1384
|
+
type: 'tool_result',
|
|
1385
|
+
toolId: result.toolUseId,
|
|
1386
|
+
content: resultContent,
|
|
1387
|
+
isError: result.isError,
|
|
1388
|
+
},
|
|
1389
|
+
},
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
parser.incrementBlockIndex();
|
|
1393
|
+
}
|
|
1394
|
+
let afterImageXml = splitContent.afterImageXml;
|
|
1395
|
+
if (request.config.thinking?.enabled) {
|
|
1396
|
+
afterImageXml += '\n<thinking>';
|
|
1397
|
+
}
|
|
1398
|
+
providerRequest = this.buildContinuationRequestWithImages(request, prefillResult, parser.getAccumulated(), splitContent.images, afterImageXml);
|
|
1399
|
+
parser.push(afterImageXml);
|
|
1400
|
+
prefillResult.assistantPrefill = parser.getAccumulated();
|
|
1401
|
+
parser.resetForNewIteration();
|
|
1402
|
+
}
|
|
1403
|
+
else {
|
|
1404
|
+
// Standard path: no images
|
|
1405
|
+
const resultsXml = formatToolResults(results);
|
|
1406
|
+
if (emitBlocks) {
|
|
1407
|
+
stream.emit({
|
|
1408
|
+
type: 'block',
|
|
1409
|
+
event: {
|
|
1410
|
+
event: 'block_start',
|
|
1411
|
+
index: parser.getBlockIndex(),
|
|
1412
|
+
block: { type: 'tool_result' },
|
|
1413
|
+
},
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
parser.push(resultsXml);
|
|
1417
|
+
for (const result of results) {
|
|
1418
|
+
const resultContent = typeof result.content === 'string'
|
|
1419
|
+
? result.content
|
|
1420
|
+
: JSON.stringify(result.content);
|
|
1421
|
+
if (emitTokens) {
|
|
1422
|
+
const toolResultMeta = {
|
|
1423
|
+
type: 'tool_result',
|
|
1424
|
+
visible: false,
|
|
1425
|
+
blockIndex: parser.getBlockIndex(),
|
|
1426
|
+
toolId: result.toolUseId,
|
|
1427
|
+
};
|
|
1428
|
+
stream.emit({ type: 'tokens', content: resultContent, meta: toolResultMeta });
|
|
1429
|
+
}
|
|
1430
|
+
if (emitBlocks) {
|
|
1431
|
+
stream.emit({
|
|
1432
|
+
type: 'block',
|
|
1433
|
+
event: {
|
|
1434
|
+
event: 'block_complete',
|
|
1435
|
+
index: parser.getBlockIndex(),
|
|
1436
|
+
block: {
|
|
1437
|
+
type: 'tool_result',
|
|
1438
|
+
toolId: result.toolUseId,
|
|
1439
|
+
content: resultContent,
|
|
1440
|
+
isError: result.isError,
|
|
1441
|
+
},
|
|
1442
|
+
},
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
parser.incrementBlockIndex();
|
|
1446
|
+
}
|
|
1447
|
+
if (request.config.thinking?.enabled) {
|
|
1448
|
+
parser.push('\n<thinking>');
|
|
1449
|
+
}
|
|
1450
|
+
prefillResult.assistantPrefill = parser.getAccumulated();
|
|
1451
|
+
providerRequest = this.buildContinuationRequest(request, prefillResult, parser.getAccumulated());
|
|
1452
|
+
}
|
|
1453
|
+
parser.resetForNewIteration();
|
|
1454
|
+
toolDepth++;
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
// Check for false-positive stop (unclosed block)
|
|
1459
|
+
if (lastStopReason === 'stop_sequence' && parser.isInsideBlock()) {
|
|
1460
|
+
if (streamResult.stopSequence) {
|
|
1461
|
+
parser.push(streamResult.stopSequence);
|
|
1462
|
+
if (emitTokens) {
|
|
1463
|
+
const meta = {
|
|
1464
|
+
type: parser.getCurrentBlockType(),
|
|
1465
|
+
visible: parser.getCurrentBlockType() === 'text',
|
|
1466
|
+
blockIndex: 0,
|
|
1467
|
+
};
|
|
1468
|
+
stream.emit({ type: 'tokens', content: streamResult.stopSequence, meta });
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
toolDepth++;
|
|
1472
|
+
if (toolDepth > maxToolDepth) {
|
|
1473
|
+
break;
|
|
1474
|
+
}
|
|
1475
|
+
prefillResult.assistantPrefill = parser.getAccumulated();
|
|
1476
|
+
providerRequest = this.buildContinuationRequest(request, prefillResult, parser.getAccumulated());
|
|
1477
|
+
parser.resetForNewIteration();
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
// No more tools, we're done
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
// Build final response
|
|
1484
|
+
const fullAccumulated = parser.getAccumulated();
|
|
1485
|
+
const newContent = fullAccumulated.slice(initialPrefillLength);
|
|
1486
|
+
const response = this.buildFinalResponse(newContent, contentBlocks, lastStopReason, totalUsage, request, prefillResult, startTime, 1, rawRequest, rawResponse, executedToolCalls, executedToolResults, initialBlockType);
|
|
1487
|
+
stream.emit({ type: 'complete', response });
|
|
1488
|
+
}
|
|
1489
|
+
catch (error) {
|
|
1490
|
+
if (this.isAbortError(error)) {
|
|
1491
|
+
const fullAccumulated = parser.getAccumulated();
|
|
1492
|
+
const newContent = fullAccumulated.slice(initialPrefillLength);
|
|
1493
|
+
stream.emit({
|
|
1494
|
+
type: 'aborted',
|
|
1495
|
+
reason: 'user',
|
|
1496
|
+
partialContent: parseAccumulatedIntoBlocks(newContent).blocks,
|
|
1497
|
+
rawAssistantText: newContent,
|
|
1498
|
+
toolCalls: executedToolCalls,
|
|
1499
|
+
toolResults: executedToolResults,
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
throw error;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Run native tool execution with yielding stream.
|
|
1509
|
+
*/
|
|
1510
|
+
async runNativeToolsYielding(request, options, stream) {
|
|
1511
|
+
const startTime = Date.now();
|
|
1512
|
+
const { maxToolDepth = 10, emitTokens = true, emitUsage = true, } = options;
|
|
1513
|
+
let toolDepth = 0;
|
|
1514
|
+
let totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
1515
|
+
let lastStopReason = 'end_turn';
|
|
1516
|
+
let rawRequest;
|
|
1517
|
+
let rawResponse;
|
|
1518
|
+
let allTextAccumulated = '';
|
|
1519
|
+
const executedToolCalls = [];
|
|
1520
|
+
const executedToolResults = [];
|
|
1521
|
+
let messages = [...request.messages];
|
|
1522
|
+
let allContentBlocks = [];
|
|
1523
|
+
try {
|
|
1524
|
+
// Tool execution loop
|
|
1525
|
+
while (toolDepth <= maxToolDepth) {
|
|
1526
|
+
// Check for cancellation
|
|
1527
|
+
if (stream.isCancelled) {
|
|
1528
|
+
stream.emit({
|
|
1529
|
+
type: 'aborted',
|
|
1530
|
+
reason: 'user',
|
|
1531
|
+
rawAssistantText: allTextAccumulated,
|
|
1532
|
+
toolCalls: executedToolCalls,
|
|
1533
|
+
toolResults: executedToolResults,
|
|
1534
|
+
});
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
// Build provider request with native tools
|
|
1538
|
+
const providerRequest = this.buildNativeToolRequest(request, messages);
|
|
1539
|
+
// Stream from provider
|
|
1540
|
+
let textAccumulated = '';
|
|
1541
|
+
let blockIndex = 0;
|
|
1542
|
+
const streamResult = await this.streamOnce(providerRequest, {
|
|
1543
|
+
onChunk: (chunk) => {
|
|
1544
|
+
if (stream.isCancelled)
|
|
1545
|
+
return;
|
|
1546
|
+
textAccumulated += chunk;
|
|
1547
|
+
allTextAccumulated += chunk;
|
|
1548
|
+
if (emitTokens) {
|
|
1549
|
+
const meta = {
|
|
1550
|
+
type: 'text',
|
|
1551
|
+
visible: true,
|
|
1552
|
+
blockIndex,
|
|
1553
|
+
};
|
|
1554
|
+
stream.emit({ type: 'tokens', content: chunk, meta });
|
|
1555
|
+
}
|
|
1556
|
+
},
|
|
1557
|
+
onContentBlock: undefined,
|
|
1558
|
+
}, {
|
|
1559
|
+
signal: stream.signal,
|
|
1560
|
+
timeoutMs: options.timeoutMs,
|
|
1561
|
+
onRequest: (req) => { rawRequest = req; },
|
|
1562
|
+
});
|
|
1563
|
+
rawResponse = streamResult.raw;
|
|
1564
|
+
lastStopReason = this.mapStopReason(streamResult.stopReason);
|
|
1565
|
+
// Accumulate usage
|
|
1566
|
+
totalUsage.inputTokens += streamResult.usage.inputTokens;
|
|
1567
|
+
totalUsage.outputTokens += streamResult.usage.outputTokens;
|
|
1568
|
+
if (emitUsage) {
|
|
1569
|
+
stream.emit({ type: 'usage', usage: { ...totalUsage } });
|
|
1570
|
+
}
|
|
1571
|
+
// Parse content blocks from response
|
|
1572
|
+
const responseBlocks = this.parseProviderContent(streamResult.content);
|
|
1573
|
+
allContentBlocks.push(...responseBlocks);
|
|
1574
|
+
// Check for tool_use blocks
|
|
1575
|
+
const toolUseBlocks = responseBlocks.filter((b) => b.type === 'tool_use');
|
|
1576
|
+
if (toolUseBlocks.length > 0 && lastStopReason === 'tool_use') {
|
|
1577
|
+
// Convert to normalized ToolCall[]
|
|
1578
|
+
const toolCalls = toolUseBlocks.map(block => ({
|
|
1579
|
+
id: block.id,
|
|
1580
|
+
name: block.name,
|
|
1581
|
+
input: block.input,
|
|
1582
|
+
}));
|
|
1583
|
+
// Track tool calls
|
|
1584
|
+
executedToolCalls.push(...toolCalls);
|
|
1585
|
+
// Build tool context
|
|
1586
|
+
const context = {
|
|
1587
|
+
rawText: JSON.stringify(toolUseBlocks),
|
|
1588
|
+
preamble: textAccumulated,
|
|
1589
|
+
depth: toolDepth,
|
|
1590
|
+
previousResults: executedToolResults,
|
|
1591
|
+
accumulated: allTextAccumulated,
|
|
1592
|
+
};
|
|
1593
|
+
// Yield control for tool execution
|
|
1594
|
+
const toolCallsEvent = {
|
|
1595
|
+
type: 'tool-calls',
|
|
1596
|
+
calls: toolCalls,
|
|
1597
|
+
context,
|
|
1598
|
+
};
|
|
1599
|
+
const results = await stream.requestToolExecution(toolCallsEvent);
|
|
1600
|
+
// Track tool results
|
|
1601
|
+
executedToolResults.push(...results);
|
|
1602
|
+
// Add tool results to content blocks
|
|
1603
|
+
for (const result of results) {
|
|
1604
|
+
allContentBlocks.push({
|
|
1605
|
+
type: 'tool_result',
|
|
1606
|
+
toolUseId: result.toolUseId,
|
|
1607
|
+
content: result.content,
|
|
1608
|
+
isError: result.isError,
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
// Add messages for next iteration
|
|
1612
|
+
messages.push({
|
|
1613
|
+
participant: 'Claude',
|
|
1614
|
+
content: responseBlocks,
|
|
1615
|
+
});
|
|
1616
|
+
messages.push({
|
|
1617
|
+
participant: 'User',
|
|
1618
|
+
content: results.map(r => ({
|
|
1619
|
+
type: 'tool_result',
|
|
1620
|
+
toolUseId: r.toolUseId,
|
|
1621
|
+
content: r.content,
|
|
1622
|
+
isError: r.isError,
|
|
1623
|
+
})),
|
|
1624
|
+
});
|
|
1625
|
+
toolDepth++;
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
// No more tools, we're done
|
|
1629
|
+
break;
|
|
1630
|
+
}
|
|
1631
|
+
const durationMs = Date.now() - startTime;
|
|
1632
|
+
const response = {
|
|
1633
|
+
content: allContentBlocks,
|
|
1634
|
+
rawAssistantText: allTextAccumulated,
|
|
1635
|
+
toolCalls: executedToolCalls,
|
|
1636
|
+
toolResults: executedToolResults,
|
|
1637
|
+
stopReason: lastStopReason,
|
|
1638
|
+
usage: totalUsage,
|
|
1639
|
+
details: {
|
|
1640
|
+
stop: {
|
|
1641
|
+
reason: lastStopReason,
|
|
1642
|
+
wasTruncated: lastStopReason === 'max_tokens',
|
|
1643
|
+
},
|
|
1644
|
+
usage: { ...totalUsage },
|
|
1645
|
+
timing: {
|
|
1646
|
+
totalDurationMs: durationMs,
|
|
1647
|
+
attempts: 1,
|
|
1648
|
+
},
|
|
1649
|
+
model: {
|
|
1650
|
+
requested: request.config.model,
|
|
1651
|
+
actual: request.config.model,
|
|
1652
|
+
provider: this.adapter.name,
|
|
1653
|
+
},
|
|
1654
|
+
cache: {
|
|
1655
|
+
markersInRequest: 0,
|
|
1656
|
+
tokensCreated: 0,
|
|
1657
|
+
tokensRead: 0,
|
|
1658
|
+
hitRatio: 0,
|
|
1659
|
+
},
|
|
1660
|
+
},
|
|
1661
|
+
raw: {
|
|
1662
|
+
request: rawRequest,
|
|
1663
|
+
response: rawResponse,
|
|
1664
|
+
},
|
|
1665
|
+
};
|
|
1666
|
+
stream.emit({ type: 'complete', response });
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
if (this.isAbortError(error)) {
|
|
1670
|
+
stream.emit({
|
|
1671
|
+
type: 'aborted',
|
|
1672
|
+
reason: 'user',
|
|
1673
|
+
rawAssistantText: allTextAccumulated,
|
|
1674
|
+
toolCalls: executedToolCalls,
|
|
1675
|
+
toolResults: executedToolResults,
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
throw error;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1129
1683
|
}
|
|
1130
1684
|
//# sourceMappingURL=membrane.js.map
|