@prompd/cli 0.4.10 → 0.4.11
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/commands/ask.d.ts +3 -0
- package/dist/commands/ask.d.ts.map +1 -0
- package/dist/commands/ask.js +121 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +192 -42
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +62 -4
- package/dist/commands/run.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/compiler/package-resolver.d.ts.map +1 -1
- package/dist/lib/compiler/package-resolver.js +5 -1
- package/dist/lib/compiler/package-resolver.js.map +1 -1
- package/dist/lib/executor.d.ts +16 -6
- package/dist/lib/executor.d.ts.map +1 -1
- package/dist/lib/executor.js +121 -245
- package/dist/lib/executor.js.map +1 -1
- package/dist/lib/index.d.ts +4 -2
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +21 -4
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/mcp.d.ts +13 -2
- package/dist/lib/mcp.d.ts.map +1 -1
- package/dist/lib/mcp.js +447 -77
- package/dist/lib/mcp.js.map +1 -1
- package/dist/lib/providers/base.d.ts +92 -0
- package/dist/lib/providers/base.d.ts.map +1 -0
- package/dist/lib/providers/base.js +741 -0
- package/dist/lib/providers/base.js.map +1 -0
- package/dist/lib/providers/factory.d.ts +37 -0
- package/dist/lib/providers/factory.d.ts.map +1 -0
- package/dist/lib/providers/factory.js +82 -0
- package/dist/lib/providers/factory.js.map +1 -0
- package/dist/lib/providers/index.d.ts +12 -0
- package/dist/lib/providers/index.d.ts.map +1 -0
- package/dist/lib/providers/index.js +25 -0
- package/dist/lib/providers/index.js.map +1 -0
- package/dist/lib/providers/types.d.ts +129 -0
- package/dist/lib/providers/types.d.ts.map +1 -0
- package/dist/lib/providers/types.js +156 -0
- package/dist/lib/providers/types.js.map +1 -0
- package/dist/lib/workflowExecutor.d.ts.map +1 -1
- package/dist/lib/workflowExecutor.js +24 -10
- package/dist/lib/workflowExecutor.js.map +1 -1
- package/dist/lib/workflowTypes.d.ts +2 -0
- package/dist/lib/workflowTypes.d.ts.map +1 -1
- package/dist/lib/workflowTypes.js.map +1 -1
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Base Provider Classes
|
|
4
|
+
*
|
|
5
|
+
* Abstract base class and concrete provider implementations for all supported LLM APIs.
|
|
6
|
+
* Uses axios for HTTP transport (Node.js environment).
|
|
7
|
+
* The frontend mirrors this structure but uses electronFetch for CORS bypass.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CohereProvider = exports.GoogleGeminiProvider = exports.AnthropicProvider = exports.OpenAICompatibleProvider = exports.BaseProvider = void 0;
|
|
14
|
+
const axios_1 = __importDefault(require("axios"));
|
|
15
|
+
/**
|
|
16
|
+
* Abstract base class for LLM providers
|
|
17
|
+
*/
|
|
18
|
+
class BaseProvider {
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* List available models from config
|
|
24
|
+
*/
|
|
25
|
+
listModels() {
|
|
26
|
+
return this.config.models || [];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Helper to create a standard error result
|
|
30
|
+
*/
|
|
31
|
+
createErrorResult(error, duration) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error,
|
|
35
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
36
|
+
duration
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Helper to create a success result
|
|
41
|
+
*/
|
|
42
|
+
createSuccessResult(response, usage, duration) {
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
response,
|
|
46
|
+
usage,
|
|
47
|
+
duration
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract error message from an axios error response
|
|
52
|
+
*/
|
|
53
|
+
extractAxiosError(error) {
|
|
54
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
55
|
+
const axErr = error;
|
|
56
|
+
const data = axErr.response?.data;
|
|
57
|
+
if (data && typeof data === 'object') {
|
|
58
|
+
const errObj = data.error;
|
|
59
|
+
if (errObj && typeof errObj.message === 'string') {
|
|
60
|
+
return errObj.message;
|
|
61
|
+
}
|
|
62
|
+
if (typeof data.message === 'string') {
|
|
63
|
+
return data.message;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return axErr.message;
|
|
67
|
+
}
|
|
68
|
+
if (error instanceof Error) {
|
|
69
|
+
return error.message;
|
|
70
|
+
}
|
|
71
|
+
return 'Unknown error';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.BaseProvider = BaseProvider;
|
|
75
|
+
/**
|
|
76
|
+
* OpenAI-compatible provider base class
|
|
77
|
+
*
|
|
78
|
+
* Many providers (Groq, Mistral, Together, Perplexity, DeepSeek, Ollama)
|
|
79
|
+
* use the OpenAI chat completions API format. This class handles all of them.
|
|
80
|
+
*/
|
|
81
|
+
class OpenAICompatibleProvider extends BaseProvider {
|
|
82
|
+
constructor(config, baseUrlOverride) {
|
|
83
|
+
super(config);
|
|
84
|
+
this.name = config.name;
|
|
85
|
+
this.displayName = config.displayName;
|
|
86
|
+
this.baseUrl = baseUrlOverride || config.baseUrl;
|
|
87
|
+
}
|
|
88
|
+
async execute(request) {
|
|
89
|
+
const startTime = Date.now();
|
|
90
|
+
try {
|
|
91
|
+
// Use the Responses API for image generation (OpenAI only, not other compatible providers)
|
|
92
|
+
if (request.enableImageGeneration && this.name === 'openai') {
|
|
93
|
+
return await this.executeWithResponses(request, startTime);
|
|
94
|
+
}
|
|
95
|
+
const messages = [];
|
|
96
|
+
// For JSON mode, ensure "json" appears in the system prompt (OpenAI requirement)
|
|
97
|
+
const systemPrompt = request.mode === 'json'
|
|
98
|
+
? (request.systemPrompt ? `${request.systemPrompt}\n\nRespond with valid JSON.` : 'Respond with valid JSON.')
|
|
99
|
+
: request.systemPrompt;
|
|
100
|
+
if (systemPrompt) {
|
|
101
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
102
|
+
}
|
|
103
|
+
messages.push({ role: 'user', content: request.prompt });
|
|
104
|
+
const body = {
|
|
105
|
+
model: request.model,
|
|
106
|
+
messages,
|
|
107
|
+
stream: false
|
|
108
|
+
};
|
|
109
|
+
if (request.maxTokens) {
|
|
110
|
+
body.max_tokens = request.maxTokens;
|
|
111
|
+
}
|
|
112
|
+
if (request.temperature !== undefined) {
|
|
113
|
+
body.temperature = request.temperature;
|
|
114
|
+
}
|
|
115
|
+
// JSON mode - request structured JSON output
|
|
116
|
+
if (request.mode === 'json') {
|
|
117
|
+
body.response_format = { type: 'json_object' };
|
|
118
|
+
}
|
|
119
|
+
const headers = {
|
|
120
|
+
'Content-Type': 'application/json'
|
|
121
|
+
};
|
|
122
|
+
if (request.apiKey) {
|
|
123
|
+
headers['Authorization'] = `Bearer ${request.apiKey}`;
|
|
124
|
+
}
|
|
125
|
+
const response = await (0, axios_1.default)({
|
|
126
|
+
method: 'POST',
|
|
127
|
+
url: `${this.baseUrl}/chat/completions`,
|
|
128
|
+
headers,
|
|
129
|
+
data: body,
|
|
130
|
+
timeout: 120000
|
|
131
|
+
});
|
|
132
|
+
const data = response.data;
|
|
133
|
+
const duration = Date.now() - startTime;
|
|
134
|
+
// Handle both string responses and multimodal content arrays
|
|
135
|
+
// Models like GPT-4o can return image content blocks alongside text
|
|
136
|
+
let content = '';
|
|
137
|
+
const messageContent = data.choices?.[0]?.message?.content;
|
|
138
|
+
if (typeof messageContent === 'string') {
|
|
139
|
+
content = messageContent;
|
|
140
|
+
}
|
|
141
|
+
else if (Array.isArray(messageContent)) {
|
|
142
|
+
for (const block of messageContent) {
|
|
143
|
+
if (block.type === 'text') {
|
|
144
|
+
content += block.text || '';
|
|
145
|
+
}
|
|
146
|
+
else if (block.type === 'image_url' && block.image_url?.url) {
|
|
147
|
+
content += `\n\n\n\n`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const usage = {
|
|
152
|
+
promptTokens: data.usage?.prompt_tokens || 0,
|
|
153
|
+
completionTokens: data.usage?.completion_tokens || 0,
|
|
154
|
+
totalTokens: data.usage?.total_tokens || 0
|
|
155
|
+
};
|
|
156
|
+
return this.createSuccessResult(content, usage, duration);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
const duration = Date.now() - startTime;
|
|
160
|
+
return this.createErrorResult(this.extractAxiosError(error), duration);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async *stream(request) {
|
|
164
|
+
const messages = [];
|
|
165
|
+
// For JSON mode, ensure "json" appears in the system prompt (OpenAI requirement)
|
|
166
|
+
const systemPrompt = request.mode === 'json'
|
|
167
|
+
? (request.systemPrompt ? `${request.systemPrompt}\n\nRespond with valid JSON.` : 'Respond with valid JSON.')
|
|
168
|
+
: request.systemPrompt;
|
|
169
|
+
if (systemPrompt) {
|
|
170
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
171
|
+
}
|
|
172
|
+
messages.push({ role: 'user', content: request.prompt });
|
|
173
|
+
const body = {
|
|
174
|
+
model: request.model,
|
|
175
|
+
messages,
|
|
176
|
+
stream: true
|
|
177
|
+
};
|
|
178
|
+
if (request.maxTokens) {
|
|
179
|
+
body.max_tokens = request.maxTokens;
|
|
180
|
+
}
|
|
181
|
+
if (request.temperature !== undefined) {
|
|
182
|
+
body.temperature = request.temperature;
|
|
183
|
+
}
|
|
184
|
+
// JSON mode - request structured JSON output
|
|
185
|
+
if (request.mode === 'json') {
|
|
186
|
+
body.response_format = { type: 'json_object' };
|
|
187
|
+
}
|
|
188
|
+
const headers = {
|
|
189
|
+
'Content-Type': 'application/json'
|
|
190
|
+
};
|
|
191
|
+
if (request.apiKey) {
|
|
192
|
+
headers['Authorization'] = `Bearer ${request.apiKey}`;
|
|
193
|
+
}
|
|
194
|
+
const response = await (0, axios_1.default)({
|
|
195
|
+
method: 'POST',
|
|
196
|
+
url: `${this.baseUrl}/chat/completions`,
|
|
197
|
+
headers,
|
|
198
|
+
data: body,
|
|
199
|
+
timeout: 120000,
|
|
200
|
+
responseType: 'stream'
|
|
201
|
+
});
|
|
202
|
+
const nodeStream = response.data;
|
|
203
|
+
let buffer = '';
|
|
204
|
+
let totalUsage;
|
|
205
|
+
for await (const chunk of nodeStream) {
|
|
206
|
+
buffer += chunk.toString();
|
|
207
|
+
const lines = buffer.split('\n');
|
|
208
|
+
buffer = lines.pop() || '';
|
|
209
|
+
for (const line of lines) {
|
|
210
|
+
const trimmed = line.trim();
|
|
211
|
+
if (!trimmed || trimmed === 'data: [DONE]')
|
|
212
|
+
continue;
|
|
213
|
+
if (!trimmed.startsWith('data: '))
|
|
214
|
+
continue;
|
|
215
|
+
try {
|
|
216
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
217
|
+
const delta = json.choices?.[0]?.delta?.content || '';
|
|
218
|
+
// Capture usage if present (some providers send on last chunk)
|
|
219
|
+
if (json.usage) {
|
|
220
|
+
totalUsage = {
|
|
221
|
+
promptTokens: json.usage.prompt_tokens || 0,
|
|
222
|
+
completionTokens: json.usage.completion_tokens || 0,
|
|
223
|
+
totalTokens: json.usage.total_tokens || 0
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (delta) {
|
|
227
|
+
yield { content: delta, done: false };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Skip invalid JSON lines
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
yield { content: '', done: true, usage: totalUsage };
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Execute using OpenAI's Responses API for image generation
|
|
239
|
+
* Uses POST /v1/responses with tools: [{"type": "image_generation"}]
|
|
240
|
+
*/
|
|
241
|
+
async executeWithResponses(request, startTime) {
|
|
242
|
+
const input = [];
|
|
243
|
+
if (request.systemPrompt) {
|
|
244
|
+
input.push({ role: 'developer', content: request.systemPrompt });
|
|
245
|
+
}
|
|
246
|
+
input.push({ role: 'user', content: request.prompt });
|
|
247
|
+
const body = {
|
|
248
|
+
model: request.model,
|
|
249
|
+
input,
|
|
250
|
+
tools: [{ type: 'image_generation' }]
|
|
251
|
+
};
|
|
252
|
+
if (request.maxTokens) {
|
|
253
|
+
body.max_output_tokens = request.maxTokens;
|
|
254
|
+
}
|
|
255
|
+
if (request.temperature !== undefined) {
|
|
256
|
+
body.temperature = request.temperature;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const response = await (0, axios_1.default)({
|
|
260
|
+
method: 'POST',
|
|
261
|
+
url: `${this.baseUrl}/responses`,
|
|
262
|
+
headers: {
|
|
263
|
+
'Content-Type': 'application/json',
|
|
264
|
+
'Authorization': `Bearer ${request.apiKey}`
|
|
265
|
+
},
|
|
266
|
+
data: body,
|
|
267
|
+
timeout: 120000
|
|
268
|
+
});
|
|
269
|
+
const data = response.data;
|
|
270
|
+
const duration = Date.now() - startTime;
|
|
271
|
+
// Parse the Responses API output format
|
|
272
|
+
// Output is an array of items: message (text), image_generation_call (images)
|
|
273
|
+
let content = '';
|
|
274
|
+
const outputItems = data.output || [];
|
|
275
|
+
for (const item of outputItems) {
|
|
276
|
+
if (item.type === 'message') {
|
|
277
|
+
const parts = item.content || [];
|
|
278
|
+
for (const part of parts) {
|
|
279
|
+
if (part.type === 'output_text') {
|
|
280
|
+
content += part.text || '';
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if (item.type === 'image_generation_call' && item.result) {
|
|
285
|
+
content += `\n\n\n\n`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const usage = {
|
|
289
|
+
promptTokens: data.usage?.input_tokens || 0,
|
|
290
|
+
completionTokens: data.usage?.output_tokens || 0,
|
|
291
|
+
totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0)
|
|
292
|
+
};
|
|
293
|
+
return this.createSuccessResult(content, usage, duration);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
const duration = Date.now() - startTime;
|
|
297
|
+
return this.createErrorResult(this.extractAxiosError(error), duration);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
exports.OpenAICompatibleProvider = OpenAICompatibleProvider;
|
|
302
|
+
/**
|
|
303
|
+
* Anthropic provider using the Messages API
|
|
304
|
+
*/
|
|
305
|
+
class AnthropicProvider extends BaseProvider {
|
|
306
|
+
constructor() {
|
|
307
|
+
super(...arguments);
|
|
308
|
+
this.name = 'anthropic';
|
|
309
|
+
this.displayName = 'Anthropic';
|
|
310
|
+
this.baseUrl = 'https://api.anthropic.com';
|
|
311
|
+
}
|
|
312
|
+
async execute(request) {
|
|
313
|
+
const startTime = Date.now();
|
|
314
|
+
try {
|
|
315
|
+
const messages = [
|
|
316
|
+
{ role: 'user', content: request.prompt }
|
|
317
|
+
];
|
|
318
|
+
// For thinking mode, ensure minimum 1024 tokens for both max_tokens and budget
|
|
319
|
+
const isThinking = request.mode === 'thinking';
|
|
320
|
+
const maxTokens = isThinking
|
|
321
|
+
? Math.max(1024, request.maxTokens || 4096)
|
|
322
|
+
: (request.maxTokens || 4096);
|
|
323
|
+
const body = {
|
|
324
|
+
model: request.model,
|
|
325
|
+
max_tokens: maxTokens,
|
|
326
|
+
messages
|
|
327
|
+
};
|
|
328
|
+
if (request.systemPrompt) {
|
|
329
|
+
body.system = request.systemPrompt;
|
|
330
|
+
}
|
|
331
|
+
if (request.temperature !== undefined) {
|
|
332
|
+
body.temperature = request.temperature;
|
|
333
|
+
}
|
|
334
|
+
// Extended thinking mode - uses budget_tokens (minimum 1024)
|
|
335
|
+
if (isThinking) {
|
|
336
|
+
body.thinking = { type: 'enabled', budget_tokens: maxTokens };
|
|
337
|
+
}
|
|
338
|
+
const response = await (0, axios_1.default)({
|
|
339
|
+
method: 'POST',
|
|
340
|
+
url: `${this.baseUrl}/v1/messages`,
|
|
341
|
+
headers: {
|
|
342
|
+
'Content-Type': 'application/json',
|
|
343
|
+
'x-api-key': request.apiKey,
|
|
344
|
+
'anthropic-version': '2023-06-01'
|
|
345
|
+
},
|
|
346
|
+
data: body,
|
|
347
|
+
timeout: 120000
|
|
348
|
+
});
|
|
349
|
+
const data = response.data;
|
|
350
|
+
const duration = Date.now() - startTime;
|
|
351
|
+
// Anthropic returns content as an array of blocks
|
|
352
|
+
// With thinking mode, there may be thinking blocks followed by text blocks
|
|
353
|
+
let content = '';
|
|
354
|
+
if (data.content && Array.isArray(data.content)) {
|
|
355
|
+
for (const block of data.content) {
|
|
356
|
+
if (block.type === 'text') {
|
|
357
|
+
content += block.text || '';
|
|
358
|
+
}
|
|
359
|
+
else if (block.type === 'thinking') {
|
|
360
|
+
content += block.thinking || '';
|
|
361
|
+
}
|
|
362
|
+
else if (block.type === 'image' && block.source?.data) {
|
|
363
|
+
const mimeType = block.source.media_type || 'image/png';
|
|
364
|
+
content += `\n\n\n\n`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const usage = {
|
|
369
|
+
promptTokens: data.usage?.input_tokens || 0,
|
|
370
|
+
completionTokens: data.usage?.output_tokens || 0,
|
|
371
|
+
totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0)
|
|
372
|
+
};
|
|
373
|
+
return this.createSuccessResult(content, usage, duration);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
const duration = Date.now() - startTime;
|
|
377
|
+
return this.createErrorResult(this.extractAxiosError(error), duration);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async *stream(request) {
|
|
381
|
+
const messages = [
|
|
382
|
+
{ role: 'user', content: request.prompt }
|
|
383
|
+
];
|
|
384
|
+
// For thinking mode, ensure minimum 1024 tokens for both max_tokens and budget
|
|
385
|
+
const isThinking = request.mode === 'thinking';
|
|
386
|
+
const maxTokens = isThinking
|
|
387
|
+
? Math.max(1024, request.maxTokens || 4096)
|
|
388
|
+
: (request.maxTokens || 4096);
|
|
389
|
+
const body = {
|
|
390
|
+
model: request.model,
|
|
391
|
+
max_tokens: maxTokens,
|
|
392
|
+
messages,
|
|
393
|
+
stream: true
|
|
394
|
+
};
|
|
395
|
+
if (request.systemPrompt) {
|
|
396
|
+
body.system = request.systemPrompt;
|
|
397
|
+
}
|
|
398
|
+
if (request.temperature !== undefined) {
|
|
399
|
+
body.temperature = request.temperature;
|
|
400
|
+
}
|
|
401
|
+
// Extended thinking mode - uses budget_tokens (minimum 1024)
|
|
402
|
+
if (isThinking) {
|
|
403
|
+
body.thinking = { type: 'enabled', budget_tokens: maxTokens };
|
|
404
|
+
}
|
|
405
|
+
const response = await (0, axios_1.default)({
|
|
406
|
+
method: 'POST',
|
|
407
|
+
url: `${this.baseUrl}/v1/messages`,
|
|
408
|
+
headers: {
|
|
409
|
+
'Content-Type': 'application/json',
|
|
410
|
+
'x-api-key': request.apiKey,
|
|
411
|
+
'anthropic-version': '2023-06-01'
|
|
412
|
+
},
|
|
413
|
+
data: body,
|
|
414
|
+
timeout: 120000,
|
|
415
|
+
responseType: 'stream'
|
|
416
|
+
});
|
|
417
|
+
const nodeStream = response.data;
|
|
418
|
+
let buffer = '';
|
|
419
|
+
let totalUsage;
|
|
420
|
+
for await (const chunk of nodeStream) {
|
|
421
|
+
buffer += chunk.toString();
|
|
422
|
+
const lines = buffer.split('\n');
|
|
423
|
+
buffer = lines.pop() || '';
|
|
424
|
+
for (const line of lines) {
|
|
425
|
+
const trimmed = line.trim();
|
|
426
|
+
if (!trimmed.startsWith('data: '))
|
|
427
|
+
continue;
|
|
428
|
+
try {
|
|
429
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
430
|
+
if (json.type === 'message_start' && json.message?.usage) {
|
|
431
|
+
// Capture input tokens from message_start (comes first)
|
|
432
|
+
totalUsage = {
|
|
433
|
+
promptTokens: json.message.usage.input_tokens || 0,
|
|
434
|
+
completionTokens: 0,
|
|
435
|
+
totalTokens: json.message.usage.input_tokens || 0
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
else if (json.type === 'content_block_delta') {
|
|
439
|
+
// Handle both regular text and thinking text deltas
|
|
440
|
+
const delta = json.delta?.text || json.delta?.thinking || '';
|
|
441
|
+
if (delta) {
|
|
442
|
+
yield { content: delta, done: false };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else if (json.type === 'message_delta' && json.usage) {
|
|
446
|
+
// Update with output tokens (comes at end)
|
|
447
|
+
const outputTokens = json.usage.output_tokens || 0;
|
|
448
|
+
if (totalUsage) {
|
|
449
|
+
totalUsage.completionTokens = outputTokens;
|
|
450
|
+
totalUsage.totalTokens = totalUsage.promptTokens + outputTokens;
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
totalUsage = {
|
|
454
|
+
promptTokens: 0,
|
|
455
|
+
completionTokens: outputTokens,
|
|
456
|
+
totalTokens: outputTokens
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// Skip invalid JSON
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
yield { content: '', done: true, usage: totalUsage };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
exports.AnthropicProvider = AnthropicProvider;
|
|
470
|
+
/**
|
|
471
|
+
* Google Gemini provider using the Generative Language API
|
|
472
|
+
*/
|
|
473
|
+
class GoogleGeminiProvider extends BaseProvider {
|
|
474
|
+
constructor() {
|
|
475
|
+
super(...arguments);
|
|
476
|
+
this.name = 'google';
|
|
477
|
+
this.displayName = 'Google Gemini';
|
|
478
|
+
this.baseUrl = 'https://generativelanguage.googleapis.com';
|
|
479
|
+
}
|
|
480
|
+
async execute(request) {
|
|
481
|
+
const startTime = Date.now();
|
|
482
|
+
try {
|
|
483
|
+
const contents = [
|
|
484
|
+
{
|
|
485
|
+
parts: [{ text: request.prompt }]
|
|
486
|
+
}
|
|
487
|
+
];
|
|
488
|
+
const body = {
|
|
489
|
+
contents
|
|
490
|
+
};
|
|
491
|
+
if (request.systemPrompt) {
|
|
492
|
+
body.systemInstruction = { parts: [{ text: request.systemPrompt }] };
|
|
493
|
+
}
|
|
494
|
+
const generationConfig = {};
|
|
495
|
+
if (request.maxTokens) {
|
|
496
|
+
generationConfig.maxOutputTokens = request.maxTokens;
|
|
497
|
+
}
|
|
498
|
+
if (request.temperature !== undefined) {
|
|
499
|
+
generationConfig.temperature = request.temperature;
|
|
500
|
+
}
|
|
501
|
+
// Enable image output modality for models that support it
|
|
502
|
+
if (request.enableImageGeneration) {
|
|
503
|
+
generationConfig.responseModalities = ['TEXT', 'IMAGE'];
|
|
504
|
+
}
|
|
505
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
506
|
+
body.generationConfig = generationConfig;
|
|
507
|
+
}
|
|
508
|
+
const url = `${this.baseUrl}/v1beta/models/${request.model}:generateContent?key=${request.apiKey}`;
|
|
509
|
+
const response = await (0, axios_1.default)({
|
|
510
|
+
method: 'POST',
|
|
511
|
+
url,
|
|
512
|
+
headers: {
|
|
513
|
+
'Content-Type': 'application/json'
|
|
514
|
+
},
|
|
515
|
+
data: body,
|
|
516
|
+
timeout: 120000
|
|
517
|
+
});
|
|
518
|
+
const data = response.data;
|
|
519
|
+
const duration = Date.now() - startTime;
|
|
520
|
+
// Extract text and images from candidates
|
|
521
|
+
let content = '';
|
|
522
|
+
const parts = data.candidates?.[0]?.content?.parts || [];
|
|
523
|
+
for (const part of parts) {
|
|
524
|
+
if (part.text) {
|
|
525
|
+
content += part.text;
|
|
526
|
+
}
|
|
527
|
+
else if (part.inline_data?.data) {
|
|
528
|
+
const mimeType = part.inline_data.mime_type || 'image/png';
|
|
529
|
+
content += `\n\n\n\n`;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const usageMetadata = data.usageMetadata || {};
|
|
533
|
+
const usage = {
|
|
534
|
+
promptTokens: usageMetadata.promptTokenCount || 0,
|
|
535
|
+
completionTokens: usageMetadata.candidatesTokenCount || 0,
|
|
536
|
+
totalTokens: usageMetadata.totalTokenCount || 0
|
|
537
|
+
};
|
|
538
|
+
return this.createSuccessResult(content, usage, duration);
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
const duration = Date.now() - startTime;
|
|
542
|
+
return this.createErrorResult(this.extractAxiosError(error), duration);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async *stream(request) {
|
|
546
|
+
const contents = [
|
|
547
|
+
{
|
|
548
|
+
parts: [{ text: request.prompt }]
|
|
549
|
+
}
|
|
550
|
+
];
|
|
551
|
+
const body = {
|
|
552
|
+
contents
|
|
553
|
+
};
|
|
554
|
+
if (request.systemPrompt) {
|
|
555
|
+
body.systemInstruction = { parts: [{ text: request.systemPrompt }] };
|
|
556
|
+
}
|
|
557
|
+
const generationConfig = {};
|
|
558
|
+
if (request.maxTokens) {
|
|
559
|
+
generationConfig.maxOutputTokens = request.maxTokens;
|
|
560
|
+
}
|
|
561
|
+
if (request.temperature !== undefined) {
|
|
562
|
+
generationConfig.temperature = request.temperature;
|
|
563
|
+
}
|
|
564
|
+
if (request.enableImageGeneration) {
|
|
565
|
+
generationConfig.responseModalities = ['TEXT', 'IMAGE'];
|
|
566
|
+
}
|
|
567
|
+
if (Object.keys(generationConfig).length > 0) {
|
|
568
|
+
body.generationConfig = generationConfig;
|
|
569
|
+
}
|
|
570
|
+
const url = `${this.baseUrl}/v1beta/models/${request.model}:streamGenerateContent?alt=sse&key=${request.apiKey}`;
|
|
571
|
+
const response = await (0, axios_1.default)({
|
|
572
|
+
method: 'POST',
|
|
573
|
+
url,
|
|
574
|
+
headers: {
|
|
575
|
+
'Content-Type': 'application/json'
|
|
576
|
+
},
|
|
577
|
+
data: body,
|
|
578
|
+
timeout: 120000,
|
|
579
|
+
responseType: 'stream'
|
|
580
|
+
});
|
|
581
|
+
const nodeStream = response.data;
|
|
582
|
+
let buffer = '';
|
|
583
|
+
let totalUsage;
|
|
584
|
+
for await (const chunk of nodeStream) {
|
|
585
|
+
buffer += chunk.toString();
|
|
586
|
+
const lines = buffer.split('\n');
|
|
587
|
+
buffer = lines.pop() || '';
|
|
588
|
+
for (const line of lines) {
|
|
589
|
+
const trimmed = line.trim();
|
|
590
|
+
if (!trimmed.startsWith('data: '))
|
|
591
|
+
continue;
|
|
592
|
+
try {
|
|
593
|
+
const json = JSON.parse(trimmed.slice(6));
|
|
594
|
+
// Handle both text and image parts in streamed chunks
|
|
595
|
+
const parts = json.candidates?.[0]?.content?.parts || [];
|
|
596
|
+
let chunkContent = '';
|
|
597
|
+
for (const part of parts) {
|
|
598
|
+
if (part.text) {
|
|
599
|
+
chunkContent += part.text;
|
|
600
|
+
}
|
|
601
|
+
else if (part.inline_data?.data) {
|
|
602
|
+
const mimeType = part.inline_data.mime_type || 'image/png';
|
|
603
|
+
chunkContent += `\n\n\n\n`;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (json.usageMetadata) {
|
|
607
|
+
totalUsage = {
|
|
608
|
+
promptTokens: json.usageMetadata.promptTokenCount || 0,
|
|
609
|
+
completionTokens: json.usageMetadata.candidatesTokenCount || 0,
|
|
610
|
+
totalTokens: json.usageMetadata.totalTokenCount || 0
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (chunkContent) {
|
|
614
|
+
yield { content: chunkContent, done: false };
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
catch {
|
|
618
|
+
// Skip invalid JSON
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
yield { content: '', done: true, usage: totalUsage };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
exports.GoogleGeminiProvider = GoogleGeminiProvider;
|
|
626
|
+
/**
|
|
627
|
+
* Cohere provider using the Chat API
|
|
628
|
+
*/
|
|
629
|
+
class CohereProvider extends BaseProvider {
|
|
630
|
+
constructor() {
|
|
631
|
+
super(...arguments);
|
|
632
|
+
this.name = 'cohere';
|
|
633
|
+
this.displayName = 'Cohere';
|
|
634
|
+
this.baseUrl = 'https://api.cohere.ai';
|
|
635
|
+
}
|
|
636
|
+
async execute(request) {
|
|
637
|
+
const startTime = Date.now();
|
|
638
|
+
try {
|
|
639
|
+
const body = {
|
|
640
|
+
model: request.model,
|
|
641
|
+
message: request.prompt
|
|
642
|
+
};
|
|
643
|
+
if (request.systemPrompt) {
|
|
644
|
+
body.preamble = request.systemPrompt;
|
|
645
|
+
}
|
|
646
|
+
if (request.maxTokens) {
|
|
647
|
+
body.max_tokens = request.maxTokens;
|
|
648
|
+
}
|
|
649
|
+
if (request.temperature !== undefined) {
|
|
650
|
+
body.temperature = request.temperature;
|
|
651
|
+
}
|
|
652
|
+
const response = await (0, axios_1.default)({
|
|
653
|
+
method: 'POST',
|
|
654
|
+
url: `${this.baseUrl}/v1/chat`,
|
|
655
|
+
headers: {
|
|
656
|
+
'Content-Type': 'application/json',
|
|
657
|
+
'Authorization': `Bearer ${request.apiKey}`
|
|
658
|
+
},
|
|
659
|
+
data: body,
|
|
660
|
+
timeout: 120000
|
|
661
|
+
});
|
|
662
|
+
const data = response.data;
|
|
663
|
+
const duration = Date.now() - startTime;
|
|
664
|
+
const content = data.text || '';
|
|
665
|
+
const tokens = data.meta?.tokens || {};
|
|
666
|
+
const usage = {
|
|
667
|
+
promptTokens: tokens.input_tokens || 0,
|
|
668
|
+
completionTokens: tokens.output_tokens || 0,
|
|
669
|
+
totalTokens: (tokens.input_tokens || 0) + (tokens.output_tokens || 0)
|
|
670
|
+
};
|
|
671
|
+
return this.createSuccessResult(content, usage, duration);
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
const duration = Date.now() - startTime;
|
|
675
|
+
return this.createErrorResult(this.extractAxiosError(error), duration);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async *stream(request) {
|
|
679
|
+
const body = {
|
|
680
|
+
model: request.model,
|
|
681
|
+
message: request.prompt,
|
|
682
|
+
stream: true
|
|
683
|
+
};
|
|
684
|
+
if (request.systemPrompt) {
|
|
685
|
+
body.preamble = request.systemPrompt;
|
|
686
|
+
}
|
|
687
|
+
if (request.maxTokens) {
|
|
688
|
+
body.max_tokens = request.maxTokens;
|
|
689
|
+
}
|
|
690
|
+
if (request.temperature !== undefined) {
|
|
691
|
+
body.temperature = request.temperature;
|
|
692
|
+
}
|
|
693
|
+
const response = await (0, axios_1.default)({
|
|
694
|
+
method: 'POST',
|
|
695
|
+
url: `${this.baseUrl}/v1/chat`,
|
|
696
|
+
headers: {
|
|
697
|
+
'Content-Type': 'application/json',
|
|
698
|
+
'Authorization': `Bearer ${request.apiKey}`
|
|
699
|
+
},
|
|
700
|
+
data: body,
|
|
701
|
+
timeout: 120000,
|
|
702
|
+
responseType: 'stream'
|
|
703
|
+
});
|
|
704
|
+
const nodeStream = response.data;
|
|
705
|
+
let buffer = '';
|
|
706
|
+
let totalUsage;
|
|
707
|
+
for await (const chunk of nodeStream) {
|
|
708
|
+
buffer += chunk.toString();
|
|
709
|
+
const lines = buffer.split('\n');
|
|
710
|
+
buffer = lines.pop() || '';
|
|
711
|
+
for (const line of lines) {
|
|
712
|
+
const trimmed = line.trim();
|
|
713
|
+
if (!trimmed)
|
|
714
|
+
continue;
|
|
715
|
+
try {
|
|
716
|
+
const json = JSON.parse(trimmed);
|
|
717
|
+
if (json.event_type === 'text-generation') {
|
|
718
|
+
const text = json.text || '';
|
|
719
|
+
if (text) {
|
|
720
|
+
yield { content: text, done: false };
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
else if (json.event_type === 'stream-end' && json.response?.meta?.tokens) {
|
|
724
|
+
const tokens = json.response.meta.tokens;
|
|
725
|
+
totalUsage = {
|
|
726
|
+
promptTokens: tokens.input_tokens || 0,
|
|
727
|
+
completionTokens: tokens.output_tokens || 0,
|
|
728
|
+
totalTokens: (tokens.input_tokens || 0) + (tokens.output_tokens || 0)
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
// Skip invalid JSON
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
yield { content: '', done: true, usage: totalUsage };
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
exports.CohereProvider = CohereProvider;
|
|
741
|
+
//# sourceMappingURL=base.js.map
|