@jaypie/llm 1.2.0-rc.0 → 1.2.0-rc.10

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 (49) hide show
  1. package/dist/cjs/Llm.d.ts +7 -0
  2. package/dist/cjs/index.cjs +834 -4
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/index.d.ts +2 -0
  5. package/dist/cjs/operate/OperateLoop.d.ts +0 -2
  6. package/dist/cjs/operate/StreamLoop.d.ts +41 -0
  7. package/dist/cjs/operate/adapters/AnthropicAdapter.d.ts +2 -0
  8. package/dist/cjs/operate/adapters/GeminiAdapter.d.ts +2 -0
  9. package/dist/cjs/operate/adapters/OpenAiAdapter.d.ts +2 -0
  10. package/dist/cjs/operate/adapters/OpenRouterAdapter.d.ts +2 -0
  11. package/dist/cjs/operate/adapters/ProviderAdapter.interface.d.ts +9 -0
  12. package/dist/cjs/operate/index.d.ts +2 -0
  13. package/dist/cjs/providers/anthropic/AnthropicProvider.class.d.ts +4 -0
  14. package/dist/cjs/providers/anthropic/utils.d.ts +1 -1
  15. package/dist/cjs/providers/gemini/GeminiProvider.class.d.ts +4 -0
  16. package/dist/cjs/providers/gemini/utils.d.ts +1 -1
  17. package/dist/cjs/providers/openai/OpenAiProvider.class.d.ts +4 -0
  18. package/dist/cjs/providers/openai/utils.d.ts +1 -1
  19. package/dist/cjs/providers/openrouter/OpenRouterProvider.class.d.ts +4 -0
  20. package/dist/cjs/providers/openrouter/utils.d.ts +1 -1
  21. package/dist/cjs/tools/Toolkit.class.d.ts +1 -1
  22. package/dist/cjs/types/LlmProvider.interface.d.ts +2 -0
  23. package/dist/cjs/types/LlmStreamChunk.interface.d.ts +41 -0
  24. package/dist/cjs/util/logger.d.ts +2 -2
  25. package/dist/esm/Llm.d.ts +7 -0
  26. package/dist/esm/index.d.ts +2 -0
  27. package/dist/esm/index.js +835 -5
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/esm/operate/OperateLoop.d.ts +0 -2
  30. package/dist/esm/operate/StreamLoop.d.ts +41 -0
  31. package/dist/esm/operate/adapters/AnthropicAdapter.d.ts +2 -0
  32. package/dist/esm/operate/adapters/GeminiAdapter.d.ts +2 -0
  33. package/dist/esm/operate/adapters/OpenAiAdapter.d.ts +2 -0
  34. package/dist/esm/operate/adapters/OpenRouterAdapter.d.ts +2 -0
  35. package/dist/esm/operate/adapters/ProviderAdapter.interface.d.ts +9 -0
  36. package/dist/esm/operate/index.d.ts +2 -0
  37. package/dist/esm/providers/anthropic/AnthropicProvider.class.d.ts +4 -0
  38. package/dist/esm/providers/anthropic/utils.d.ts +1 -1
  39. package/dist/esm/providers/gemini/GeminiProvider.class.d.ts +4 -0
  40. package/dist/esm/providers/gemini/utils.d.ts +1 -1
  41. package/dist/esm/providers/openai/OpenAiProvider.class.d.ts +4 -0
  42. package/dist/esm/providers/openai/utils.d.ts +1 -1
  43. package/dist/esm/providers/openrouter/OpenRouterProvider.class.d.ts +4 -0
  44. package/dist/esm/providers/openrouter/utils.d.ts +1 -1
  45. package/dist/esm/tools/Toolkit.class.d.ts +1 -1
  46. package/dist/esm/types/LlmProvider.interface.d.ts +2 -0
  47. package/dist/esm/types/LlmStreamChunk.interface.d.ts +41 -0
  48. package/dist/esm/util/logger.d.ts +2 -2
  49. package/package.json +6 -4
package/dist/esm/index.js CHANGED
@@ -156,6 +156,14 @@ function determineModelProvider(input) {
156
156
  provider: DEFAULT.PROVIDER.NAME,
157
157
  };
158
158
  }
159
+ // Check for explicit openrouter: prefix
160
+ if (input.startsWith("openrouter:")) {
161
+ const model = input.slice("openrouter:".length);
162
+ return {
163
+ model,
164
+ provider: PROVIDER.OPENROUTER.NAME,
165
+ };
166
+ }
159
167
  // Check if input is a provider name
160
168
  if (input === PROVIDER.ANTHROPIC.NAME) {
161
169
  return {
@@ -217,6 +225,14 @@ function determineModelProvider(input) {
217
225
  };
218
226
  }
219
227
  }
228
+ // Assume OpenRouter for models containing "/" (e.g., "openai/gpt-4", "anthropic/claude-3-opus")
229
+ // This check must come before match words so that "openai/gpt-4" is not matched by "openai" keyword
230
+ if (input.includes("/")) {
231
+ return {
232
+ model: input,
233
+ provider: PROVIDER.OPENROUTER.NAME,
234
+ };
235
+ }
220
236
  // Check Anthropic match words
221
237
  const lowerInput = input.toLowerCase();
222
238
  for (const matchWord of PROVIDER.ANTHROPIC.MODEL_MATCH_WORDS) {
@@ -336,6 +352,19 @@ var LlmResponseStatus;
336
352
  LlmResponseStatus["InProgress"] = "in_progress";
337
353
  })(LlmResponseStatus || (LlmResponseStatus = {}));
338
354
 
355
+ //
356
+ //
357
+ // Types
358
+ //
359
+ var LlmStreamChunkType;
360
+ (function (LlmStreamChunkType) {
361
+ LlmStreamChunkType["Done"] = "done";
362
+ LlmStreamChunkType["Error"] = "error";
363
+ LlmStreamChunkType["Text"] = "text";
364
+ LlmStreamChunkType["ToolCall"] = "tool_call";
365
+ LlmStreamChunkType["ToolResult"] = "tool_result";
366
+ })(LlmStreamChunkType || (LlmStreamChunkType = {}));
367
+
339
368
  /**
340
369
  * Converts a string to a standardized LlmInputMessage
341
370
  * @param input - String to format
@@ -720,7 +749,13 @@ class AnthropicAdapter extends BaseProviderAdapter {
720
749
  //
721
750
  buildRequest(request) {
722
751
  // Convert messages to Anthropic format (remove 'type' property)
723
- const messages = request.messages.map((msg) => {
752
+ // Filter out system messages - Anthropic only accepts system as a top-level field
753
+ const messages = request.messages
754
+ .filter((msg) => {
755
+ const role = msg.role;
756
+ return role !== "system";
757
+ })
758
+ .map((msg) => {
724
759
  const anthropicMsg = structuredClone(msg);
725
760
  delete anthropicMsg.type;
726
761
  return anthropicMsg;
@@ -813,6 +848,90 @@ class AnthropicAdapter extends BaseProviderAdapter {
813
848
  const anthropic = client;
814
849
  return (await anthropic.messages.create(request));
815
850
  }
851
+ async *executeStreamRequest(client, request) {
852
+ const anthropic = client;
853
+ const streamRequest = {
854
+ ...request,
855
+ stream: true,
856
+ };
857
+ const stream = await anthropic.messages.create(streamRequest);
858
+ // Track current tool call being built
859
+ let currentToolCall = null;
860
+ // Track usage for final chunk
861
+ let inputTokens = 0;
862
+ let outputTokens = 0;
863
+ let model = streamRequest.model;
864
+ for await (const event of stream) {
865
+ if (event.type === "message_start") {
866
+ // Extract initial usage and model info
867
+ const message = event.message;
868
+ if (message.usage) {
869
+ inputTokens = message.usage.input_tokens;
870
+ }
871
+ model = message.model;
872
+ }
873
+ else if (event.type === "content_block_start") {
874
+ const contentBlock = event.content_block;
875
+ if (contentBlock.type === "tool_use") {
876
+ // Start building a tool call
877
+ currentToolCall = {
878
+ id: contentBlock.id,
879
+ name: contentBlock.name,
880
+ arguments: "",
881
+ };
882
+ }
883
+ }
884
+ else if (event.type === "content_block_delta") {
885
+ const delta = event.delta;
886
+ if (delta.type === "text_delta") {
887
+ yield {
888
+ type: LlmStreamChunkType.Text,
889
+ content: delta.text,
890
+ };
891
+ }
892
+ else if (delta.type === "input_json_delta" && currentToolCall) {
893
+ // Accumulate tool call arguments
894
+ currentToolCall.arguments += delta.partial_json;
895
+ }
896
+ }
897
+ else if (event.type === "content_block_stop") {
898
+ // If we were building a tool call, emit it now
899
+ if (currentToolCall) {
900
+ yield {
901
+ type: LlmStreamChunkType.ToolCall,
902
+ toolCall: {
903
+ id: currentToolCall.id,
904
+ name: currentToolCall.name,
905
+ arguments: currentToolCall.arguments,
906
+ },
907
+ };
908
+ currentToolCall = null;
909
+ }
910
+ }
911
+ else if (event.type === "message_delta") {
912
+ // Extract final usage
913
+ if (event.usage) {
914
+ outputTokens = event.usage.output_tokens;
915
+ }
916
+ }
917
+ else if (event.type === "message_stop") {
918
+ // Emit done chunk with usage
919
+ yield {
920
+ type: LlmStreamChunkType.Done,
921
+ usage: [
922
+ {
923
+ input: inputTokens,
924
+ output: outputTokens,
925
+ reasoning: 0,
926
+ total: inputTokens + outputTokens,
927
+ provider: this.name,
928
+ model,
929
+ },
930
+ ],
931
+ };
932
+ }
933
+ }
934
+ }
816
935
  //
817
936
  // Response Parsing
818
937
  //
@@ -1145,6 +1264,77 @@ class GeminiAdapter extends BaseProviderAdapter {
1145
1264
  });
1146
1265
  return response;
1147
1266
  }
1267
+ async *executeStreamRequest(client, request) {
1268
+ const genAI = client;
1269
+ const geminiRequest = request;
1270
+ // Use generateContentStream for streaming
1271
+ const stream = await genAI.models.generateContentStream({
1272
+ model: geminiRequest.model,
1273
+ contents: geminiRequest.contents,
1274
+ config: geminiRequest.config,
1275
+ });
1276
+ // Track current function call being built
1277
+ let currentFunctionCall = null;
1278
+ // Track usage for final chunk
1279
+ let inputTokens = 0;
1280
+ let outputTokens = 0;
1281
+ let reasoningTokens = 0;
1282
+ const model = geminiRequest.model || this.defaultModel;
1283
+ for await (const chunk of stream) {
1284
+ // Extract text content from the chunk
1285
+ const candidate = chunk.candidates?.[0];
1286
+ if (candidate?.content?.parts) {
1287
+ for (const part of candidate.content.parts) {
1288
+ // Handle text content (excluding thought parts)
1289
+ if (part.text && !part.thought) {
1290
+ yield {
1291
+ type: LlmStreamChunkType.Text,
1292
+ content: part.text,
1293
+ };
1294
+ }
1295
+ // Handle function calls
1296
+ if (part.functionCall) {
1297
+ const functionCall = part.functionCall;
1298
+ currentFunctionCall = {
1299
+ id: functionCall.id || this.generateCallId(),
1300
+ name: functionCall.name || "",
1301
+ arguments: functionCall.args || {},
1302
+ };
1303
+ // Emit the function call immediately
1304
+ yield {
1305
+ type: LlmStreamChunkType.ToolCall,
1306
+ toolCall: {
1307
+ id: currentFunctionCall.id,
1308
+ name: currentFunctionCall.name,
1309
+ arguments: JSON.stringify(currentFunctionCall.arguments),
1310
+ },
1311
+ };
1312
+ currentFunctionCall = null;
1313
+ }
1314
+ }
1315
+ }
1316
+ // Extract usage metadata if present
1317
+ if (chunk.usageMetadata) {
1318
+ inputTokens = chunk.usageMetadata.promptTokenCount || 0;
1319
+ outputTokens = chunk.usageMetadata.candidatesTokenCount || 0;
1320
+ reasoningTokens = chunk.usageMetadata.thoughtsTokenCount || 0;
1321
+ }
1322
+ }
1323
+ // Emit done chunk with final usage
1324
+ yield {
1325
+ type: LlmStreamChunkType.Done,
1326
+ usage: [
1327
+ {
1328
+ input: inputTokens,
1329
+ output: outputTokens,
1330
+ reasoning: reasoningTokens,
1331
+ total: inputTokens + outputTokens,
1332
+ provider: this.name,
1333
+ model,
1334
+ },
1335
+ ],
1336
+ };
1337
+ }
1148
1338
  //
1149
1339
  // Response Parsing
1150
1340
  //
@@ -1610,6 +1800,95 @@ class OpenAiAdapter extends BaseProviderAdapter {
1610
1800
  // @ts-expect-error OpenAI SDK types don't match our request format exactly
1611
1801
  return await openai.responses.create(request);
1612
1802
  }
1803
+ async *executeStreamRequest(client, request) {
1804
+ const openai = client;
1805
+ const baseRequest = request;
1806
+ const streamRequest = {
1807
+ ...baseRequest,
1808
+ stream: true,
1809
+ };
1810
+ const stream = await openai.responses.create(streamRequest);
1811
+ // Track current function call being built
1812
+ let currentFunctionCall = null;
1813
+ // Track usage for final chunk
1814
+ let inputTokens = 0;
1815
+ let outputTokens = 0;
1816
+ let reasoningTokens = 0;
1817
+ const model = baseRequest.model || this.defaultModel;
1818
+ // Cast to async iterable - when stream: true, this is always a Stream<ResponseStreamEvent>
1819
+ const asyncStream = stream;
1820
+ for await (const event of asyncStream) {
1821
+ const eventType = event.type;
1822
+ if (eventType === "response.output_text.delta") {
1823
+ // Text content delta
1824
+ const delta = event.delta;
1825
+ if (delta) {
1826
+ yield {
1827
+ type: LlmStreamChunkType.Text,
1828
+ content: delta,
1829
+ };
1830
+ }
1831
+ }
1832
+ else if (eventType === "response.function_call_arguments.delta") {
1833
+ // Function call arguments delta - accumulate
1834
+ const delta = event.delta;
1835
+ if (delta && currentFunctionCall) {
1836
+ currentFunctionCall.arguments += delta;
1837
+ }
1838
+ }
1839
+ else if (eventType === "response.output_item.added") {
1840
+ // New output item - check if it's a function call
1841
+ const item = event.item;
1842
+ if (item?.type === "function_call") {
1843
+ currentFunctionCall = {
1844
+ id: item.id || "",
1845
+ callId: item.call_id || "",
1846
+ name: item.name || "",
1847
+ arguments: "",
1848
+ };
1849
+ }
1850
+ }
1851
+ else if (eventType === "response.output_item.done") {
1852
+ // Output item completed - emit function call if that's what we were building
1853
+ if (currentFunctionCall) {
1854
+ yield {
1855
+ type: LlmStreamChunkType.ToolCall,
1856
+ toolCall: {
1857
+ id: currentFunctionCall.callId,
1858
+ name: currentFunctionCall.name,
1859
+ arguments: currentFunctionCall.arguments,
1860
+ },
1861
+ };
1862
+ currentFunctionCall = null;
1863
+ }
1864
+ }
1865
+ else if (eventType === "response.completed") {
1866
+ // Response completed - extract final usage
1867
+ const response = event.response;
1868
+ if (response?.usage) {
1869
+ inputTokens = response.usage.input_tokens || 0;
1870
+ outputTokens = response.usage.output_tokens || 0;
1871
+ reasoningTokens = response.usage.output_tokens_details?.reasoning_tokens || 0;
1872
+ }
1873
+ }
1874
+ else if (eventType === "response.done") {
1875
+ // Stream done - emit final chunk with usage
1876
+ yield {
1877
+ type: LlmStreamChunkType.Done,
1878
+ usage: [
1879
+ {
1880
+ input: inputTokens,
1881
+ output: outputTokens,
1882
+ reasoning: reasoningTokens,
1883
+ total: inputTokens + outputTokens,
1884
+ provider: this.name,
1885
+ model,
1886
+ },
1887
+ ],
1888
+ };
1889
+ }
1890
+ }
1891
+ }
1613
1892
  //
1614
1893
  // Response Parsing
1615
1894
  //
@@ -1922,6 +2201,110 @@ class OpenRouterAdapter extends BaseProviderAdapter {
1922
2201
  });
1923
2202
  return response;
1924
2203
  }
2204
+ async *executeStreamRequest(client, request) {
2205
+ const openRouter = client;
2206
+ const openRouterRequest = request;
2207
+ // Use chat.send with stream: true for streaming responses
2208
+ const stream = await openRouter.chat.send({
2209
+ model: openRouterRequest.model,
2210
+ messages: openRouterRequest.messages,
2211
+ tools: openRouterRequest.tools,
2212
+ toolChoice: openRouterRequest.tool_choice,
2213
+ user: openRouterRequest.user,
2214
+ stream: true,
2215
+ });
2216
+ // Track current tool call being built
2217
+ let currentToolCall = null;
2218
+ // Track usage for final chunk
2219
+ let inputTokens = 0;
2220
+ let outputTokens = 0;
2221
+ const model = openRouterRequest.model || this.defaultModel;
2222
+ for await (const chunk of stream) {
2223
+ const typedChunk = chunk;
2224
+ const choices = typedChunk.choices;
2225
+ if (choices && choices.length > 0) {
2226
+ const delta = choices[0].delta;
2227
+ // Handle text content
2228
+ if (delta?.content) {
2229
+ yield {
2230
+ type: LlmStreamChunkType.Text,
2231
+ content: delta.content,
2232
+ };
2233
+ }
2234
+ // Handle tool calls
2235
+ if (delta?.tool_calls && delta.tool_calls.length > 0) {
2236
+ for (const toolCallDelta of delta.tool_calls) {
2237
+ if (toolCallDelta.id) {
2238
+ // New tool call starting
2239
+ if (currentToolCall) {
2240
+ // Emit the previous tool call
2241
+ yield {
2242
+ type: LlmStreamChunkType.ToolCall,
2243
+ toolCall: {
2244
+ id: currentToolCall.id,
2245
+ name: currentToolCall.name,
2246
+ arguments: currentToolCall.arguments,
2247
+ },
2248
+ };
2249
+ }
2250
+ currentToolCall = {
2251
+ id: toolCallDelta.id,
2252
+ name: toolCallDelta.function?.name || "",
2253
+ arguments: toolCallDelta.function?.arguments || "",
2254
+ };
2255
+ }
2256
+ else if (currentToolCall) {
2257
+ // Continuing existing tool call
2258
+ if (toolCallDelta.function?.name) {
2259
+ currentToolCall.name += toolCallDelta.function.name;
2260
+ }
2261
+ if (toolCallDelta.function?.arguments) {
2262
+ currentToolCall.arguments += toolCallDelta.function.arguments;
2263
+ }
2264
+ }
2265
+ }
2266
+ }
2267
+ // Check for finish reason
2268
+ if (choices[0].finish_reason) {
2269
+ // Emit any pending tool call
2270
+ if (currentToolCall) {
2271
+ yield {
2272
+ type: LlmStreamChunkType.ToolCall,
2273
+ toolCall: {
2274
+ id: currentToolCall.id,
2275
+ name: currentToolCall.name,
2276
+ arguments: currentToolCall.arguments,
2277
+ },
2278
+ };
2279
+ currentToolCall = null;
2280
+ }
2281
+ }
2282
+ }
2283
+ // Extract usage if present (usually in the final chunk)
2284
+ if (typedChunk.usage) {
2285
+ inputTokens =
2286
+ typedChunk.usage.prompt_tokens || typedChunk.usage.promptTokens || 0;
2287
+ outputTokens =
2288
+ typedChunk.usage.completion_tokens ||
2289
+ typedChunk.usage.completionTokens ||
2290
+ 0;
2291
+ }
2292
+ }
2293
+ // Emit done chunk with final usage
2294
+ yield {
2295
+ type: LlmStreamChunkType.Done,
2296
+ usage: [
2297
+ {
2298
+ input: inputTokens,
2299
+ output: outputTokens,
2300
+ reasoning: 0,
2301
+ total: inputTokens + outputTokens,
2302
+ provider: this.name,
2303
+ model,
2304
+ },
2305
+ ],
2306
+ };
2307
+ }
1925
2308
  //
1926
2309
  // Response Parsing
1927
2310
  //
@@ -2769,7 +3152,7 @@ class RetryExecutor {
2769
3152
  //
2770
3153
  // Constants
2771
3154
  //
2772
- const ERROR = {
3155
+ const ERROR$1 = {
2773
3156
  BAD_FUNCTION_CALL: "Bad Function Call",
2774
3157
  };
2775
3158
  //
@@ -2806,13 +3189,16 @@ class OperateLoop {
2806
3189
  this.client = config.client;
2807
3190
  this.hookRunnerInstance = config.hookRunner ?? hookRunner;
2808
3191
  this.inputProcessorInstance = config.inputProcessor ?? inputProcessor;
2809
- this.maxRetries = config.maxRetries ?? 6;
2810
3192
  this.retryPolicy = config.retryPolicy ?? defaultRetryPolicy;
2811
3193
  }
2812
3194
  /**
2813
3195
  * Execute the operate loop for multi-turn conversations with tool calling.
2814
3196
  */
2815
3197
  async execute(input, options = {}) {
3198
+ // Log what was passed to operate
3199
+ log$1.trace("[operate] Starting operate loop");
3200
+ log$1.var({ "operate.input": input });
3201
+ log$1.var({ "operate.options": options });
2816
3202
  // Initialize state
2817
3203
  const state = this.initializeState(input, options);
2818
3204
  const context = this.createContext(options);
@@ -2915,6 +3301,9 @@ class OperateLoop {
2915
3301
  });
2916
3302
  // Build provider-specific request
2917
3303
  const providerRequest = this.adapter.buildRequest(request);
3304
+ // Log what was passed to the model
3305
+ log$1.trace("[operate] Calling model");
3306
+ log$1.var({ "operate.request": providerRequest });
2918
3307
  // Execute beforeEachModelRequest hook
2919
3308
  await this.hookRunnerInstance.runBeforeModelRequest(context.hooks, {
2920
3309
  input: state.currentInput,
@@ -2930,6 +3319,9 @@ class OperateLoop {
2930
3319
  },
2931
3320
  hooks: context.hooks,
2932
3321
  });
3322
+ // Log what was returned from the model
3323
+ log$1.trace("[operate] Model response received");
3324
+ log$1.var({ "operate.response": response });
2933
3325
  // Parse response
2934
3326
  const parsed = this.adapter.parseResponse(response, options);
2935
3327
  // Track usage
@@ -3018,7 +3410,7 @@ class OperateLoop {
3018
3410
  state.responseBuilder.setError({
3019
3411
  detail,
3020
3412
  status: jaypieError.status,
3021
- title: ERROR.BAD_FUNCTION_CALL,
3413
+ title: ERROR$1.BAD_FUNCTION_CALL,
3022
3414
  });
3023
3415
  log$1.error(`Error executing function call ${toolCall.name}`);
3024
3416
  log$1.var({ error });
@@ -3149,6 +3541,325 @@ function createOperateLoop(config) {
3149
3541
  return new OperateLoop(config);
3150
3542
  }
3151
3543
 
3544
+ //
3545
+ //
3546
+ // Constants
3547
+ //
3548
+ const ERROR = {
3549
+ BAD_FUNCTION_CALL: "Bad Function Call",
3550
+ };
3551
+ //
3552
+ //
3553
+ // Main
3554
+ //
3555
+ /**
3556
+ * StreamLoop implements streaming multi-turn conversation loop.
3557
+ * It orchestrates provider adapters and tool calling while yielding
3558
+ * stream chunks as they become available.
3559
+ */
3560
+ class StreamLoop {
3561
+ constructor(config) {
3562
+ this.adapter = config.adapter;
3563
+ this.client = config.client;
3564
+ this.hookRunnerInstance = config.hookRunner ?? hookRunner;
3565
+ this.inputProcessorInstance = config.inputProcessor ?? inputProcessor;
3566
+ }
3567
+ /**
3568
+ * Execute the streaming loop for multi-turn conversations with tool calling.
3569
+ * Yields stream chunks as they become available.
3570
+ */
3571
+ async *execute(input, options = {}) {
3572
+ // Verify adapter supports streaming
3573
+ if (!this.adapter.executeStreamRequest) {
3574
+ throw new BadGatewayError(`Provider ${this.adapter.name} does not support streaming`);
3575
+ }
3576
+ // Initialize state
3577
+ const state = this.initializeState(input, options);
3578
+ const context = this.createContext(options);
3579
+ // Build initial request
3580
+ let request = this.buildInitialRequest(state, options);
3581
+ // Multi-turn loop
3582
+ while (state.currentTurn < state.maxTurns) {
3583
+ state.currentTurn++;
3584
+ // Execute one streaming turn
3585
+ const { shouldContinue, toolCalls } = yield* this.executeOneStreamingTurn(request, state, context, options);
3586
+ if (!shouldContinue) {
3587
+ break;
3588
+ }
3589
+ // If we have tool calls, process them
3590
+ if (toolCalls && toolCalls.length > 0 && state.toolkit) {
3591
+ yield* this.processToolCalls(toolCalls, state, context, options);
3592
+ // Check if we've reached max turns
3593
+ if (state.currentTurn >= state.maxTurns) {
3594
+ const error = new TooManyRequestsError();
3595
+ const detail = `Model requested function call but exceeded ${state.maxTurns} turns`;
3596
+ log$1.warn(detail);
3597
+ yield {
3598
+ type: LlmStreamChunkType.Error,
3599
+ error: {
3600
+ detail,
3601
+ status: error.status,
3602
+ title: error.title,
3603
+ },
3604
+ };
3605
+ break;
3606
+ }
3607
+ // Rebuild request with updated history for next turn
3608
+ request = {
3609
+ format: state.formattedFormat,
3610
+ instructions: options.instructions,
3611
+ messages: state.currentInput,
3612
+ model: options.model ?? this.adapter.defaultModel,
3613
+ providerOptions: options.providerOptions,
3614
+ system: options.system,
3615
+ tools: state.formattedTools,
3616
+ user: options.user,
3617
+ };
3618
+ }
3619
+ else {
3620
+ break;
3621
+ }
3622
+ }
3623
+ // Emit final done chunk with accumulated usage
3624
+ yield {
3625
+ type: LlmStreamChunkType.Done,
3626
+ usage: state.usageItems,
3627
+ };
3628
+ }
3629
+ //
3630
+ // Private Methods
3631
+ //
3632
+ initializeState(input, options) {
3633
+ // Process input with placeholders
3634
+ const processedInput = this.inputProcessorInstance.process(input, options);
3635
+ // Determine max turns
3636
+ const maxTurns = maxTurnsFromOptions(options);
3637
+ // Get toolkit
3638
+ let toolkit;
3639
+ if (options.tools) {
3640
+ if (options.tools instanceof Toolkit) {
3641
+ toolkit = options.tools;
3642
+ }
3643
+ else if (Array.isArray(options.tools) && options.tools.length > 0) {
3644
+ const explain = options.explain ?? false;
3645
+ toolkit = new Toolkit(options.tools, { explain });
3646
+ }
3647
+ }
3648
+ // Format output schema through adapter if provided
3649
+ let formattedFormat;
3650
+ if (options.format) {
3651
+ formattedFormat = this.adapter.formatOutputSchema(options.format);
3652
+ }
3653
+ // Format tools through adapter
3654
+ const formattedTools = toolkit
3655
+ ? this.adapter.formatTools(toolkit, formattedFormat)
3656
+ : undefined;
3657
+ return {
3658
+ currentInput: processedInput.history,
3659
+ currentTurn: 0,
3660
+ formattedFormat,
3661
+ formattedTools,
3662
+ maxTurns,
3663
+ toolkit,
3664
+ usageItems: [],
3665
+ };
3666
+ }
3667
+ createContext(options) {
3668
+ return {
3669
+ hooks: options.hooks ?? {},
3670
+ options,
3671
+ };
3672
+ }
3673
+ buildInitialRequest(state, options) {
3674
+ return {
3675
+ format: state.formattedFormat,
3676
+ instructions: options.instructions,
3677
+ messages: state.currentInput,
3678
+ model: options.model ?? this.adapter.defaultModel,
3679
+ providerOptions: options.providerOptions,
3680
+ system: options.system,
3681
+ tools: state.formattedTools,
3682
+ user: options.user,
3683
+ };
3684
+ }
3685
+ async *executeOneStreamingTurn(request, state, context, options) {
3686
+ // Build provider-specific request
3687
+ const providerRequest = this.adapter.buildRequest(request);
3688
+ // Execute beforeEachModelRequest hook
3689
+ await this.hookRunnerInstance.runBeforeModelRequest(context.hooks, {
3690
+ input: state.currentInput,
3691
+ options,
3692
+ providerRequest,
3693
+ });
3694
+ // Collect tool calls from the stream
3695
+ const collectedToolCalls = [];
3696
+ // Execute streaming request
3697
+ const streamGenerator = this.adapter.executeStreamRequest(this.client, providerRequest);
3698
+ for await (const chunk of streamGenerator) {
3699
+ // Pass through text chunks
3700
+ if (chunk.type === LlmStreamChunkType.Text) {
3701
+ yield chunk;
3702
+ }
3703
+ // Collect tool calls
3704
+ if (chunk.type === LlmStreamChunkType.ToolCall) {
3705
+ collectedToolCalls.push({
3706
+ callId: chunk.toolCall.id,
3707
+ name: chunk.toolCall.name,
3708
+ arguments: chunk.toolCall.arguments,
3709
+ raw: chunk.toolCall,
3710
+ });
3711
+ yield chunk;
3712
+ }
3713
+ // Track usage from done chunk (but don't yield it yet - we'll emit our own)
3714
+ if (chunk.type === LlmStreamChunkType.Done && chunk.usage) {
3715
+ state.usageItems.push(...chunk.usage);
3716
+ }
3717
+ // Pass through error chunks
3718
+ if (chunk.type === LlmStreamChunkType.Error) {
3719
+ yield chunk;
3720
+ }
3721
+ }
3722
+ // Execute afterEachModelResponse hook
3723
+ await this.hookRunnerInstance.runAfterModelResponse(context.hooks, {
3724
+ content: "",
3725
+ input: state.currentInput,
3726
+ options,
3727
+ providerRequest,
3728
+ providerResponse: null,
3729
+ usage: state.usageItems,
3730
+ });
3731
+ // If we have tool calls and a toolkit, continue the loop
3732
+ if (collectedToolCalls.length > 0 && state.toolkit && state.maxTurns > 1) {
3733
+ // Add tool calls to history
3734
+ for (const toolCall of collectedToolCalls) {
3735
+ state.currentInput.push({
3736
+ type: LlmMessageType.FunctionCall,
3737
+ name: toolCall.name,
3738
+ arguments: toolCall.arguments,
3739
+ call_id: toolCall.callId,
3740
+ id: toolCall.callId,
3741
+ });
3742
+ }
3743
+ return { shouldContinue: true, toolCalls: collectedToolCalls };
3744
+ }
3745
+ return { shouldContinue: false };
3746
+ }
3747
+ async *processToolCalls(toolCalls, state, context, _options) {
3748
+ for (const toolCall of toolCalls) {
3749
+ try {
3750
+ // Execute beforeEachTool hook
3751
+ await this.hookRunnerInstance.runBeforeTool(context.hooks, {
3752
+ args: toolCall.arguments,
3753
+ toolName: toolCall.name,
3754
+ });
3755
+ // Call the tool
3756
+ log$1.trace(`[stream] Calling tool - ${toolCall.name}`);
3757
+ const result = await state.toolkit.call({
3758
+ arguments: toolCall.arguments,
3759
+ name: toolCall.name,
3760
+ });
3761
+ // Execute afterEachTool hook
3762
+ await this.hookRunnerInstance.runAfterTool(context.hooks, {
3763
+ args: toolCall.arguments,
3764
+ result,
3765
+ toolName: toolCall.name,
3766
+ });
3767
+ // Yield tool result chunk
3768
+ yield {
3769
+ type: LlmStreamChunkType.ToolResult,
3770
+ toolResult: {
3771
+ id: toolCall.callId,
3772
+ name: toolCall.name,
3773
+ result,
3774
+ },
3775
+ };
3776
+ // Add tool result to history
3777
+ state.currentInput.push({
3778
+ type: LlmMessageType.FunctionCallOutput,
3779
+ output: JSON.stringify(result),
3780
+ call_id: toolCall.callId,
3781
+ name: toolCall.name,
3782
+ });
3783
+ }
3784
+ catch (error) {
3785
+ // Execute onToolError hook
3786
+ await this.hookRunnerInstance.runOnToolError(context.hooks, {
3787
+ args: toolCall.arguments,
3788
+ error: error,
3789
+ toolName: toolCall.name,
3790
+ });
3791
+ // Yield error chunk
3792
+ const jaypieError = new BadGatewayError();
3793
+ const detail = [
3794
+ `Error executing function call ${toolCall.name}.`,
3795
+ error.message,
3796
+ ].join("\n");
3797
+ yield {
3798
+ type: LlmStreamChunkType.Error,
3799
+ error: {
3800
+ detail,
3801
+ status: jaypieError.status,
3802
+ title: ERROR.BAD_FUNCTION_CALL,
3803
+ },
3804
+ };
3805
+ log$1.error(`Error executing function call ${toolCall.name}`);
3806
+ log$1.var({ error });
3807
+ }
3808
+ }
3809
+ }
3810
+ /**
3811
+ * Convert Gemini contents format to internal history format.
3812
+ */
3813
+ convertGeminiContentsToHistory(contents) {
3814
+ const history = [];
3815
+ for (const content of contents) {
3816
+ if (!content.parts)
3817
+ continue;
3818
+ for (const part of content.parts) {
3819
+ if (part.text && typeof part.text === "string") {
3820
+ history.push({
3821
+ role: content.role === "model"
3822
+ ? LlmMessageRole.Assistant
3823
+ : LlmMessageRole.User,
3824
+ content: part.text,
3825
+ type: LlmMessageType.Message,
3826
+ });
3827
+ }
3828
+ else if (part.functionCall) {
3829
+ const fc = part.functionCall;
3830
+ history.push({
3831
+ type: LlmMessageType.FunctionCall,
3832
+ name: fc.name || "",
3833
+ arguments: JSON.stringify(fc.args || {}),
3834
+ call_id: fc.id || "",
3835
+ id: fc.id || "",
3836
+ });
3837
+ }
3838
+ else if (part.functionResponse) {
3839
+ const fr = part.functionResponse;
3840
+ history.push({
3841
+ type: LlmMessageType.FunctionCallOutput,
3842
+ output: JSON.stringify(fr.response || {}),
3843
+ call_id: "",
3844
+ name: fr.name || "",
3845
+ });
3846
+ }
3847
+ }
3848
+ }
3849
+ return history;
3850
+ }
3851
+ }
3852
+ //
3853
+ //
3854
+ // Factory
3855
+ //
3856
+ /**
3857
+ * Create a StreamLoop instance with the specified configuration.
3858
+ */
3859
+ function createStreamLoop(config) {
3860
+ return new StreamLoop(config);
3861
+ }
3862
+
3152
3863
  // SDK loader with caching
3153
3864
  let cachedSdk$2 = null;
3154
3865
  async function loadSdk$2() {
@@ -3304,6 +4015,17 @@ class AnthropicProvider {
3304
4015
  });
3305
4016
  return this._operateLoop;
3306
4017
  }
4018
+ async getStreamLoop() {
4019
+ if (this._streamLoop) {
4020
+ return this._streamLoop;
4021
+ }
4022
+ const client = await this.getClient();
4023
+ this._streamLoop = createStreamLoop({
4024
+ adapter: anthropicAdapter,
4025
+ client,
4026
+ });
4027
+ return this._streamLoop;
4028
+ }
3307
4029
  // Main send method
3308
4030
  async send(message, options) {
3309
4031
  const client = await this.getClient();
@@ -3340,6 +4062,18 @@ class AnthropicProvider {
3340
4062
  }
3341
4063
  return response;
3342
4064
  }
4065
+ async *stream(input, options = {}) {
4066
+ const streamLoop = await this.getStreamLoop();
4067
+ const mergedOptions = { ...options, model: options.model ?? this.model };
4068
+ // Create a merged history including both the tracked history and any explicitly provided history
4069
+ if (this.conversationHistory.length > 0) {
4070
+ mergedOptions.history = options.history
4071
+ ? [...this.conversationHistory, ...options.history]
4072
+ : [...this.conversationHistory];
4073
+ }
4074
+ // Execute stream loop
4075
+ yield* streamLoop.execute(input, mergedOptions);
4076
+ }
3343
4077
  }
3344
4078
 
3345
4079
  // SDK loader with caching
@@ -3416,6 +4150,17 @@ class GeminiProvider {
3416
4150
  });
3417
4151
  return this._operateLoop;
3418
4152
  }
4153
+ async getStreamLoop() {
4154
+ if (this._streamLoop) {
4155
+ return this._streamLoop;
4156
+ }
4157
+ const client = await this.getClient();
4158
+ this._streamLoop = createStreamLoop({
4159
+ adapter: geminiAdapter,
4160
+ client,
4161
+ });
4162
+ return this._streamLoop;
4163
+ }
3419
4164
  async send(message, options) {
3420
4165
  const client = await this.getClient();
3421
4166
  const { messages } = prepareMessages$2(message, options);
@@ -3470,6 +4215,18 @@ class GeminiProvider {
3470
4215
  }
3471
4216
  return response;
3472
4217
  }
4218
+ async *stream(input, options = {}) {
4219
+ const streamLoop = await this.getStreamLoop();
4220
+ const mergedOptions = { ...options, model: options.model ?? this.model };
4221
+ // Create a merged history including both the tracked history and any explicitly provided history
4222
+ if (this.conversationHistory.length > 0) {
4223
+ mergedOptions.history = options.history
4224
+ ? [...this.conversationHistory, ...options.history]
4225
+ : [...this.conversationHistory];
4226
+ }
4227
+ // Execute stream loop
4228
+ yield* streamLoop.execute(input, mergedOptions);
4229
+ }
3473
4230
  }
3474
4231
 
3475
4232
  // Logger
@@ -3584,6 +4341,17 @@ class OpenAiProvider {
3584
4341
  });
3585
4342
  return this._operateLoop;
3586
4343
  }
4344
+ async getStreamLoop() {
4345
+ if (this._streamLoop) {
4346
+ return this._streamLoop;
4347
+ }
4348
+ const client = await this.getClient();
4349
+ this._streamLoop = createStreamLoop({
4350
+ adapter: openAiAdapter,
4351
+ client,
4352
+ });
4353
+ return this._streamLoop;
4354
+ }
3587
4355
  async send(message, options) {
3588
4356
  const client = await this.getClient();
3589
4357
  const messages = prepareMessages$1(message, options || {});
@@ -3618,6 +4386,18 @@ class OpenAiProvider {
3618
4386
  }
3619
4387
  return response;
3620
4388
  }
4389
+ async *stream(input, options = {}) {
4390
+ const streamLoop = await this.getStreamLoop();
4391
+ const mergedOptions = { ...options, model: options.model ?? this.model };
4392
+ // Create a merged history including both the tracked history and any explicitly provided history
4393
+ if (this.conversationHistory.length > 0) {
4394
+ mergedOptions.history = options.history
4395
+ ? [...this.conversationHistory, ...options.history]
4396
+ : [...this.conversationHistory];
4397
+ }
4398
+ // Execute stream loop
4399
+ yield* streamLoop.execute(input, mergedOptions);
4400
+ }
3621
4401
  }
3622
4402
 
3623
4403
  // SDK loader with caching
@@ -3708,6 +4488,17 @@ class OpenRouterProvider {
3708
4488
  });
3709
4489
  return this._operateLoop;
3710
4490
  }
4491
+ async getStreamLoop() {
4492
+ if (this._streamLoop) {
4493
+ return this._streamLoop;
4494
+ }
4495
+ const client = await this.getClient();
4496
+ this._streamLoop = createStreamLoop({
4497
+ adapter: openRouterAdapter,
4498
+ client,
4499
+ });
4500
+ return this._streamLoop;
4501
+ }
3711
4502
  async send(message, options) {
3712
4503
  const client = await this.getClient();
3713
4504
  const messages = prepareMessages(message, options);
@@ -3757,6 +4548,18 @@ class OpenRouterProvider {
3757
4548
  }
3758
4549
  return response;
3759
4550
  }
4551
+ async *stream(input, options = {}) {
4552
+ const streamLoop = await this.getStreamLoop();
4553
+ const mergedOptions = { ...options, model: options.model ?? this.model };
4554
+ // Create a merged history including both the tracked history and any explicitly provided history
4555
+ if (this.conversationHistory.length > 0) {
4556
+ mergedOptions.history = options.history
4557
+ ? [...this.conversationHistory, ...options.history]
4558
+ : [...this.conversationHistory];
4559
+ }
4560
+ // Execute stream loop
4561
+ yield* streamLoop.execute(input, mergedOptions);
4562
+ }
3760
4563
  }
3761
4564
 
3762
4565
  class Llm {
@@ -3824,6 +4627,12 @@ class Llm {
3824
4627
  }
3825
4628
  return this._llm.operate(input, options);
3826
4629
  }
4630
+ async *stream(input, options = {}) {
4631
+ if (!this._llm.stream) {
4632
+ throw new NotImplementedError(`Provider ${this._provider} does not support stream method`);
4633
+ }
4634
+ yield* this._llm.stream(input, options);
4635
+ }
3827
4636
  static async send(message, options) {
3828
4637
  const { llm, apiKey, model, ...messageOptions } = options || {};
3829
4638
  const instance = new Llm(llm, { apiKey, model });
@@ -3850,6 +4659,27 @@ class Llm {
3850
4659
  const instance = new Llm(finalLlm, { apiKey, model: finalModel });
3851
4660
  return instance.operate(input, operateOptions);
3852
4661
  }
4662
+ static stream(input, options) {
4663
+ const { llm, apiKey, model, ...streamOptions } = options || {};
4664
+ let finalLlm = llm;
4665
+ let finalModel = model;
4666
+ if (!llm && model) {
4667
+ const determined = determineModelProvider(model);
4668
+ if (determined.provider) {
4669
+ finalLlm = determined.provider;
4670
+ }
4671
+ }
4672
+ else if (llm && model) {
4673
+ // When both llm and model are provided, check if they conflict
4674
+ const determined = determineModelProvider(model);
4675
+ if (determined.provider && determined.provider !== llm) {
4676
+ // Don't pass the conflicting model to the constructor
4677
+ finalModel = undefined;
4678
+ }
4679
+ }
4680
+ const instance = new Llm(finalLlm, { apiKey, model: finalModel });
4681
+ return instance.stream(input, streamOptions);
4682
+ }
3853
4683
  }
3854
4684
 
3855
4685
  const random = {
@@ -4142,5 +4972,5 @@ const toolkit = new JaypieToolkit(tools);
4142
4972
  [LlmMessageRole.Developer]: "user",
4143
4973
  });
4144
4974
 
4145
- export { GeminiProvider, JaypieToolkit, constants as LLM, Llm, LlmMessageRole, LlmMessageType, OpenRouterProvider, Toolkit, toolkit, tools };
4975
+ export { GeminiProvider, JaypieToolkit, constants as LLM, Llm, LlmMessageRole, LlmMessageType, LlmStreamChunkType, OpenRouterProvider, Toolkit, toolkit, tools };
4146
4976
  //# sourceMappingURL=index.js.map