@node-llm/core 0.1.0 → 0.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.
- package/README.md +119 -132
- package/dist/chat/Chat.d.ts +29 -10
- package/dist/chat/Chat.d.ts.map +1 -1
- package/dist/chat/Chat.js +61 -23
- package/dist/chat/Content.d.ts +1 -1
- package/dist/chat/Content.d.ts.map +1 -1
- package/dist/chat/Message.d.ts +2 -0
- package/dist/chat/Message.d.ts.map +1 -1
- package/dist/chat/Stream.d.ts +21 -0
- package/dist/chat/Stream.d.ts.map +1 -0
- package/dist/chat/Stream.js +57 -0
- package/dist/errors/index.d.ts +66 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +97 -0
- package/dist/executor/Executor.d.ts.map +1 -1
- package/dist/executor/Executor.js +4 -1
- package/dist/image/GeneratedImage.d.ts +25 -0
- package/dist/image/GeneratedImage.d.ts.map +1 -0
- package/dist/image/GeneratedImage.js +59 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/llm.d.ts +6 -0
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +15 -1
- package/dist/providers/Provider.d.ts +21 -0
- package/dist/providers/Provider.d.ts.map +1 -1
- package/dist/providers/openai/Chat.d.ts.map +1 -1
- package/dist/providers/openai/Chat.js +9 -3
- package/dist/providers/openai/Errors.d.ts +2 -0
- package/dist/providers/openai/Errors.d.ts.map +1 -0
- package/dist/providers/openai/Errors.js +34 -0
- package/dist/providers/openai/Image.d.ts +8 -0
- package/dist/providers/openai/Image.d.ts.map +1 -0
- package/dist/providers/openai/Image.js +39 -0
- package/dist/providers/openai/OpenAIProvider.d.ts +3 -1
- package/dist/providers/openai/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/openai/OpenAIProvider.js +6 -0
- package/dist/providers/openai/Streaming.d.ts.map +1 -1
- package/dist/providers/openai/Streaming.js +4 -0
- package/dist/providers/openai/types.d.ts +8 -0
- package/dist/providers/openai/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,215 +1,202 @@
|
|
|
1
|
-
# node-llm
|
|
1
|
+
# @node-llm/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@node-llm/core)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
- clean abstractions
|
|
7
|
-
- minimal magic
|
|
8
|
-
- streaming-first design
|
|
9
|
-
- no SDK lock-in
|
|
7
|
+
A provider-agnostic LLM core for Node.js, heavily inspired by the elegant design of [ruby-llm](https://github.com/crmne/ruby_llm).
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
`node-llm` focuses on clean abstractions, minimal magic, and a streaming-first design. It provides a unified interface to interact with various LLM providers without being locked into their specific SDKs.
|
|
12
10
|
|
|
13
11
|
---
|
|
14
12
|
|
|
15
|
-
## Features
|
|
13
|
+
## 🚀 Features
|
|
16
14
|
|
|
17
|
-
- **Provider-
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **Retry support**: Configurable retry logic at the execution layer.
|
|
25
|
-
- **Strict ESM and TypeScript**: Modern, type-safe development.
|
|
15
|
+
- **Provider-Agnostic**: Switch between OpenAI, Anthropic, and others with a single line of config.
|
|
16
|
+
- **Streaming-First**: Native `AsyncIterator` support for real-time token delivery.
|
|
17
|
+
- **Tool Calling**: Automatic execution loop for model-requested functions.
|
|
18
|
+
- **Multi-modal & Smart Files**: Built-in support for Vision (images), Audio, and Text files.
|
|
19
|
+
- **Fluent API**: Chainable methods like `.withTool()` for dynamic registration.
|
|
20
|
+
- **Resilient**: Configurable retry logic at the execution layer.
|
|
21
|
+
- **Type-Safe**: Written in TypeScript with full ESM support.
|
|
26
22
|
|
|
27
23
|
---
|
|
28
24
|
|
|
29
|
-
## Installation
|
|
25
|
+
## 📦 Installation
|
|
30
26
|
|
|
31
27
|
```bash
|
|
28
|
+
npm install @node-llm/core
|
|
29
|
+
# or
|
|
32
30
|
pnpm add @node-llm/core
|
|
33
31
|
```
|
|
34
32
|
|
|
35
33
|
---
|
|
36
34
|
|
|
37
|
-
##
|
|
35
|
+
## 🛠️ Quick Start
|
|
38
36
|
|
|
39
|
-
###
|
|
37
|
+
### 1. Configure the Provider
|
|
40
38
|
|
|
41
|
-
```
|
|
42
|
-
|
|
39
|
+
```ts
|
|
40
|
+
import { LLM } from "@node-llm/core";
|
|
41
|
+
import "dotenv/config";
|
|
42
|
+
|
|
43
|
+
LLM.configure({
|
|
44
|
+
provider: "openai", // Uses OPENAI_API_KEY from env
|
|
45
|
+
retry: { attempts: 3, delayMs: 500 }
|
|
46
|
+
});
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
### 2. Basic Chat
|
|
46
50
|
|
|
47
51
|
```ts
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
const chat = LLM.chat("gpt-4o-mini", {
|
|
53
|
+
systemPrompt: "You are a helpful assistant."
|
|
54
|
+
});
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
const response = await chat.ask("What is Node.js?");
|
|
57
|
+
|
|
58
|
+
// Use as a string directly
|
|
59
|
+
console.log(response);
|
|
60
|
+
|
|
61
|
+
// Or access metadata (RubyLLM style)
|
|
62
|
+
console.log(response.content);
|
|
63
|
+
console.log(`Model: ${response.model_id}`);
|
|
64
|
+
console.log(`Tokens: ${response.input_tokens} in, ${response.output_tokens} out`);
|
|
65
|
+
```
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
### 3. Streaming Responses
|
|
54
68
|
|
|
55
69
|
```ts
|
|
56
|
-
|
|
70
|
+
for await (const chunk of chat.stream("Write a poem")) {
|
|
71
|
+
process.stdout.write(chunk.content);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
57
74
|
|
|
58
|
-
|
|
59
|
-
provider: "openai",
|
|
60
|
-
});
|
|
75
|
+
### 4. Image Generation (Paint)
|
|
61
76
|
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
Generate images and interact with them using a rich API.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const image = await LLM.paint("a sunset over mountains", {
|
|
81
|
+
model: "dall-e-3"
|
|
64
82
|
});
|
|
65
83
|
|
|
66
|
-
|
|
67
|
-
console.log(
|
|
84
|
+
// Use as a URL string
|
|
85
|
+
console.log(`URL: ${image}`);
|
|
68
86
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
console.log(
|
|
87
|
+
// Or use rich methods
|
|
88
|
+
await image.save("sunset.png");
|
|
89
|
+
console.log(`Format: ${image.mimeType}`);
|
|
72
90
|
```
|
|
73
91
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
## Streaming Responses
|
|
92
|
+
### 5. Token Usage Tracking
|
|
77
93
|
|
|
78
|
-
|
|
94
|
+
Track tokens for individual turns or the entire conversation.
|
|
79
95
|
|
|
80
96
|
```ts
|
|
81
|
-
|
|
97
|
+
const response = await chat.ask("Hello!");
|
|
82
98
|
|
|
83
|
-
|
|
99
|
+
console.log(response.input_tokens); // 10
|
|
100
|
+
console.log(response.output_tokens); // 5
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
// Access aggregated usage for the whole session
|
|
103
|
+
console.log(chat.totalUsage.total_tokens);
|
|
104
|
+
```
|
|
86
105
|
|
|
87
|
-
|
|
106
|
+
---
|
|
88
107
|
|
|
89
|
-
|
|
90
|
-
process.stdout.write(token);
|
|
91
|
-
full += token;
|
|
92
|
-
}
|
|
108
|
+
## 📚 Examples
|
|
93
109
|
|
|
94
|
-
|
|
110
|
+
Check the [examples](./examples) directory for focused scripts organized by provider:
|
|
111
|
+
|
|
112
|
+
### OpenAI Examples
|
|
113
|
+
| Example | Description |
|
|
114
|
+
| :--- | :--- |
|
|
115
|
+
| [Basic Chat](./examples/openai/01-basic-chat.mjs) | Simple completion request |
|
|
116
|
+
| [Streaming](./examples/openai/02-streaming.mjs) | Real-time token streaming |
|
|
117
|
+
| [Tool Calling](./examples/openai/03-tool-calling.mjs) | Automatic tool execution loop |
|
|
118
|
+
| [Vision](./examples/openai/04-vision.mjs) | Image analysis |
|
|
119
|
+
| [List Models](./examples/openai/05-list-models.mjs) | Enumerate available models |
|
|
120
|
+
| [Paint](./examples/openai/06-paint.mjs) | Image generation with DALL-E |
|
|
121
|
+
| [Image Features](./examples/openai/07-image-features.mjs) | Saving and processing generated images |
|
|
122
|
+
| [Token Usage](./examples/openai/08-token-usage.mjs) | Detailed stats for turns and conversations |
|
|
123
|
+
|
|
124
|
+
To run an example:
|
|
125
|
+
```bash
|
|
126
|
+
node examples/openai/01-basic-chat.mjs
|
|
95
127
|
```
|
|
96
128
|
|
|
97
129
|
---
|
|
98
130
|
|
|
99
|
-
##
|
|
131
|
+
## 🔌 Advanced Usage
|
|
100
132
|
|
|
101
|
-
|
|
133
|
+
### Tool Calling (Function Calling)
|
|
102
134
|
|
|
103
|
-
|
|
104
|
-
import { LLM, Tool } from "@node-llm/core";
|
|
135
|
+
Define your tools and let the library handle the execution loop automatically.
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
const weatherTool
|
|
137
|
+
```ts
|
|
138
|
+
const weatherTool = {
|
|
108
139
|
type: 'function',
|
|
109
140
|
function: {
|
|
110
141
|
name: 'get_weather',
|
|
111
|
-
description: 'Get the current weather for a location',
|
|
112
142
|
parameters: {
|
|
113
143
|
type: 'object',
|
|
114
|
-
properties: {
|
|
115
|
-
location: { type: 'string', description: 'The city and state, e.g. San Francisco, CA' },
|
|
116
|
-
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
|
|
117
|
-
},
|
|
118
|
-
required: ['location']
|
|
144
|
+
properties: { location: { type: 'string' } }
|
|
119
145
|
}
|
|
120
146
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Call your real API here
|
|
124
|
-
return JSON.stringify({ location, temperature: 22, unit, condition: "Sunny" });
|
|
147
|
+
handler: async ({ location }) => {
|
|
148
|
+
return JSON.stringify({ location, temp: 22, unit: 'celsius' });
|
|
125
149
|
}
|
|
126
150
|
};
|
|
127
151
|
|
|
128
|
-
//
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// OR Option B: via fluent API (Ruby-LLM style)
|
|
134
|
-
const chat2 = LLM.chat("gpt-4o-mini")
|
|
135
|
-
.withTool(weatherTool);
|
|
136
|
-
|
|
137
|
-
// 4. Ask a question
|
|
138
|
-
const reply = await chat.ask("What is the weather in London?");
|
|
139
|
-
console.log(reply);
|
|
140
|
-
// Output: "The weather in London is currently 22°C and sunny."
|
|
152
|
+
// Use the fluent API to add tools on the fly
|
|
153
|
+
const reply = await chat
|
|
154
|
+
.withTool(weatherTool)
|
|
155
|
+
.ask("What is the weather in London?");
|
|
141
156
|
```
|
|
142
157
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
## File & Multi-modal Support
|
|
158
|
+
### Multi-modal & File Support
|
|
146
159
|
|
|
147
|
-
|
|
160
|
+
Pass local paths or URLs directly. The library handles reading, MIME detection, and encoding.
|
|
148
161
|
|
|
149
162
|
```ts
|
|
150
|
-
//
|
|
151
|
-
await chat.ask("
|
|
152
|
-
files: ["./
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Text files (content is automatically appended to prompt)
|
|
156
|
-
await chat.ask("Summarize this code", {
|
|
157
|
-
files: ["./app.ts"]
|
|
163
|
+
// Vision
|
|
164
|
+
await chat.ask("What's in this image?", {
|
|
165
|
+
files: ["./screenshot.png"]
|
|
158
166
|
});
|
|
159
167
|
|
|
160
|
-
//
|
|
161
|
-
await chat.ask("
|
|
162
|
-
files: ["https://example.com/photo.png"]
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Audio files (OpenAI input_audio support)
|
|
166
|
-
await chat.ask("Transcribe this meeting", {
|
|
168
|
+
// Audio
|
|
169
|
+
await chat.ask("Transcribe this", {
|
|
167
170
|
files: ["./meeting.mp3"]
|
|
168
171
|
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## Retry Support
|
|
174
|
-
|
|
175
|
-
Retries are applied before chat execution, not inside providers.
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
retry: {
|
|
181
|
-
attempts: 3,
|
|
182
|
-
delayMs: 500,
|
|
183
|
-
},
|
|
173
|
+
// Text/Code Analysis
|
|
174
|
+
await chat.ask("Explain this code", {
|
|
175
|
+
files: ["./app.ts"]
|
|
184
176
|
});
|
|
185
177
|
```
|
|
186
178
|
|
|
187
|
-
Retry behavior:
|
|
188
|
-
- Only transient failures are retried
|
|
189
|
-
- Chat and providers remain clean
|
|
190
|
-
- Designed for future timeouts and circuit breakers
|
|
191
|
-
|
|
192
179
|
---
|
|
193
180
|
|
|
194
|
-
##
|
|
181
|
+
## 📋 Supported Providers
|
|
195
182
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
183
|
+
| Provider | Status | Notes |
|
|
184
|
+
| :--- | :--- | :--- |
|
|
185
|
+
| **OpenAI** | ✅ Supported | Chat, Streaming, Tools, Vision, Audio, Images (DALL-E) |
|
|
186
|
+
| **Anthropic** | 🏗️ Roadmap | Coming soon |
|
|
187
|
+
| **Azure OpenAI** | 🏗️ Roadmap | Coming soon |
|
|
201
188
|
|
|
202
189
|
---
|
|
203
190
|
|
|
204
|
-
## Design Philosophy
|
|
191
|
+
## 🧠 Design Philosophy
|
|
205
192
|
|
|
206
|
-
- **Explicit over
|
|
207
|
-
- **
|
|
208
|
-
- **
|
|
209
|
-
- **Production Ready**: Built
|
|
193
|
+
- **Explicit over Implicit**: No hidden side effects.
|
|
194
|
+
- **Minimal Dependencies**: Lightweight core with zero bloat.
|
|
195
|
+
- **Developer Experience**: Inspired by Ruby's elegance, built for Node's performance.
|
|
196
|
+
- **Production Ready**: Built-in retries and strict type checking.
|
|
210
197
|
|
|
211
198
|
---
|
|
212
199
|
|
|
213
|
-
## License
|
|
200
|
+
## 📄 License
|
|
214
201
|
|
|
215
|
-
MIT
|
|
202
|
+
MIT © [node-llm contributors]
|
package/dist/chat/Chat.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { Message } from "./Message.js";
|
|
2
2
|
import { ChatOptions } from "./ChatOptions.js";
|
|
3
|
-
import { Provider } from "../providers/Provider.js";
|
|
3
|
+
import { Provider, Usage } from "../providers/Provider.js";
|
|
4
|
+
export interface AskOptions {
|
|
5
|
+
images?: string[];
|
|
6
|
+
files?: string[];
|
|
7
|
+
temperature?: number;
|
|
8
|
+
maxTokens?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Enhanced string that includes token usage metadata.
|
|
12
|
+
* Behaves like a regular string but has .usage and .input_tokens etc.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ChatResponseString extends String {
|
|
15
|
+
readonly usage: Usage;
|
|
16
|
+
readonly model: string;
|
|
17
|
+
constructor(content: string, usage: Usage, model: string);
|
|
18
|
+
get input_tokens(): number;
|
|
19
|
+
get output_tokens(): number;
|
|
20
|
+
get total_tokens(): number;
|
|
21
|
+
get cached_tokens(): number | undefined;
|
|
22
|
+
get content(): string;
|
|
23
|
+
get model_id(): string;
|
|
24
|
+
toString(): string;
|
|
25
|
+
}
|
|
4
26
|
export declare class Chat {
|
|
5
27
|
private readonly provider;
|
|
6
28
|
private readonly model;
|
|
@@ -12,6 +34,10 @@ export declare class Chat {
|
|
|
12
34
|
* Read-only access to message history
|
|
13
35
|
*/
|
|
14
36
|
get history(): readonly Message[];
|
|
37
|
+
/**
|
|
38
|
+
* Aggregate usage across the entire conversation
|
|
39
|
+
*/
|
|
40
|
+
get totalUsage(): Usage;
|
|
15
41
|
/**
|
|
16
42
|
* Add a tool to the chat session (fluent API)
|
|
17
43
|
*/
|
|
@@ -19,17 +45,10 @@ export declare class Chat {
|
|
|
19
45
|
/**
|
|
20
46
|
* Ask the model a question
|
|
21
47
|
*/
|
|
22
|
-
ask(content: string, options?:
|
|
23
|
-
images?: string[];
|
|
24
|
-
files?: string[];
|
|
25
|
-
temperature?: number;
|
|
26
|
-
maxTokens?: number;
|
|
27
|
-
}): Promise<string>;
|
|
48
|
+
ask(content: string, options?: AskOptions): Promise<ChatResponseString>;
|
|
28
49
|
/**
|
|
29
50
|
* Streams the model's response to a user question.
|
|
30
|
-
* @param content The user's question to send to the model.
|
|
31
|
-
* @returns An async generator yielding chunks of the assistant's response as strings.
|
|
32
51
|
*/
|
|
33
|
-
stream(content: string): AsyncGenerator<
|
|
52
|
+
stream(content: string): AsyncGenerator<import("../providers/Provider.js").ChatChunk, void, unknown>;
|
|
34
53
|
}
|
|
35
54
|
//# sourceMappingURL=Chat.d.ts.map
|
package/dist/chat/Chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../src/chat/Chat.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../src/chat/Chat.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAK3D,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,MAAM;aAG1B,KAAK,EAAE,KAAK;aACZ,KAAK,EAAE,MAAM;gBAF7B,OAAO,EAAE,MAAM,EACC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM;IAK/B,IAAI,YAAY,WAAsC;IACtD,IAAI,aAAa,WAAuC;IACxD,IAAI,YAAY,WAAsC;IACtD,IAAI,aAAa,uBAAuC;IAExD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,QAAQ;CAGT;AAED,qBAAa,IAAI;IAKb,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,QAAQ,CAAW;gBAGR,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,WAAgB;IAmB5C;;OAEG;IACH,IAAI,OAAO,IAAI,SAAS,OAAO,EAAE,CAEhC;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,KAAK,CAatB;IAED;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAQzB;;OAEG;IACG,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA6G7E;;OAEG;IACI,MAAM,CAAC,OAAO,EAAE,MAAM;CAI9B"}
|
package/dist/chat/Chat.js
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import { FileLoader } from "../utils/FileLoader.js";
|
|
2
2
|
import { Executor } from "../executor/Executor.js";
|
|
3
3
|
import { LLM } from "../llm.js";
|
|
4
|
+
import { Stream } from "./Stream.js";
|
|
5
|
+
/**
|
|
6
|
+
* Enhanced string that includes token usage metadata.
|
|
7
|
+
* Behaves like a regular string but has .usage and .input_tokens etc.
|
|
8
|
+
*/
|
|
9
|
+
export class ChatResponseString extends String {
|
|
10
|
+
usage;
|
|
11
|
+
model;
|
|
12
|
+
constructor(content, usage, model) {
|
|
13
|
+
super(content);
|
|
14
|
+
this.usage = usage;
|
|
15
|
+
this.model = model;
|
|
16
|
+
}
|
|
17
|
+
get input_tokens() { return this.usage.input_tokens; }
|
|
18
|
+
get output_tokens() { return this.usage.output_tokens; }
|
|
19
|
+
get total_tokens() { return this.usage.total_tokens; }
|
|
20
|
+
get cached_tokens() { return this.usage.cached_tokens; }
|
|
21
|
+
get content() {
|
|
22
|
+
return this.valueOf();
|
|
23
|
+
}
|
|
24
|
+
get model_id() {
|
|
25
|
+
return this.model;
|
|
26
|
+
}
|
|
27
|
+
toString() {
|
|
28
|
+
return this.valueOf();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
4
31
|
export class Chat {
|
|
5
32
|
provider;
|
|
6
33
|
model;
|
|
@@ -28,6 +55,20 @@ export class Chat {
|
|
|
28
55
|
get history() {
|
|
29
56
|
return this.messages;
|
|
30
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Aggregate usage across the entire conversation
|
|
60
|
+
*/
|
|
61
|
+
get totalUsage() {
|
|
62
|
+
return this.messages.reduce((acc, msg) => {
|
|
63
|
+
if (msg.usage) {
|
|
64
|
+
acc.input_tokens += msg.usage.input_tokens;
|
|
65
|
+
acc.output_tokens += msg.usage.output_tokens;
|
|
66
|
+
acc.total_tokens += msg.usage.total_tokens;
|
|
67
|
+
acc.cached_tokens = (acc.cached_tokens ?? 0) + (msg.usage.cached_tokens ?? 0);
|
|
68
|
+
}
|
|
69
|
+
return acc;
|
|
70
|
+
}, { input_tokens: 0, output_tokens: 0, total_tokens: 0, cached_tokens: 0 });
|
|
71
|
+
}
|
|
31
72
|
/**
|
|
32
73
|
* Add a tool to the chat session (fluent API)
|
|
33
74
|
*/
|
|
@@ -71,11 +112,24 @@ export class Chat {
|
|
|
71
112
|
temperature: options?.temperature ?? this.options.temperature,
|
|
72
113
|
max_tokens: options?.maxTokens ?? this.options.maxTokens,
|
|
73
114
|
};
|
|
115
|
+
let totalUsage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
116
|
+
const trackUsage = (u) => {
|
|
117
|
+
if (u) {
|
|
118
|
+
totalUsage.input_tokens += u.input_tokens;
|
|
119
|
+
totalUsage.output_tokens += u.output_tokens;
|
|
120
|
+
totalUsage.total_tokens += u.total_tokens;
|
|
121
|
+
if (u.cached_tokens) {
|
|
122
|
+
totalUsage.cached_tokens = (totalUsage.cached_tokens ?? 0) + u.cached_tokens;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
74
126
|
let response = await this.executor.executeChat(executeOptions);
|
|
127
|
+
trackUsage(response.usage);
|
|
75
128
|
this.messages.push({
|
|
76
129
|
role: "assistant",
|
|
77
|
-
content: response.content,
|
|
130
|
+
content: new ChatResponseString(response.content ?? "", response.usage ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 }, this.model),
|
|
78
131
|
tool_calls: response.tool_calls,
|
|
132
|
+
usage: response.usage,
|
|
79
133
|
});
|
|
80
134
|
while (response.tool_calls && response.tool_calls.length > 0) {
|
|
81
135
|
for (const toolCall of response.tool_calls) {
|
|
@@ -111,37 +165,21 @@ export class Chat {
|
|
|
111
165
|
messages: this.messages,
|
|
112
166
|
tools: this.options.tools,
|
|
113
167
|
});
|
|
168
|
+
trackUsage(response.usage);
|
|
114
169
|
this.messages.push({
|
|
115
170
|
role: "assistant",
|
|
116
|
-
content: response.content,
|
|
171
|
+
content: new ChatResponseString(response.content ?? "", response.usage ?? { input_tokens: 0, output_tokens: 0, total_tokens: 0 }, this.model),
|
|
117
172
|
tool_calls: response.tool_calls,
|
|
173
|
+
usage: response.usage,
|
|
118
174
|
});
|
|
119
175
|
}
|
|
120
|
-
return response.content ?? "";
|
|
176
|
+
return new ChatResponseString(response.content ?? "", totalUsage, this.model);
|
|
121
177
|
}
|
|
122
178
|
/**
|
|
123
179
|
* Streams the model's response to a user question.
|
|
124
|
-
* @param content The user's question to send to the model.
|
|
125
|
-
* @returns An async generator yielding chunks of the assistant's response as strings.
|
|
126
180
|
*/
|
|
127
181
|
async *stream(content) {
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
throw new Error("Streaming not supported by provider");
|
|
131
|
-
}
|
|
132
|
-
let full = "";
|
|
133
|
-
for await (const chunk of this.provider.stream({
|
|
134
|
-
model: this.model,
|
|
135
|
-
messages: this.messages,
|
|
136
|
-
})) {
|
|
137
|
-
if (chunk.content) {
|
|
138
|
-
full += chunk.content;
|
|
139
|
-
yield chunk.content;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
this.messages.push({
|
|
143
|
-
role: "assistant",
|
|
144
|
-
content: full,
|
|
145
|
-
});
|
|
182
|
+
const streamer = new Stream(this.provider, this.model, this.options, this.messages);
|
|
183
|
+
yield* streamer.stream(content);
|
|
146
184
|
}
|
|
147
185
|
}
|
package/dist/chat/Content.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Content.d.ts","sourceRoot":"","sources":["../../src/chat/Content.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,WAAW,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"Content.d.ts","sourceRoot":"","sources":["../../src/chat/Content.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,EAAE,CAAC"}
|
package/dist/chat/Message.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Role } from "./Role.js";
|
|
2
2
|
import { ToolCall } from "./Tool.js";
|
|
3
3
|
import { MessageContent } from "./Content.js";
|
|
4
|
+
import { Usage } from "../providers/Provider.js";
|
|
4
5
|
export interface Message {
|
|
5
6
|
role: Role;
|
|
6
7
|
content: MessageContent | null;
|
|
7
8
|
tool_calls?: ToolCall[];
|
|
8
9
|
tool_call_id?: string;
|
|
9
10
|
name?: string;
|
|
11
|
+
usage?: Usage;
|
|
10
12
|
}
|
|
11
13
|
//# sourceMappingURL=Message.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../src/chat/Message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"Message.d.ts","sourceRoot":"","sources":["../../src/chat/Message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEjD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,EAAE,QAAQ,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;CACf"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Message } from "./Message.js";
|
|
2
|
+
import { ChatOptions } from "./ChatOptions.js";
|
|
3
|
+
import { Provider } from "../providers/Provider.js";
|
|
4
|
+
export declare class Stream {
|
|
5
|
+
private readonly provider;
|
|
6
|
+
private readonly model;
|
|
7
|
+
private readonly options;
|
|
8
|
+
private messages;
|
|
9
|
+
constructor(provider: Provider, model: string, options?: ChatOptions, messages?: Message[]);
|
|
10
|
+
/**
|
|
11
|
+
* Read-only access to message history
|
|
12
|
+
*/
|
|
13
|
+
get history(): readonly Message[];
|
|
14
|
+
/**
|
|
15
|
+
* Streams the model's response to a user question.
|
|
16
|
+
* @param content The user's question to send to the model.
|
|
17
|
+
* @returns An async generator yielding chunks of the assistant's response as strings.
|
|
18
|
+
*/
|
|
19
|
+
stream(content: string): AsyncGenerator<import("../providers/Provider.js").ChatChunk, void, unknown>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=Stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Stream.d.ts","sourceRoot":"","sources":["../../src/chat/Stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,qBAAa,MAAM;IAIf,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAY;gBAGT,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,WAAgB,EAC1C,QAAQ,CAAC,EAAE,OAAO,EAAE;IAmBtB;;OAEG;IACH,IAAI,OAAO,IAAI,SAAS,OAAO,EAAE,CAEhC;IAED;;;;OAIG;IACI,MAAM,CAAC,OAAO,EAAE,MAAM;CA0B9B"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class Stream {
|
|
2
|
+
provider;
|
|
3
|
+
model;
|
|
4
|
+
options;
|
|
5
|
+
messages;
|
|
6
|
+
constructor(provider, model, options = {}, messages) {
|
|
7
|
+
this.provider = provider;
|
|
8
|
+
this.model = model;
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.messages = messages ?? [];
|
|
11
|
+
// Only initialize if we're starting a new history
|
|
12
|
+
if (this.messages.length === 0) {
|
|
13
|
+
if (options.systemPrompt) {
|
|
14
|
+
this.messages.push({
|
|
15
|
+
role: "system",
|
|
16
|
+
content: options.systemPrompt,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (options.messages) {
|
|
20
|
+
this.messages.push(...options.messages);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Read-only access to message history
|
|
26
|
+
*/
|
|
27
|
+
get history() {
|
|
28
|
+
return this.messages;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Streams the model's response to a user question.
|
|
32
|
+
* @param content The user's question to send to the model.
|
|
33
|
+
* @returns An async generator yielding chunks of the assistant's response as strings.
|
|
34
|
+
*/
|
|
35
|
+
async *stream(content) {
|
|
36
|
+
this.messages.push({ role: "user", content });
|
|
37
|
+
if (!this.provider.stream) {
|
|
38
|
+
throw new Error("Streaming not supported by provider");
|
|
39
|
+
}
|
|
40
|
+
let full = "";
|
|
41
|
+
for await (const chunk of this.provider.stream({
|
|
42
|
+
model: this.model,
|
|
43
|
+
messages: this.messages,
|
|
44
|
+
temperature: this.options.temperature,
|
|
45
|
+
max_tokens: this.options.maxTokens,
|
|
46
|
+
})) {
|
|
47
|
+
if (chunk.content) {
|
|
48
|
+
full += chunk.content;
|
|
49
|
+
}
|
|
50
|
+
yield chunk;
|
|
51
|
+
}
|
|
52
|
+
this.messages.push({
|
|
53
|
+
role: "assistant",
|
|
54
|
+
content: full,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|