@animalabs/membrane 0.5.24 → 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.
Files changed (38) hide show
  1. package/dist/membrane.d.ts +37 -0
  2. package/dist/membrane.d.ts.map +1 -1
  3. package/dist/membrane.js +554 -0
  4. package/dist/membrane.js.map +1 -1
  5. package/dist/providers/mock.d.ts +8 -0
  6. package/dist/providers/mock.d.ts.map +1 -1
  7. package/dist/providers/mock.js +39 -2
  8. package/dist/providers/mock.js.map +1 -1
  9. package/dist/providers/openai-compatible.d.ts.map +1 -1
  10. package/dist/providers/openai-compatible.js +5 -1
  11. package/dist/providers/openai-compatible.js.map +1 -1
  12. package/dist/providers/openai.d.ts.map +1 -1
  13. package/dist/providers/openai.js +5 -1
  14. package/dist/providers/openai.js.map +1 -1
  15. package/dist/providers/openrouter.d.ts.map +1 -1
  16. package/dist/providers/openrouter.js +5 -1
  17. package/dist/providers/openrouter.js.map +1 -1
  18. package/dist/types/index.d.ts +2 -0
  19. package/dist/types/index.d.ts.map +1 -1
  20. package/dist/types/index.js +1 -0
  21. package/dist/types/index.js.map +1 -1
  22. package/dist/types/yielding-stream.d.ts +167 -0
  23. package/dist/types/yielding-stream.d.ts.map +1 -0
  24. package/dist/types/yielding-stream.js +34 -0
  25. package/dist/types/yielding-stream.js.map +1 -0
  26. package/dist/yielding-stream.d.ts +60 -0
  27. package/dist/yielding-stream.d.ts.map +1 -0
  28. package/dist/yielding-stream.js +204 -0
  29. package/dist/yielding-stream.js.map +1 -0
  30. package/package.json +1 -1
  31. package/src/membrane.ts +691 -1
  32. package/src/providers/mock.ts +47 -2
  33. package/src/providers/openai-compatible.ts +8 -3
  34. package/src/providers/openai.ts +8 -3
  35. package/src/providers/openrouter.ts +8 -3
  36. package/src/types/index.ts +23 -0
  37. package/src/types/yielding-stream.ts +228 -0
  38. package/src/yielding-stream.ts +271 -0
@@ -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
@@ -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;AA0B1B,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;CAuB7B"}
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