@lite-agent/provider 0.1.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/dist/index.d.ts +37 -0
- package/dist/index.js +291 -0
- package/package.json +55 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { ModelProvider } from '@lite-agent/core';
|
|
3
|
+
import OpenAI from 'openai';
|
|
4
|
+
|
|
5
|
+
interface AnthropicClientLike {
|
|
6
|
+
messages: {
|
|
7
|
+
create(params: Anthropic.MessageCreateParamsStreaming, options?: {
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}): Promise<AsyncIterable<Anthropic.RawMessageStreamEvent>> | AsyncIterable<Anthropic.RawMessageStreamEvent>;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
interface AnthropicProviderOptions {
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
baseURL?: string;
|
|
15
|
+
client?: AnthropicClientLike;
|
|
16
|
+
}
|
|
17
|
+
declare function anthropic(opts?: AnthropicProviderOptions): ModelProvider;
|
|
18
|
+
|
|
19
|
+
type Chunk = OpenAI.Chat.Completions.ChatCompletionChunk;
|
|
20
|
+
type Params = OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming;
|
|
21
|
+
interface OpenAIClientLike {
|
|
22
|
+
chat: {
|
|
23
|
+
completions: {
|
|
24
|
+
create(params: Params, options?: {
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
}): Promise<AsyncIterable<Chunk>> | AsyncIterable<Chunk>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface OpenAIProviderOptions {
|
|
31
|
+
apiKey?: string;
|
|
32
|
+
baseURL?: string;
|
|
33
|
+
client?: OpenAIClientLike;
|
|
34
|
+
}
|
|
35
|
+
declare function openai(opts?: OpenAIProviderOptions): ModelProvider;
|
|
36
|
+
|
|
37
|
+
export { type AnthropicClientLike, type AnthropicProviderOptions, type OpenAIClientLike, type OpenAIProviderOptions, anthropic, openai };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// src/anthropic/anthropic.ts
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import { ProviderError } from "@lite-agent/core";
|
|
4
|
+
|
|
5
|
+
// src/anthropic/mapping.ts
|
|
6
|
+
var DEFAULT_MAX_TOKENS = 4096;
|
|
7
|
+
function toBlockParam(b) {
|
|
8
|
+
if (b.type === "text") return { type: "text", text: b.text };
|
|
9
|
+
if (b.type === "tool_call") {
|
|
10
|
+
return {
|
|
11
|
+
type: "tool_use",
|
|
12
|
+
id: b.id,
|
|
13
|
+
name: b.name,
|
|
14
|
+
input: b.input ?? {}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return b.isError ? {
|
|
18
|
+
type: "tool_result",
|
|
19
|
+
tool_use_id: b.id,
|
|
20
|
+
content: b.content,
|
|
21
|
+
is_error: true
|
|
22
|
+
} : { type: "tool_result", tool_use_id: b.id, content: b.content };
|
|
23
|
+
}
|
|
24
|
+
function toMessageParam(m) {
|
|
25
|
+
const role = m.role === "assistant" ? "assistant" : "user";
|
|
26
|
+
const content = typeof m.content === "string" ? m.content : m.content.map(toBlockParam);
|
|
27
|
+
return { role, content };
|
|
28
|
+
}
|
|
29
|
+
function toInputSchema(parameters) {
|
|
30
|
+
const { $schema: _drop, ...rest } = parameters;
|
|
31
|
+
return rest;
|
|
32
|
+
}
|
|
33
|
+
function toTool(spec) {
|
|
34
|
+
return {
|
|
35
|
+
name: spec.name,
|
|
36
|
+
description: spec.description,
|
|
37
|
+
input_schema: toInputSchema(spec.parameters)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function toAnthropicParams(req) {
|
|
41
|
+
const params = {
|
|
42
|
+
model: req.model,
|
|
43
|
+
max_tokens: req.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
44
|
+
messages: req.messages.filter((m) => m.role !== "system").map(toMessageParam),
|
|
45
|
+
stream: true
|
|
46
|
+
};
|
|
47
|
+
if (req.system) params.system = req.system;
|
|
48
|
+
if (req.stopSequences) params.stop_sequences = req.stopSequences;
|
|
49
|
+
if (req.tools && req.tools.length) params.tools = req.tools.map(toTool);
|
|
50
|
+
return params;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/anthropic/stream.ts
|
|
54
|
+
async function* translateStream(events) {
|
|
55
|
+
const blocks = [];
|
|
56
|
+
const toolJson = [];
|
|
57
|
+
let inputTokens = 0;
|
|
58
|
+
let outputTokens = 0;
|
|
59
|
+
for await (const event of events) {
|
|
60
|
+
switch (event.type) {
|
|
61
|
+
case "message_start":
|
|
62
|
+
inputTokens = event.message.usage.input_tokens;
|
|
63
|
+
break;
|
|
64
|
+
case "content_block_start": {
|
|
65
|
+
const cb = event.content_block;
|
|
66
|
+
if (cb.type === "text") {
|
|
67
|
+
blocks[event.index] = { type: "text", text: cb.text };
|
|
68
|
+
} else if (cb.type === "tool_use") {
|
|
69
|
+
blocks[event.index] = {
|
|
70
|
+
type: "tool_call",
|
|
71
|
+
id: cb.id,
|
|
72
|
+
name: cb.name,
|
|
73
|
+
input: {}
|
|
74
|
+
};
|
|
75
|
+
toolJson[event.index] = "";
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "content_block_delta": {
|
|
80
|
+
const d = event.delta;
|
|
81
|
+
if (d.type === "text_delta") {
|
|
82
|
+
const b = blocks[event.index];
|
|
83
|
+
if (b && b.type === "text") b.text += d.text;
|
|
84
|
+
yield { type: "text_delta", text: d.text };
|
|
85
|
+
} else if (d.type === "input_json_delta") {
|
|
86
|
+
toolJson[event.index] = (toolJson[event.index] ?? "") + d.partial_json;
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "content_block_stop": {
|
|
91
|
+
const b = blocks[event.index];
|
|
92
|
+
if (b && b.type === "tool_call") {
|
|
93
|
+
const raw = toolJson[event.index] ?? "";
|
|
94
|
+
b.input = raw ? JSON.parse(raw) : {};
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "message_delta":
|
|
99
|
+
outputTokens = event.usage.output_tokens;
|
|
100
|
+
break;
|
|
101
|
+
case "message_stop": {
|
|
102
|
+
const message = {
|
|
103
|
+
role: "assistant",
|
|
104
|
+
content: blocks.filter((b) => b !== void 0)
|
|
105
|
+
};
|
|
106
|
+
const usage = { inputTokens, outputTokens };
|
|
107
|
+
yield { type: "message_done", message, usage };
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/anthropic/anthropic.ts
|
|
115
|
+
function toProviderError(e) {
|
|
116
|
+
if (e instanceof ProviderError) return e;
|
|
117
|
+
const status = typeof e.status === "number" ? e.status : void 0;
|
|
118
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
119
|
+
return new ProviderError(message, status);
|
|
120
|
+
}
|
|
121
|
+
function anthropic(opts = {}) {
|
|
122
|
+
const client = opts.client ?? new Anthropic({
|
|
123
|
+
apiKey: opts.apiKey ?? process.env["ANTHROPIC_API_KEY"],
|
|
124
|
+
baseURL: opts.baseURL ?? process.env["BASE_URL"]
|
|
125
|
+
});
|
|
126
|
+
return {
|
|
127
|
+
id: "anthropic",
|
|
128
|
+
async *stream(req, signal) {
|
|
129
|
+
const params = toAnthropicParams(req);
|
|
130
|
+
try {
|
|
131
|
+
const raw = await client.messages.create(
|
|
132
|
+
params,
|
|
133
|
+
signal ? { signal } : void 0
|
|
134
|
+
);
|
|
135
|
+
yield* translateStream(raw);
|
|
136
|
+
} catch (e) {
|
|
137
|
+
throw toProviderError(e);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/openai/openai.ts
|
|
144
|
+
import OpenAI from "openai";
|
|
145
|
+
import { ProviderError as ProviderError2 } from "@lite-agent/core";
|
|
146
|
+
|
|
147
|
+
// src/openai/mapping.ts
|
|
148
|
+
function textOf(blocks) {
|
|
149
|
+
return blocks.filter(
|
|
150
|
+
(b) => b.type === "text"
|
|
151
|
+
).map((b) => b.text).join("");
|
|
152
|
+
}
|
|
153
|
+
function toChatMessages(m) {
|
|
154
|
+
if (typeof m.content === "string") {
|
|
155
|
+
if (m.role === "assistant")
|
|
156
|
+
return [{ role: "assistant", content: m.content }];
|
|
157
|
+
return [{ role: "user", content: m.content }];
|
|
158
|
+
}
|
|
159
|
+
if (m.role === "assistant") {
|
|
160
|
+
const msg = {
|
|
161
|
+
role: "assistant",
|
|
162
|
+
content: textOf(m.content) || null
|
|
163
|
+
};
|
|
164
|
+
const toolCalls = m.content.filter(
|
|
165
|
+
(b) => b.type === "tool_call"
|
|
166
|
+
).map(
|
|
167
|
+
(b) => ({
|
|
168
|
+
id: b.id,
|
|
169
|
+
type: "function",
|
|
170
|
+
function: { name: b.name, arguments: JSON.stringify(b.input ?? {}) }
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
if (toolCalls.length) msg.tool_calls = toolCalls;
|
|
174
|
+
return [msg];
|
|
175
|
+
}
|
|
176
|
+
const out = [];
|
|
177
|
+
const texts = [];
|
|
178
|
+
for (const b of m.content) {
|
|
179
|
+
if (b.type === "tool_result")
|
|
180
|
+
out.push({ role: "tool", tool_call_id: b.id, content: b.content });
|
|
181
|
+
else if (b.type === "text") texts.push(b.text);
|
|
182
|
+
}
|
|
183
|
+
if (texts.length) out.push({ role: "user", content: texts.join("") });
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function toChatTool(spec) {
|
|
187
|
+
const { $schema: _drop, ...parameters } = spec.parameters;
|
|
188
|
+
return {
|
|
189
|
+
type: "function",
|
|
190
|
+
function: { name: spec.name, description: spec.description, parameters }
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function toOpenAIParams(req) {
|
|
194
|
+
const messages = [];
|
|
195
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
196
|
+
for (const m of req.messages) {
|
|
197
|
+
if (m.role === "system") continue;
|
|
198
|
+
messages.push(...toChatMessages(m));
|
|
199
|
+
}
|
|
200
|
+
const params = {
|
|
201
|
+
model: req.model,
|
|
202
|
+
messages,
|
|
203
|
+
stream: true,
|
|
204
|
+
stream_options: { include_usage: true }
|
|
205
|
+
};
|
|
206
|
+
if (req.maxTokens) params.max_tokens = req.maxTokens;
|
|
207
|
+
if (req.stopSequences) params.stop = req.stopSequences;
|
|
208
|
+
if (req.tools && req.tools.length) params.tools = req.tools.map(toChatTool);
|
|
209
|
+
return params;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/openai/stream.ts
|
|
213
|
+
import { textBlock } from "@lite-agent/core";
|
|
214
|
+
function safeParse(args) {
|
|
215
|
+
if (!args) return {};
|
|
216
|
+
try {
|
|
217
|
+
return JSON.parse(args);
|
|
218
|
+
} catch {
|
|
219
|
+
return {};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function* translateStream2(raw) {
|
|
223
|
+
let text = "";
|
|
224
|
+
const calls = /* @__PURE__ */ new Map();
|
|
225
|
+
let usage = { inputTokens: 0, outputTokens: 0 };
|
|
226
|
+
for await (const chunk of raw) {
|
|
227
|
+
const delta = chunk.choices[0]?.delta;
|
|
228
|
+
if (delta?.content) {
|
|
229
|
+
text += delta.content;
|
|
230
|
+
yield { type: "text_delta", text: delta.content };
|
|
231
|
+
}
|
|
232
|
+
for (const tc of delta?.tool_calls ?? []) {
|
|
233
|
+
const cur = calls.get(tc.index) ?? { id: "", name: "", args: "" };
|
|
234
|
+
if (tc.id) cur.id = tc.id;
|
|
235
|
+
if (tc.function?.name) cur.name = tc.function.name;
|
|
236
|
+
if (tc.function?.arguments) cur.args += tc.function.arguments;
|
|
237
|
+
calls.set(tc.index, cur);
|
|
238
|
+
}
|
|
239
|
+
if (chunk.usage) {
|
|
240
|
+
usage = {
|
|
241
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
242
|
+
outputTokens: chunk.usage.completion_tokens
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const content = [];
|
|
247
|
+
if (text) content.push(textBlock(text));
|
|
248
|
+
for (const [, c] of [...calls.entries()].sort((a, b) => a[0] - b[0])) {
|
|
249
|
+
content.push({
|
|
250
|
+
type: "tool_call",
|
|
251
|
+
id: c.id,
|
|
252
|
+
name: c.name,
|
|
253
|
+
input: safeParse(c.args)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const message = { role: "assistant", content };
|
|
257
|
+
yield { type: "message_done", message, usage };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/openai/openai.ts
|
|
261
|
+
function toProviderError2(e) {
|
|
262
|
+
if (e instanceof ProviderError2) return e;
|
|
263
|
+
const status = typeof e.status === "number" ? e.status : void 0;
|
|
264
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
265
|
+
return new ProviderError2(message, status);
|
|
266
|
+
}
|
|
267
|
+
function openai(opts = {}) {
|
|
268
|
+
const client = opts.client ?? new OpenAI({
|
|
269
|
+
apiKey: opts.apiKey ?? process.env["OPENAI_API_KEY"],
|
|
270
|
+
baseURL: opts.baseURL ?? process.env["OPENAI_BASE_URL"]
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
id: "openai",
|
|
274
|
+
async *stream(req, signal) {
|
|
275
|
+
const params = toOpenAIParams(req);
|
|
276
|
+
try {
|
|
277
|
+
const raw = await client.chat.completions.create(
|
|
278
|
+
params,
|
|
279
|
+
signal ? { signal } : void 0
|
|
280
|
+
);
|
|
281
|
+
yield* translateStream2(raw);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
throw toProviderError2(e);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
export {
|
|
289
|
+
anthropic,
|
|
290
|
+
openai
|
|
291
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lite-agent/provider",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Model providers for lite-agent: Anthropic Messages API + OpenAI Chat Completions (also OpenAI-compatible / local endpoints).",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"agent",
|
|
24
|
+
"ai",
|
|
25
|
+
"llm",
|
|
26
|
+
"anthropic",
|
|
27
|
+
"claude",
|
|
28
|
+
"openai",
|
|
29
|
+
"provider"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/NelsonYong/lite-agent.git",
|
|
34
|
+
"directory": "packages/provider"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup src/index.ts --format esm --dts --clean --tsconfig tsconfig.build.json",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@anthropic-ai/sdk": "^0.80.0",
|
|
46
|
+
"@lite-agent/core": "workspace:*",
|
|
47
|
+
"openai": "^6.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^25.5.0",
|
|
51
|
+
"tsup": "^8.3.0",
|
|
52
|
+
"typescript": "^6.0.2",
|
|
53
|
+
"vitest": "^2.1.0"
|
|
54
|
+
}
|
|
55
|
+
}
|