@node-llm/core 1.1.0 → 1.2.0

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 (45) hide show
  1. package/dist/chat/ChatStream.d.ts.map +1 -1
  2. package/dist/chat/ChatStream.js +85 -34
  3. package/dist/config.d.ts +1 -1
  4. package/dist/errors/index.d.ts +1 -1
  5. package/dist/errors/index.js +2 -2
  6. package/dist/models/models.js +15 -15
  7. package/dist/providers/BaseProvider.d.ts +1 -1
  8. package/dist/providers/BaseProvider.js +1 -1
  9. package/dist/providers/Provider.d.ts +1 -0
  10. package/dist/providers/Provider.d.ts.map +1 -1
  11. package/dist/providers/anthropic/Chat.d.ts.map +1 -1
  12. package/dist/providers/anthropic/Chat.js +5 -1
  13. package/dist/providers/anthropic/Streaming.d.ts.map +1 -1
  14. package/dist/providers/anthropic/Streaming.js +49 -2
  15. package/dist/providers/deepseek/Chat.d.ts.map +1 -1
  16. package/dist/providers/deepseek/Chat.js +5 -4
  17. package/dist/providers/deepseek/Streaming.d.ts.map +1 -1
  18. package/dist/providers/deepseek/Streaming.js +49 -3
  19. package/dist/providers/gemini/Chat.d.ts.map +1 -1
  20. package/dist/providers/gemini/Chat.js +3 -0
  21. package/dist/providers/gemini/Embeddings.d.ts.map +1 -1
  22. package/dist/providers/gemini/Embeddings.js +3 -0
  23. package/dist/providers/gemini/Image.d.ts.map +1 -1
  24. package/dist/providers/gemini/Image.js +3 -0
  25. package/dist/providers/gemini/Streaming.d.ts.map +1 -1
  26. package/dist/providers/gemini/Streaming.js +32 -1
  27. package/dist/providers/gemini/Transcription.d.ts.map +1 -1
  28. package/dist/providers/gemini/Transcription.js +3 -0
  29. package/dist/providers/openai/Chat.d.ts.map +1 -1
  30. package/dist/providers/openai/Chat.js +5 -4
  31. package/dist/providers/openai/Embedding.d.ts.map +1 -1
  32. package/dist/providers/openai/Embedding.js +5 -1
  33. package/dist/providers/openai/Image.d.ts.map +1 -1
  34. package/dist/providers/openai/Image.js +5 -1
  35. package/dist/providers/openai/Moderation.d.ts.map +1 -1
  36. package/dist/providers/openai/Moderation.js +12 -6
  37. package/dist/providers/openai/Streaming.d.ts.map +1 -1
  38. package/dist/providers/openai/Streaming.js +53 -4
  39. package/dist/providers/openai/Transcription.d.ts.map +1 -1
  40. package/dist/providers/openai/Transcription.js +9 -2
  41. package/dist/providers/registry.js +1 -1
  42. package/dist/utils/logger.d.ts +8 -0
  43. package/dist/utils/logger.d.ts.map +1 -1
  44. package/dist/utils/logger.js +22 -0
  45. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { APIError } from "../../errors/index.js";
2
+ import { logger } from "../../utils/logger.js";
2
3
  export class DeepSeekStreaming {
3
4
  baseUrl;
4
5
  apiKey;
@@ -22,8 +23,12 @@ export class DeepSeekStreaming {
22
23
  if (response_format)
23
24
  body.response_format = response_format;
24
25
  let done = false;
26
+ // Track tool calls being built across chunks
27
+ const toolCallsMap = new Map();
25
28
  try {
26
- const response = await fetch(`${this.baseUrl}/chat/completions`, {
29
+ const url = `${this.baseUrl}/chat/completions`;
30
+ logger.logRequest("DeepSeek", "POST", url, body);
31
+ const response = await fetch(url, {
27
32
  method: "POST",
28
33
  headers: {
29
34
  "Authorization": `Bearer ${this.apiKey}`,
@@ -37,6 +42,7 @@ export class DeepSeekStreaming {
37
42
  const errorText = await response.text();
38
43
  throw new Error(`DeepSeek API error: ${response.status} - ${errorText}`);
39
44
  }
45
+ logger.debug("DeepSeek streaming started", { status: response.status, statusText: response.statusText });
40
46
  if (!response.body) {
41
47
  throw new Error("No response body for streaming");
42
48
  }
@@ -62,6 +68,18 @@ export class DeepSeekStreaming {
62
68
  const data = trimmed.replace("data: ", "").trim();
63
69
  if (data === "[DONE]") {
64
70
  done = true;
71
+ // Yield final tool calls if any were accumulated
72
+ if (toolCallsMap.size > 0) {
73
+ const toolCalls = Array.from(toolCallsMap.values()).map(tc => ({
74
+ id: tc.id,
75
+ type: "function",
76
+ function: {
77
+ name: tc.function.name,
78
+ arguments: tc.function.arguments
79
+ }
80
+ }));
81
+ yield { content: "", tool_calls: toolCalls, done: true };
82
+ }
65
83
  return;
66
84
  }
67
85
  try {
@@ -70,14 +88,42 @@ export class DeepSeekStreaming {
70
88
  if (json.error) {
71
89
  throw new APIError("DeepSeek", response.status, json.error.message || "Stream error");
72
90
  }
73
- const deltaContent = json.choices?.[0]?.delta?.content;
74
- const deltaReasoning = json.choices?.[0]?.delta?.reasoning_content;
91
+ const delta = json.choices?.[0]?.delta;
92
+ const deltaContent = delta?.content;
93
+ const deltaReasoning = delta?.reasoning_content;
75
94
  if (deltaContent || deltaReasoning) {
76
95
  yield {
77
96
  content: deltaContent || "",
78
97
  reasoning: deltaReasoning || ""
79
98
  };
80
99
  }
100
+ // Handle tool calls delta
101
+ if (delta?.tool_calls) {
102
+ for (const toolCallDelta of delta.tool_calls) {
103
+ const index = toolCallDelta.index;
104
+ if (!toolCallsMap.has(index)) {
105
+ toolCallsMap.set(index, {
106
+ id: toolCallDelta.id || "",
107
+ type: "function",
108
+ function: {
109
+ name: toolCallDelta.function?.name || "",
110
+ arguments: toolCallDelta.function?.arguments || ""
111
+ }
112
+ });
113
+ }
114
+ else {
115
+ const existing = toolCallsMap.get(index);
116
+ if (toolCallDelta.id)
117
+ existing.id = toolCallDelta.id;
118
+ if (toolCallDelta.function?.name) {
119
+ existing.function.name += toolCallDelta.function.name;
120
+ }
121
+ if (toolCallDelta.function?.arguments) {
122
+ existing.function.arguments += toolCallDelta.function.arguments;
123
+ }
124
+ }
125
+ }
126
+ }
81
127
  }
82
128
  catch (e) {
83
129
  // Re-throw APIError
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,gBAAgB,CAAC;AAOlE,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAyF1D,OAAO,CAAC,cAAc;CAwBvB"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,gBAAgB,CAAC;AAQlE,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA4F1D,OAAO,CAAC,cAAc;CAwBvB"}
@@ -2,6 +2,7 @@ import { Capabilities } from "./Capabilities.js";
2
2
  import { handleGeminiError } from "./Errors.js";
3
3
  import { GeminiChatUtils } from "./ChatUtils.js";
4
4
  import { ModelRegistry } from "../../models/ModelRegistry.js";
5
+ import { logger } from "../../utils/logger.js";
5
6
  export class GeminiChat {
6
7
  baseUrl;
7
8
  apiKey;
@@ -49,6 +50,7 @@ export class GeminiChat {
49
50
  },
50
51
  ];
51
52
  }
53
+ logger.logRequest("Gemini", "POST", url, payload);
52
54
  const response = await fetch(url, {
53
55
  method: "POST",
54
56
  headers: {
@@ -60,6 +62,7 @@ export class GeminiChat {
60
62
  await handleGeminiError(response, request.model);
61
63
  }
62
64
  const json = (await response.json());
65
+ logger.logResponse("Gemini", response.status, response.statusText, json);
63
66
  const candidate = json.candidates?.[0];
64
67
  const content = candidate?.content?.parts
65
68
  ?.filter(p => p.text)
@@ -1 +1 @@
1
- {"version":3,"file":"Embeddings.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Embeddings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAIrE,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAwCrE"}
1
+ {"version":3,"file":"Embeddings.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Embeddings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAKrE,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA2CrE"}
@@ -1,4 +1,5 @@
1
1
  import { handleGeminiError } from "./Errors.js";
2
+ import { logger } from "../../utils/logger.js";
2
3
  export class GeminiEmbeddings {
3
4
  baseUrl;
4
5
  apiKey;
@@ -24,6 +25,7 @@ export class GeminiEmbeddings {
24
25
  return item;
25
26
  })
26
27
  };
28
+ logger.logRequest("Gemini", "POST", url, payload);
27
29
  const response = await fetch(url, {
28
30
  method: "POST",
29
31
  headers: { "Content-Type": "application/json" },
@@ -33,6 +35,7 @@ export class GeminiEmbeddings {
33
35
  await handleGeminiError(response, modelId);
34
36
  }
35
37
  const json = (await response.json());
38
+ logger.logResponse("Gemini", response.status, response.statusText, json);
36
39
  const vectors = json.embeddings?.map(e => e.values) || [];
37
40
  return {
38
41
  model: modelId,
@@ -1 +1 @@
1
- {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG7D,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;CA8C7D"}
1
+ {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI7D,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;CAiD7D"}
@@ -1,4 +1,5 @@
1
1
  import { handleGeminiError } from "./Errors.js";
2
+ import { logger } from "../../utils/logger.js";
2
3
  export class GeminiImage {
3
4
  baseUrl;
4
5
  apiKey;
@@ -22,6 +23,7 @@ export class GeminiImage {
22
23
  sampleCount: 1,
23
24
  },
24
25
  };
26
+ logger.logRequest("Gemini", "POST", url, body);
25
27
  const response = await fetch(url, {
26
28
  method: "POST",
27
29
  headers: {
@@ -33,6 +35,7 @@ export class GeminiImage {
33
35
  await handleGeminiError(response, modelId);
34
36
  }
35
37
  const json = await response.json();
38
+ logger.logResponse("Gemini", response.status, response.statusText, json);
36
39
  const imageData = json.predictions?.[0];
37
40
  if (!imageData || !imageData.bytesBase64Encoded) {
38
41
  throw new Error("Unexpected response format from Gemini image generation API");
@@ -1 +1 @@
1
- {"version":3,"file":"Streaming.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAMxD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEtE,OAAO,CACZ,OAAO,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,eAAe,GAC3B,cAAc,CAAC,SAAS,CAAC;IAuG5B,OAAO,CAAC,cAAc;CAwBvB"}
1
+ {"version":3,"file":"Streaming.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAOxD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEtE,OAAO,CACZ,OAAO,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,eAAe,GAC3B,cAAc,CAAC,SAAS,CAAC;IAyI5B,OAAO,CAAC,cAAc;CAwBvB"}
@@ -1,6 +1,7 @@
1
1
  import { Capabilities } from "./Capabilities.js";
2
2
  import { handleGeminiError } from "./Errors.js";
3
3
  import { GeminiChatUtils } from "./ChatUtils.js";
4
+ import { logger } from "../../utils/logger.js";
4
5
  export class GeminiStreaming {
5
6
  baseUrl;
6
7
  apiKey;
@@ -33,8 +34,21 @@ export class GeminiStreaming {
33
34
  if (systemInstructionParts.length > 0) {
34
35
  payload.systemInstruction = { parts: systemInstructionParts };
35
36
  }
37
+ if (request.tools && request.tools.length > 0) {
38
+ payload.tools = [
39
+ {
40
+ functionDeclarations: request.tools.map((t) => ({
41
+ name: t.function.name,
42
+ description: t.function.description,
43
+ parameters: t.function.parameters,
44
+ })),
45
+ },
46
+ ];
47
+ }
36
48
  let done = false;
49
+ const toolCalls = [];
37
50
  try {
51
+ logger.logRequest("Gemini", "POST", url, payload);
38
52
  const response = await fetch(url, {
39
53
  method: "POST",
40
54
  headers: {
@@ -46,6 +60,7 @@ export class GeminiStreaming {
46
60
  if (!response.ok) {
47
61
  await handleGeminiError(response, request.model);
48
62
  }
63
+ logger.debug("Gemini streaming started", { status: response.status, statusText: response.statusText });
49
64
  if (!response.body) {
50
65
  throw new Error("No response body for streaming");
51
66
  }
@@ -54,8 +69,13 @@ export class GeminiStreaming {
54
69
  let buffer = "";
55
70
  while (true) {
56
71
  const { value, done: readerDone } = await reader.read();
57
- if (readerDone)
72
+ if (readerDone) {
73
+ // Yield tool calls if any were collected
74
+ if (toolCalls.length > 0) {
75
+ yield { content: "", tool_calls: toolCalls, done: true };
76
+ }
58
77
  break;
78
+ }
59
79
  buffer += decoder.decode(value, { stream: true });
60
80
  let lineEnd;
61
81
  while ((lineEnd = buffer.indexOf("\n")) !== -1) {
@@ -76,6 +96,17 @@ export class GeminiStreaming {
76
96
  if (part.text) {
77
97
  yield { content: part.text };
78
98
  }
99
+ // Handle function calls
100
+ if (part.functionCall) {
101
+ toolCalls.push({
102
+ id: part.functionCall.name, // Gemini uses name as ID
103
+ type: "function",
104
+ function: {
105
+ name: part.functionCall.name,
106
+ arguments: JSON.stringify(part.functionCall.args || {})
107
+ }
108
+ });
109
+ }
79
110
  }
80
111
  }
81
112
  catch (e) {
@@ -1 +1 @@
1
- {"version":3,"file":"Transcription.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Transcription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAK7E,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAA8E;gBAEvF,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA2D7E"}
1
+ {"version":3,"file":"Transcription.d.ts","sourceRoot":"","sources":["../../../src/providers/gemini/Transcription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAM7E,qBAAa,mBAAmB;IAGlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAA8E;gBAEvF,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA8D7E"}
@@ -1,5 +1,6 @@
1
1
  import { handleGeminiError } from "./Errors.js";
2
2
  import { BinaryUtils } from "../../utils/Binary.js";
3
+ import { logger } from "../../utils/logger.js";
3
4
  export class GeminiTranscription {
4
5
  baseUrl;
5
6
  apiKey;
@@ -42,6 +43,7 @@ export class GeminiTranscription {
42
43
  responseMimeType: "text/plain",
43
44
  },
44
45
  };
46
+ logger.logRequest("Gemini", "POST", url, payload);
45
47
  const response = await fetch(url, {
46
48
  method: "POST",
47
49
  headers: {
@@ -53,6 +55,7 @@ export class GeminiTranscription {
53
55
  await handleGeminiError(response, model);
54
56
  }
55
57
  const json = (await response.json());
58
+ logger.logResponse("Gemini", response.status, response.statusText, json);
56
59
  const text = json.candidates?.[0]?.content?.parts?.map(p => p.text).join("") || "";
57
60
  return {
58
61
  text,
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,gBAAgB,CAAC;AAOlE,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAsD3D"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAS,MAAM,gBAAgB,CAAC;AAQlE,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAuD3D"}
@@ -2,6 +2,7 @@ import { Capabilities } from "./Capabilities.js";
2
2
  import { handleOpenAIError } from "./Errors.js";
3
3
  import { ModelRegistry } from "../../models/ModelRegistry.js";
4
4
  import { buildUrl } from "./utils.js";
5
+ import { logger } from "../../utils/logger.js";
5
6
  export class OpenAIChat {
6
7
  baseUrl;
7
8
  apiKey;
@@ -25,10 +26,9 @@ export class OpenAIChat {
25
26
  body.tools = tools;
26
27
  if (response_format)
27
28
  body.response_format = response_format;
28
- if (process.env.NODELLM_DEBUG === "true") {
29
- console.log(`[OpenAI Request] ${JSON.stringify(body, null, 2)}`);
30
- }
31
- const response = await fetch(buildUrl(this.baseUrl, '/chat/completions'), {
29
+ const url = buildUrl(this.baseUrl, '/chat/completions');
30
+ logger.logRequest("OpenAI", "POST", url, body);
31
+ const response = await fetch(url, {
32
32
  method: "POST",
33
33
  headers: {
34
34
  "Authorization": `Bearer ${this.apiKey}`,
@@ -41,6 +41,7 @@ export class OpenAIChat {
41
41
  await handleOpenAIError(response, request.model);
42
42
  }
43
43
  const json = (await response.json());
44
+ logger.logResponse("OpenAI", response.status, response.statusText, json);
44
45
  const message = json.choices[0]?.message;
45
46
  const content = message?.content ?? null;
46
47
  const tool_calls = message?.tool_calls;
@@ -1 +1 @@
1
- {"version":3,"file":"Embedding.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Embedding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAMrE,qBAAa,eAAe;IAExB,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM;IAClC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM;IAGnC,SAAS,CAAC,eAAe,IAAI,MAAM;IAInC,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMtC,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA2CrE"}
1
+ {"version":3,"file":"Embedding.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Embedding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAOrE,qBAAa,eAAe;IAExB,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM;IAClC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM;IAGnC,SAAS,CAAC,eAAe,IAAI,MAAM;IAInC,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMtC,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA+CrE"}
@@ -2,6 +2,7 @@ import { handleOpenAIError } from "./Errors.js";
2
2
  import { Capabilities } from "./Capabilities.js";
3
3
  import { DEFAULT_MODELS } from "../../constants.js";
4
4
  import { buildUrl } from "./utils.js";
5
+ import { logger } from "../../utils/logger.js";
5
6
  export class OpenAIEmbedding {
6
7
  baseUrl;
7
8
  apiKey;
@@ -30,7 +31,9 @@ export class OpenAIEmbedding {
30
31
  if (request.user) {
31
32
  body.user = request.user;
32
33
  }
33
- const response = await fetch(buildUrl(this.baseUrl, '/embeddings'), {
34
+ const url = buildUrl(this.baseUrl, '/embeddings');
35
+ logger.logRequest("OpenAI", "POST", url, body);
36
+ const response = await fetch(url, {
34
37
  method: "POST",
35
38
  headers: {
36
39
  "Authorization": `Bearer ${this.apiKey}`,
@@ -42,6 +45,7 @@ export class OpenAIEmbedding {
42
45
  await handleOpenAIError(response, request.model || DEFAULT_MODELS.EMBEDDING);
43
46
  }
44
47
  const { data, model: responseModel, usage } = await response.json();
48
+ logger.logResponse("OpenAI", response.status, response.statusText, { data, model: responseModel, usage });
45
49
  // Extract vectors from the response
46
50
  const vectors = data.map((item) => item.embedding);
47
51
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI7D,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;CAmC7D"}
1
+ {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAK7D,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;CAuC7D"}
@@ -1,5 +1,6 @@
1
1
  import { handleOpenAIError } from "./Errors.js";
2
2
  import { buildUrl } from "./utils.js";
3
+ import { logger } from "../../utils/logger.js";
3
4
  export class OpenAIImage {
4
5
  baseUrl;
5
6
  apiKey;
@@ -15,7 +16,9 @@ export class OpenAIImage {
15
16
  quality: request.quality || "standard",
16
17
  n: request.n || 1,
17
18
  };
18
- const response = await fetch(buildUrl(this.baseUrl, '/images/generations'), {
19
+ const url = buildUrl(this.baseUrl, '/images/generations');
20
+ logger.logRequest("OpenAI", "POST", url, body);
21
+ const response = await fetch(url, {
19
22
  method: "POST",
20
23
  headers: {
21
24
  "Authorization": `Bearer ${this.apiKey}`,
@@ -27,6 +30,7 @@ export class OpenAIImage {
27
30
  await handleOpenAIError(response, request.model);
28
31
  }
29
32
  const json = await response.json();
33
+ logger.logResponse("OpenAI", response.status, response.statusText, json);
30
34
  const data = json.data?.[0];
31
35
  if (!data) {
32
36
  throw new Error("OpenAI returned empty image response");
@@ -1 +1 @@
1
- {"version":3,"file":"Moderation.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Moderation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAKvE,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAmBvE"}
1
+ {"version":3,"file":"Moderation.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Moderation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAMvE,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA0BvE"}
@@ -1,6 +1,7 @@
1
1
  import { handleOpenAIError } from "./Errors.js";
2
2
  import { DEFAULT_MODELS } from "../../constants.js";
3
3
  import { buildUrl } from "./utils.js";
4
+ import { logger } from "../../utils/logger.js";
4
5
  export class OpenAIModeration {
5
6
  baseUrl;
6
7
  apiKey;
@@ -9,20 +10,25 @@ export class OpenAIModeration {
9
10
  this.apiKey = apiKey;
10
11
  }
11
12
  async execute(request) {
12
- const response = await fetch(buildUrl(this.baseUrl, '/moderations'), {
13
+ const body = {
14
+ input: request.input,
15
+ model: request.model || DEFAULT_MODELS.MODERATION,
16
+ };
17
+ const url = buildUrl(this.baseUrl, '/moderations');
18
+ logger.logRequest("OpenAI", "POST", url, body);
19
+ const response = await fetch(url, {
13
20
  method: "POST",
14
21
  headers: {
15
22
  "Authorization": `Bearer ${this.apiKey}`,
16
23
  "Content-Type": "application/json",
17
24
  },
18
- body: JSON.stringify({
19
- input: request.input,
20
- model: request.model || DEFAULT_MODELS.MODERATION,
21
- }),
25
+ body: JSON.stringify(body),
22
26
  });
23
27
  if (!response.ok) {
24
28
  await handleOpenAIError(response, request.model || DEFAULT_MODELS.MODERATION);
25
29
  }
26
- return (await response.json());
30
+ const json = await response.json();
31
+ logger.logResponse("OpenAI", response.status, response.statusText, json);
32
+ return json;
27
33
  }
28
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Streaming.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAMxD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEtE,OAAO,CACZ,OAAO,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,eAAe,GAC3B,cAAc,CAAC,SAAS,CAAC;CAgH7B"}
1
+ {"version":3,"file":"Streaming.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAOxD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEtE,OAAO,CACZ,OAAO,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,eAAe,GAC3B,cAAc,CAAC,SAAS,CAAC;CAqK7B"}
@@ -2,6 +2,7 @@ import { Capabilities } from "./Capabilities.js";
2
2
  import { handleOpenAIError } from "./Errors.js";
3
3
  import { buildUrl } from "./utils.js";
4
4
  import { APIError } from "../../errors/index.js";
5
+ import { logger } from "../../utils/logger.js";
5
6
  export class OpenAIStreaming {
6
7
  baseUrl;
7
8
  apiKey;
@@ -26,9 +27,16 @@ export class OpenAIStreaming {
26
27
  if (request.response_format) {
27
28
  body.response_format = request.response_format;
28
29
  }
30
+ if (request.tools && request.tools.length > 0) {
31
+ body.tools = request.tools;
32
+ }
29
33
  let done = false;
34
+ // Track tool calls being built across chunks
35
+ const toolCallsMap = new Map();
30
36
  try {
31
- const response = await fetch(buildUrl(this.baseUrl, '/chat/completions'), {
37
+ const url = buildUrl(this.baseUrl, '/chat/completions');
38
+ logger.logRequest("OpenAI", "POST", url, body);
39
+ const response = await fetch(url, {
32
40
  method: "POST",
33
41
  headers: {
34
42
  "Authorization": `Bearer ${this.apiKey}`,
@@ -41,6 +49,7 @@ export class OpenAIStreaming {
41
49
  if (!response.ok) {
42
50
  await handleOpenAIError(response, request.model);
43
51
  }
52
+ logger.debug("OpenAI streaming started", { status: response.status, statusText: response.statusText });
44
53
  if (!response.body) {
45
54
  throw new Error("No response body for streaming");
46
55
  }
@@ -66,6 +75,18 @@ export class OpenAIStreaming {
66
75
  const data = trimmed.replace("data: ", "").trim();
67
76
  if (data === "[DONE]") {
68
77
  done = true;
78
+ // Yield final tool calls if any were accumulated
79
+ if (toolCallsMap.size > 0) {
80
+ const toolCalls = Array.from(toolCallsMap.values()).map(tc => ({
81
+ id: tc.id,
82
+ type: "function",
83
+ function: {
84
+ name: tc.function.name,
85
+ arguments: tc.function.arguments
86
+ }
87
+ }));
88
+ yield { content: "", tool_calls: toolCalls, done: true };
89
+ }
69
90
  return;
70
91
  }
71
92
  try {
@@ -74,9 +95,37 @@ export class OpenAIStreaming {
74
95
  if (json.error) {
75
96
  throw new APIError("OpenAI", response.status, json.error.message || "Stream error");
76
97
  }
77
- const delta = json.choices?.[0]?.delta?.content;
78
- if (delta) {
79
- yield { content: delta };
98
+ const delta = json.choices?.[0]?.delta;
99
+ // Handle content delta
100
+ if (delta?.content) {
101
+ yield { content: delta.content };
102
+ }
103
+ // Handle tool calls delta
104
+ if (delta?.tool_calls) {
105
+ for (const toolCallDelta of delta.tool_calls) {
106
+ const index = toolCallDelta.index;
107
+ if (!toolCallsMap.has(index)) {
108
+ toolCallsMap.set(index, {
109
+ id: toolCallDelta.id || "",
110
+ type: "function",
111
+ function: {
112
+ name: toolCallDelta.function?.name || "",
113
+ arguments: toolCallDelta.function?.arguments || ""
114
+ }
115
+ });
116
+ }
117
+ else {
118
+ const existing = toolCallsMap.get(index);
119
+ if (toolCallDelta.id)
120
+ existing.id = toolCallDelta.id;
121
+ if (toolCallDelta.function?.name) {
122
+ existing.function.name += toolCallDelta.function.name;
123
+ }
124
+ if (toolCallDelta.function?.arguments) {
125
+ existing.function.arguments += toolCallDelta.function.arguments;
126
+ }
127
+ }
128
+ }
80
129
  }
81
130
  }
82
131
  catch (e) {
@@ -1 +1 @@
1
- {"version":3,"file":"Transcription.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Transcription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAM7E,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;YAU9D,oBAAoB;YA4CpB,iBAAiB;CAwHhC"}
1
+ {"version":3,"file":"Transcription.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/Transcription.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAO7E,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAEvE,OAAO,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;YAU9D,oBAAoB;YAgDpB,iBAAiB;CA4HhC"}
@@ -2,6 +2,7 @@ import { handleOpenAIError } from "./Errors.js";
2
2
  import { AudioUtils } from "../../utils/audio.js";
3
3
  import { DEFAULT_MODELS } from "../../constants.js";
4
4
  import { buildUrl } from "./utils.js";
5
+ import { logger } from "../../utils/logger.js";
5
6
  export class OpenAITranscription {
6
7
  baseUrl;
7
8
  apiKey;
@@ -30,7 +31,9 @@ export class OpenAITranscription {
30
31
  if (request.language) {
31
32
  formData.append("language", request.language);
32
33
  }
33
- const response = await fetch(buildUrl(this.baseUrl, '/audio/transcriptions'), {
34
+ const url = buildUrl(this.baseUrl, '/audio/transcriptions');
35
+ logger.logRequest("OpenAI", "POST", url, { model: request.model || DEFAULT_MODELS.TRANSCRIPTION, file: fileName });
36
+ const response = await fetch(url, {
34
37
  method: "POST",
35
38
  headers: {
36
39
  "Authorization": `Bearer ${this.apiKey}`,
@@ -41,6 +44,7 @@ export class OpenAITranscription {
41
44
  await handleOpenAIError(response, request.model || DEFAULT_MODELS.TRANSCRIPTION);
42
45
  }
43
46
  const json = (await response.json());
47
+ logger.logResponse("OpenAI", response.status, response.statusText, json);
44
48
  return {
45
49
  text: json.text,
46
50
  model: json.model || request.model || DEFAULT_MODELS.TRANSCRIPTION,
@@ -120,7 +124,9 @@ export class OpenAITranscription {
120
124
  }
121
125
  ]
122
126
  };
123
- const response = await fetch(buildUrl(this.baseUrl, '/chat/completions'), {
127
+ const url = buildUrl(this.baseUrl, '/chat/completions');
128
+ logger.logRequest("OpenAI", "POST", url, body);
129
+ const response = await fetch(url, {
124
130
  method: "POST",
125
131
  headers: {
126
132
  "Authorization": `Bearer ${this.apiKey}`,
@@ -132,6 +138,7 @@ export class OpenAITranscription {
132
138
  await handleOpenAIError(response, actualModel);
133
139
  }
134
140
  const json = await response.json();
141
+ logger.logResponse("OpenAI", response.status, response.statusText, json);
135
142
  const content = json.choices[0]?.message?.content || "";
136
143
  let text = content;
137
144
  let segments = [];
@@ -21,7 +21,7 @@ class ProviderRegistry {
21
21
  resolve(name) {
22
22
  const factory = this.providers.get(name);
23
23
  if (!factory) {
24
- throw new Error(`Unknown NodeLLM provider '${name}'`);
24
+ throw new Error(`Unknown LLM provider '${name}'`);
25
25
  }
26
26
  return factory();
27
27
  }
@@ -4,6 +4,14 @@
4
4
  declare class Logger {
5
5
  private isDebugEnabled;
6
6
  debug(message: string, data?: any): void;
7
+ /**
8
+ * Log HTTP request details for debugging
9
+ */
10
+ logRequest(provider: string, method: string, url: string, body?: any): void;
11
+ /**
12
+ * Log HTTP response details for debugging
13
+ */
14
+ logResponse(provider: string, status: number, statusText: string, body?: any): void;
7
15
  warn(message: string): void;
8
16
  error(message: string, error?: Error): void;
9
17
  info(message: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAM,MAAM;IACV,OAAO,CAAC,cAAc;IAItB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAOxC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;IAI3C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5B;AAED,eAAO,MAAM,MAAM,QAAe,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAM,MAAM;IACV,OAAO,CAAC,cAAc;IAItB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAOxC;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAS3E;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IASnF,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;IAI3C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5B;AAED,eAAO,MAAM,MAAM,QAAe,CAAC"}
@@ -11,6 +11,28 @@ class Logger {
11
11
  console.log(`[NodeLLM Debug] ${message}${formattedData}`);
12
12
  }
13
13
  }
14
+ /**
15
+ * Log HTTP request details for debugging
16
+ */
17
+ logRequest(provider, method, url, body) {
18
+ if (this.isDebugEnabled()) {
19
+ console.log(`[NodeLLM] [${provider}] Request: ${method} ${url}`);
20
+ if (body) {
21
+ console.log(JSON.stringify(body, null, 2));
22
+ }
23
+ }
24
+ }
25
+ /**
26
+ * Log HTTP response details for debugging
27
+ */
28
+ logResponse(provider, status, statusText, body) {
29
+ if (this.isDebugEnabled()) {
30
+ console.log(`[NodeLLM] [${provider}] Response: ${status} ${statusText}`);
31
+ if (body) {
32
+ console.log(JSON.stringify(body, null, 2));
33
+ }
34
+ }
35
+ }
14
36
  warn(message) {
15
37
  console.warn(`[NodeLLM] ${message}`);
16
38
  }