@compilr-dev/agents 0.3.1 → 0.3.4
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/agent.d.ts +107 -2
- package/dist/agent.js +151 -22
- package/dist/context/manager.d.ts +8 -0
- package/dist/context/manager.js +25 -2
- package/dist/errors.d.ts +20 -1
- package/dist/errors.js +44 -2
- package/dist/index.d.ts +16 -1
- package/dist/index.js +13 -1
- package/dist/messages/index.d.ts +12 -5
- package/dist/messages/index.js +53 -15
- package/dist/providers/claude.js +7 -0
- package/dist/providers/fireworks.d.ts +86 -0
- package/dist/providers/fireworks.js +123 -0
- package/dist/providers/gemini-native.d.ts +86 -0
- package/dist/providers/gemini-native.js +374 -0
- package/dist/providers/groq.d.ts +86 -0
- package/dist/providers/groq.js +123 -0
- package/dist/providers/index.d.ts +17 -2
- package/dist/providers/index.js +13 -2
- package/dist/providers/openai-compatible.js +12 -1
- package/dist/providers/openrouter.d.ts +95 -0
- package/dist/providers/openrouter.js +138 -0
- package/dist/providers/perplexity.d.ts +86 -0
- package/dist/providers/perplexity.js +123 -0
- package/dist/providers/together.d.ts +86 -0
- package/dist/providers/together.js +123 -0
- package/dist/providers/types.d.ts +19 -0
- package/dist/state/agent-state.d.ts +1 -0
- package/dist/state/agent-state.js +2 -0
- package/dist/state/serializer.js +20 -2
- package/dist/state/types.d.ts +5 -0
- package/dist/tools/builtin/ask-user-simple.js +1 -0
- package/dist/tools/builtin/ask-user.js +1 -0
- package/dist/tools/builtin/bash.js +123 -2
- package/dist/tools/builtin/shell-manager.d.ts +15 -0
- package/dist/tools/builtin/shell-manager.js +51 -0
- package/dist/tools/builtin/suggest.js +1 -0
- package/dist/tools/builtin/todo.js +2 -0
- package/dist/tools/define.d.ts +6 -0
- package/dist/tools/define.js +1 -0
- package/dist/tools/types.d.ts +19 -0
- package/dist/utils/index.d.ts +119 -4
- package/dist/utils/index.js +164 -13
- package/package.json +7 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeminiNativeProvider - Native Google Gen AI SDK implementation
|
|
3
|
+
*
|
|
4
|
+
* Uses the native @google/genai SDK instead of OpenAI-compatible endpoint.
|
|
5
|
+
* This enables proper support for Gemini 3 models with thought signatures.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const provider = new GeminiNativeProvider({
|
|
10
|
+
* apiKey: process.env.GOOGLE_AI_API_KEY,
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* const agent = new Agent({ provider });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { GoogleGenAI } from '@google/genai';
|
|
17
|
+
import { ProviderError } from '../errors.js';
|
|
18
|
+
/**
|
|
19
|
+
* Default model for Gemini API
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_MODEL = 'gemini-2.5-flash';
|
|
22
|
+
/**
|
|
23
|
+
* Default max tokens
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_MAX_TOKENS = 4096;
|
|
26
|
+
/**
|
|
27
|
+
* GeminiNativeProvider implements LLMProvider using the native Google Gen AI SDK
|
|
28
|
+
*
|
|
29
|
+
* Benefits over OpenAI-compatible endpoint:
|
|
30
|
+
* - Automatic thought signature handling for Gemini 3
|
|
31
|
+
* - Native streaming support
|
|
32
|
+
* - Full access to Gemini-specific features
|
|
33
|
+
*/
|
|
34
|
+
export class GeminiNativeProvider {
|
|
35
|
+
name = 'gemini';
|
|
36
|
+
client;
|
|
37
|
+
defaultModel;
|
|
38
|
+
defaultMaxTokens;
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.client = new GoogleGenAI({ apiKey: config.apiKey });
|
|
41
|
+
this.defaultModel = config.model ?? DEFAULT_MODEL;
|
|
42
|
+
this.defaultMaxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Send messages and stream the response
|
|
46
|
+
*/
|
|
47
|
+
async *chat(messages, options) {
|
|
48
|
+
const { systemInstruction, contents } = this.convertMessages(messages);
|
|
49
|
+
const tools = this.convertTools(options?.tools);
|
|
50
|
+
const model = options?.model ?? this.defaultModel;
|
|
51
|
+
// Calculate payload sizes for debugging
|
|
52
|
+
const debugPayload = {
|
|
53
|
+
systemChars: systemInstruction?.length ?? 0,
|
|
54
|
+
contentsChars: JSON.stringify(contents).length,
|
|
55
|
+
toolsChars: JSON.stringify(tools).length,
|
|
56
|
+
};
|
|
57
|
+
try {
|
|
58
|
+
// Build config
|
|
59
|
+
const config = {
|
|
60
|
+
systemInstruction,
|
|
61
|
+
maxOutputTokens: options?.maxTokens ?? this.defaultMaxTokens,
|
|
62
|
+
temperature: options?.temperature,
|
|
63
|
+
stopSequences: options?.stopSequences,
|
|
64
|
+
};
|
|
65
|
+
// Add tools if any
|
|
66
|
+
if (tools.length > 0) {
|
|
67
|
+
config.tools = [{ functionDeclarations: tools }];
|
|
68
|
+
}
|
|
69
|
+
// Request streaming response
|
|
70
|
+
const streamResponse = await this.client.models.generateContentStream({
|
|
71
|
+
model,
|
|
72
|
+
contents,
|
|
73
|
+
config,
|
|
74
|
+
});
|
|
75
|
+
// Track state for tool calls
|
|
76
|
+
let currentToolId = '';
|
|
77
|
+
let currentToolName = '';
|
|
78
|
+
let toolInputJson = '';
|
|
79
|
+
let inThinkingBlock = false;
|
|
80
|
+
// Track the last usage metadata (Gemini sends it in EVERY chunk, not just the final one)
|
|
81
|
+
let lastUsageMetadata;
|
|
82
|
+
// Process stream chunks
|
|
83
|
+
for await (const chunk of streamResponse) {
|
|
84
|
+
const streamChunks = this.processChunk(chunk, currentToolId, currentToolName, toolInputJson, inThinkingBlock);
|
|
85
|
+
for (const streamChunk of streamChunks) {
|
|
86
|
+
// Update tracking state
|
|
87
|
+
if (streamChunk.type === 'tool_use_start' && streamChunk.toolUse) {
|
|
88
|
+
currentToolId = streamChunk.toolUse.id;
|
|
89
|
+
currentToolName = streamChunk.toolUse.name;
|
|
90
|
+
toolInputJson = '';
|
|
91
|
+
}
|
|
92
|
+
else if (streamChunk.type === 'tool_use_delta' && streamChunk.text) {
|
|
93
|
+
toolInputJson += streamChunk.text;
|
|
94
|
+
}
|
|
95
|
+
else if (streamChunk.type === 'tool_use_end') {
|
|
96
|
+
currentToolId = '';
|
|
97
|
+
currentToolName = '';
|
|
98
|
+
toolInputJson = '';
|
|
99
|
+
}
|
|
100
|
+
else if (streamChunk.type === 'thinking_start') {
|
|
101
|
+
inThinkingBlock = true;
|
|
102
|
+
}
|
|
103
|
+
else if (streamChunk.type === 'thinking_end') {
|
|
104
|
+
inThinkingBlock = false;
|
|
105
|
+
}
|
|
106
|
+
yield streamChunk;
|
|
107
|
+
}
|
|
108
|
+
// Track usage metadata (update on every chunk, yield only once at end)
|
|
109
|
+
if (chunk.usageMetadata) {
|
|
110
|
+
lastUsageMetadata = chunk.usageMetadata;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Yield 'done' only ONCE after stream completes with the final usage
|
|
114
|
+
if (lastUsageMetadata) {
|
|
115
|
+
yield {
|
|
116
|
+
type: 'done',
|
|
117
|
+
model,
|
|
118
|
+
usage: {
|
|
119
|
+
inputTokens: lastUsageMetadata.promptTokenCount ?? 0,
|
|
120
|
+
outputTokens: lastUsageMetadata.candidatesTokenCount ?? 0,
|
|
121
|
+
// Gemini 2.5+ models report thinking tokens separately
|
|
122
|
+
...(lastUsageMetadata.thoughtsTokenCount
|
|
123
|
+
? { thinkingTokens: lastUsageMetadata.thoughtsTokenCount }
|
|
124
|
+
: {}),
|
|
125
|
+
// Gemini reports cached content tokens
|
|
126
|
+
...(lastUsageMetadata.cachedContentTokenCount
|
|
127
|
+
? { cacheReadTokens: lastUsageMetadata.cachedContentTokenCount }
|
|
128
|
+
: {}),
|
|
129
|
+
// Debug payload info
|
|
130
|
+
debugPayload,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw this.mapError(error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Count tokens in messages (approximation)
|
|
141
|
+
*/
|
|
142
|
+
countTokens(messages) {
|
|
143
|
+
// Approximation: ~4 characters per token
|
|
144
|
+
let charCount = 0;
|
|
145
|
+
for (const msg of messages) {
|
|
146
|
+
if (typeof msg.content === 'string') {
|
|
147
|
+
charCount += msg.content.length;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
for (const block of msg.content) {
|
|
151
|
+
switch (block.type) {
|
|
152
|
+
case 'text':
|
|
153
|
+
charCount += block.text.length;
|
|
154
|
+
break;
|
|
155
|
+
case 'tool_use':
|
|
156
|
+
charCount += JSON.stringify(block.input).length;
|
|
157
|
+
break;
|
|
158
|
+
case 'tool_result':
|
|
159
|
+
charCount += block.content.length;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return Promise.resolve(Math.ceil(charCount / 4));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Convert our Message format to Google's format
|
|
169
|
+
*/
|
|
170
|
+
convertMessages(messages) {
|
|
171
|
+
let systemInstruction;
|
|
172
|
+
const contents = [];
|
|
173
|
+
for (const msg of messages) {
|
|
174
|
+
if (msg.role === 'system') {
|
|
175
|
+
// Google uses systemInstruction in config, not a message
|
|
176
|
+
systemInstruction = typeof msg.content === 'string' ? msg.content : '';
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// user → user, assistant → model
|
|
180
|
+
const role = msg.role === 'assistant' ? 'model' : 'user';
|
|
181
|
+
const parts = this.convertContent(msg.content);
|
|
182
|
+
contents.push({ role, parts });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { systemInstruction, contents };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Convert content to Google's Part format
|
|
189
|
+
*/
|
|
190
|
+
convertContent(content) {
|
|
191
|
+
if (typeof content === 'string') {
|
|
192
|
+
return [{ text: content }];
|
|
193
|
+
}
|
|
194
|
+
const parts = [];
|
|
195
|
+
for (const block of content) {
|
|
196
|
+
switch (block.type) {
|
|
197
|
+
case 'text':
|
|
198
|
+
parts.push({ text: block.text });
|
|
199
|
+
break;
|
|
200
|
+
case 'tool_use':
|
|
201
|
+
// Convert to functionCall with thought signature if present
|
|
202
|
+
// Gemini 3 requires thought signatures on function calls
|
|
203
|
+
// Note: No 'id' field - Gemini uses 'name' for matching
|
|
204
|
+
parts.push({
|
|
205
|
+
functionCall: {
|
|
206
|
+
name: block.name,
|
|
207
|
+
args: block.input,
|
|
208
|
+
},
|
|
209
|
+
// Include thought signature if present (required for Gemini 3)
|
|
210
|
+
...(block.signature ? { thoughtSignature: block.signature } : {}),
|
|
211
|
+
});
|
|
212
|
+
break;
|
|
213
|
+
case 'tool_result': {
|
|
214
|
+
// Convert to functionResponse
|
|
215
|
+
// block.content is a JSON string from the agent - parse it for the API
|
|
216
|
+
let responseData;
|
|
217
|
+
try {
|
|
218
|
+
responseData = JSON.parse(block.content);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// If not valid JSON, wrap the string in a result object
|
|
222
|
+
responseData = { result: block.content };
|
|
223
|
+
}
|
|
224
|
+
parts.push({
|
|
225
|
+
functionResponse: {
|
|
226
|
+
// Note: No 'id' field - Gemini matches by 'name' only
|
|
227
|
+
name: this.extractToolName(content, block.toolUseId),
|
|
228
|
+
response: responseData,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case 'thinking':
|
|
234
|
+
// Thinking blocks should NOT be sent back to Gemini.
|
|
235
|
+
// They are internal model reasoning. Only the signature on function calls matters.
|
|
236
|
+
// Skip - do not add to parts.
|
|
237
|
+
break;
|
|
238
|
+
default: {
|
|
239
|
+
// Exhaustive check
|
|
240
|
+
const _exhaustive = block;
|
|
241
|
+
throw new Error(`Unknown content block type: ${_exhaustive.type}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return parts;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Extract tool name from tool_use block that matches the given toolUseId
|
|
249
|
+
*/
|
|
250
|
+
extractToolName(content, toolUseId) {
|
|
251
|
+
for (const block of content) {
|
|
252
|
+
if (block.type === 'tool_use' && block.id === toolUseId) {
|
|
253
|
+
return block.name;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Fallback if not found in same message - this shouldn't happen normally
|
|
257
|
+
return 'unknown_function';
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Convert our ToolDefinition to Google's FunctionDeclaration format
|
|
261
|
+
*/
|
|
262
|
+
convertTools(tools) {
|
|
263
|
+
if (!tools || tools.length === 0) {
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
return tools.map((tool) => ({
|
|
267
|
+
name: tool.name,
|
|
268
|
+
description: tool.description,
|
|
269
|
+
// Use parametersJsonSchema for raw JSON schema (more flexible than typed Schema)
|
|
270
|
+
parametersJsonSchema: {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: tool.inputSchema.properties,
|
|
273
|
+
required: tool.inputSchema.required,
|
|
274
|
+
},
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Process a streaming chunk into StreamChunks
|
|
279
|
+
*/
|
|
280
|
+
processChunk(chunk, _currentToolId, _currentToolName, _toolInputJson, inThinkingBlock) {
|
|
281
|
+
const chunks = [];
|
|
282
|
+
// Get the first candidate's content
|
|
283
|
+
const candidate = chunk.candidates?.[0];
|
|
284
|
+
if (!candidate?.content?.parts) {
|
|
285
|
+
return chunks;
|
|
286
|
+
}
|
|
287
|
+
for (const part of candidate.content.parts) {
|
|
288
|
+
// Text content
|
|
289
|
+
if (part.text !== undefined && !part.thought) {
|
|
290
|
+
chunks.push({
|
|
291
|
+
type: 'text',
|
|
292
|
+
text: part.text,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
// Thinking content (Gemini 3)
|
|
296
|
+
if (part.thought && part.text !== undefined) {
|
|
297
|
+
if (!inThinkingBlock) {
|
|
298
|
+
chunks.push({ type: 'thinking_start' });
|
|
299
|
+
}
|
|
300
|
+
chunks.push({
|
|
301
|
+
type: 'thinking_delta',
|
|
302
|
+
text: part.text,
|
|
303
|
+
thinking: {
|
|
304
|
+
thinking: part.text,
|
|
305
|
+
signature: part.thoughtSignature,
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
// Function call
|
|
310
|
+
if (part.functionCall) {
|
|
311
|
+
const funcCall = part.functionCall;
|
|
312
|
+
const toolId = funcCall.id ?? `tool_${String(Date.now())}_${Math.random().toString(36).slice(2, 9)}`;
|
|
313
|
+
// Capture thought signature if present (Gemini 3 attaches it to function calls)
|
|
314
|
+
// Note: thoughtSignature is not in the SDK types yet but exists on the response
|
|
315
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
316
|
+
const signature = part.thoughtSignature;
|
|
317
|
+
chunks.push({
|
|
318
|
+
type: 'tool_use_start',
|
|
319
|
+
toolUse: {
|
|
320
|
+
id: toolId,
|
|
321
|
+
name: funcCall.name ?? 'unknown',
|
|
322
|
+
signature, // Pass through the thought signature
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
// Emit the arguments as delta
|
|
326
|
+
if (funcCall.args) {
|
|
327
|
+
chunks.push({
|
|
328
|
+
type: 'tool_use_delta',
|
|
329
|
+
text: JSON.stringify(funcCall.args),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
chunks.push({ type: 'tool_use_end' });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Check for finish reason to end thinking block
|
|
336
|
+
if (inThinkingBlock && candidate.finishReason) {
|
|
337
|
+
chunks.push({ type: 'thinking_end' });
|
|
338
|
+
}
|
|
339
|
+
return chunks;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Map errors to ProviderError
|
|
343
|
+
*/
|
|
344
|
+
mapError(error) {
|
|
345
|
+
if (error instanceof Error) {
|
|
346
|
+
const message = error.message;
|
|
347
|
+
// Check for common error patterns
|
|
348
|
+
if (message.includes('API key')) {
|
|
349
|
+
return new ProviderError('Invalid Gemini API key. Check your GOOGLE_AI_API_KEY or GEMINI_API_KEY.', 'gemini', 401, error);
|
|
350
|
+
}
|
|
351
|
+
if (message.includes('rate limit') || message.includes('429')) {
|
|
352
|
+
return new ProviderError('Gemini rate limit exceeded. Please wait and try again.', 'gemini', 429, error);
|
|
353
|
+
}
|
|
354
|
+
if (message.includes('not found') || message.includes('404')) {
|
|
355
|
+
return new ProviderError(`Gemini model not found: ${message}`, 'gemini', 404, error);
|
|
356
|
+
}
|
|
357
|
+
return new ProviderError(message, 'gemini', undefined, error);
|
|
358
|
+
}
|
|
359
|
+
return new ProviderError(String(error), 'gemini');
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Create a GeminiNativeProvider with API key from environment
|
|
364
|
+
*/
|
|
365
|
+
export function createGeminiNativeProvider(config) {
|
|
366
|
+
const apiKey = config?.apiKey ?? process.env.GOOGLE_AI_API_KEY ?? process.env.GEMINI_API_KEY;
|
|
367
|
+
if (!apiKey) {
|
|
368
|
+
throw new ProviderError('Gemini API key not found. Set GOOGLE_AI_API_KEY or GEMINI_API_KEY environment variable or pass apiKey in config.', 'gemini');
|
|
369
|
+
}
|
|
370
|
+
return new GeminiNativeProvider({
|
|
371
|
+
...config,
|
|
372
|
+
apiKey,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groq LLM Provider
|
|
3
|
+
*
|
|
4
|
+
* Implements LLMProvider interface for Groq models.
|
|
5
|
+
* Extends OpenAICompatibleProvider for shared functionality.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const provider = createGroqProvider({
|
|
10
|
+
* model: 'llama-3.2-90b-vision-preview',
|
|
11
|
+
* apiKey: process.env.GROQ_API_KEY
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* - Requires valid Groq API key
|
|
17
|
+
* - Default model is llama-3.2-8b-preview
|
|
18
|
+
* - Known for extremely fast inference speeds
|
|
19
|
+
*/
|
|
20
|
+
import type { ChatOptions } from './types.js';
|
|
21
|
+
import { ProviderError } from '../errors.js';
|
|
22
|
+
import { OpenAICompatibleProvider } from './openai-compatible.js';
|
|
23
|
+
/**
|
|
24
|
+
* Configuration for GroqProvider
|
|
25
|
+
*/
|
|
26
|
+
export interface GroqProviderConfig {
|
|
27
|
+
/** Groq API key (falls back to GROQ_API_KEY env var) */
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
/** Base URL for Groq API (default: https://api.groq.com/openai) */
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
/** Default model to use (default: llama-3.2-8b-preview) */
|
|
32
|
+
model?: string;
|
|
33
|
+
/** Default max tokens (default: 4096) */
|
|
34
|
+
maxTokens?: number;
|
|
35
|
+
/** Request timeout in milliseconds (default: 120000) */
|
|
36
|
+
timeout?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Groq LLM Provider
|
|
40
|
+
*
|
|
41
|
+
* Provides streaming chat completion using Groq's ultra-fast inference.
|
|
42
|
+
* Supports Llama, Mixtral, and other models optimized for speed.
|
|
43
|
+
*/
|
|
44
|
+
export declare class GroqProvider extends OpenAICompatibleProvider {
|
|
45
|
+
readonly name = "groq";
|
|
46
|
+
private readonly apiKey;
|
|
47
|
+
constructor(config?: GroqProviderConfig);
|
|
48
|
+
/**
|
|
49
|
+
* Groq authentication with Bearer token
|
|
50
|
+
*/
|
|
51
|
+
protected getAuthHeaders(): Record<string, string>;
|
|
52
|
+
/**
|
|
53
|
+
* Groq chat completions endpoint (OpenAI-compatible)
|
|
54
|
+
*/
|
|
55
|
+
protected getEndpointPath(): string;
|
|
56
|
+
/**
|
|
57
|
+
* Groq uses standard OpenAI body format
|
|
58
|
+
*/
|
|
59
|
+
protected buildProviderSpecificBody(_options?: ChatOptions): Record<string, unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* Map HTTP errors with Groq-specific messages
|
|
62
|
+
*/
|
|
63
|
+
protected mapHttpError(status: number, body: string, _model: string): ProviderError;
|
|
64
|
+
/**
|
|
65
|
+
* Map connection errors with Groq-specific messages
|
|
66
|
+
*/
|
|
67
|
+
protected mapConnectionError(_error: Error): ProviderError;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a Groq provider instance
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* // Using environment variable (GROQ_API_KEY)
|
|
75
|
+
* const provider = createGroqProvider();
|
|
76
|
+
*
|
|
77
|
+
* // With explicit API key
|
|
78
|
+
* const provider = createGroqProvider({ apiKey: 'gsk_...' });
|
|
79
|
+
*
|
|
80
|
+
* // With custom model
|
|
81
|
+
* const provider = createGroqProvider({
|
|
82
|
+
* model: 'llama-3.2-90b-vision-preview'
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function createGroqProvider(config?: GroqProviderConfig): GroqProvider;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groq LLM Provider
|
|
3
|
+
*
|
|
4
|
+
* Implements LLMProvider interface for Groq models.
|
|
5
|
+
* Extends OpenAICompatibleProvider for shared functionality.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const provider = createGroqProvider({
|
|
10
|
+
* model: 'llama-3.2-90b-vision-preview',
|
|
11
|
+
* apiKey: process.env.GROQ_API_KEY
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* - Requires valid Groq API key
|
|
17
|
+
* - Default model is llama-3.2-8b-preview
|
|
18
|
+
* - Known for extremely fast inference speeds
|
|
19
|
+
*/
|
|
20
|
+
import { ProviderError } from '../errors.js';
|
|
21
|
+
import { OpenAICompatibleProvider } from './openai-compatible.js';
|
|
22
|
+
// Default configuration
|
|
23
|
+
const DEFAULT_MODEL = 'llama-3.2-8b-preview';
|
|
24
|
+
const DEFAULT_BASE_URL = 'https://api.groq.com/openai';
|
|
25
|
+
/**
|
|
26
|
+
* Groq LLM Provider
|
|
27
|
+
*
|
|
28
|
+
* Provides streaming chat completion using Groq's ultra-fast inference.
|
|
29
|
+
* Supports Llama, Mixtral, and other models optimized for speed.
|
|
30
|
+
*/
|
|
31
|
+
export class GroqProvider extends OpenAICompatibleProvider {
|
|
32
|
+
name = 'groq';
|
|
33
|
+
apiKey;
|
|
34
|
+
constructor(config = {}) {
|
|
35
|
+
const apiKey = config.apiKey ?? process.env.GROQ_API_KEY;
|
|
36
|
+
if (!apiKey) {
|
|
37
|
+
throw new ProviderError('Groq API key not found. Set GROQ_API_KEY environment variable or pass apiKey in config.', 'groq');
|
|
38
|
+
}
|
|
39
|
+
const baseConfig = {
|
|
40
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
41
|
+
model: config.model ?? DEFAULT_MODEL,
|
|
42
|
+
maxTokens: config.maxTokens,
|
|
43
|
+
timeout: config.timeout,
|
|
44
|
+
};
|
|
45
|
+
super(baseConfig);
|
|
46
|
+
this.apiKey = apiKey;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Groq authentication with Bearer token
|
|
50
|
+
*/
|
|
51
|
+
getAuthHeaders() {
|
|
52
|
+
return {
|
|
53
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Groq chat completions endpoint (OpenAI-compatible)
|
|
58
|
+
*/
|
|
59
|
+
getEndpointPath() {
|
|
60
|
+
return '/v1/chat/completions';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Groq uses standard OpenAI body format
|
|
64
|
+
*/
|
|
65
|
+
buildProviderSpecificBody(_options) {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Map HTTP errors with Groq-specific messages
|
|
70
|
+
*/
|
|
71
|
+
mapHttpError(status, body, _model) {
|
|
72
|
+
let message = `Groq error (${String(status)})`;
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(body);
|
|
75
|
+
if (parsed.error?.message) {
|
|
76
|
+
message = parsed.error.message;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
message = body || message;
|
|
81
|
+
}
|
|
82
|
+
switch (status) {
|
|
83
|
+
case 401:
|
|
84
|
+
return new ProviderError('Invalid Groq API key. Check your GROQ_API_KEY.', 'groq', 401);
|
|
85
|
+
case 403:
|
|
86
|
+
return new ProviderError('Access denied. Check your Groq API key permissions.', 'groq', 403);
|
|
87
|
+
case 429:
|
|
88
|
+
return new ProviderError('Groq rate limit exceeded. Please wait and try again.', 'groq', 429);
|
|
89
|
+
case 500:
|
|
90
|
+
case 502:
|
|
91
|
+
case 503:
|
|
92
|
+
return new ProviderError('Groq service temporarily unavailable. Please try again later.', 'groq', status);
|
|
93
|
+
default:
|
|
94
|
+
return new ProviderError(message, 'groq', status);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Map connection errors with Groq-specific messages
|
|
99
|
+
*/
|
|
100
|
+
mapConnectionError(_error) {
|
|
101
|
+
return new ProviderError('Failed to connect to Groq API. Check your internet connection.', 'groq');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a Groq provider instance
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* // Using environment variable (GROQ_API_KEY)
|
|
110
|
+
* const provider = createGroqProvider();
|
|
111
|
+
*
|
|
112
|
+
* // With explicit API key
|
|
113
|
+
* const provider = createGroqProvider({ apiKey: 'gsk_...' });
|
|
114
|
+
*
|
|
115
|
+
* // With custom model
|
|
116
|
+
* const provider = createGroqProvider({
|
|
117
|
+
* model: 'llama-3.2-90b-vision-preview'
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export function createGroqProvider(config = {}) {
|
|
122
|
+
return new GroqProvider(config);
|
|
123
|
+
}
|
|
@@ -12,5 +12,20 @@ export { OpenAICompatibleProvider } from './openai-compatible.js';
|
|
|
12
12
|
export type { OpenAICompatibleConfig, OpenAIMessage, OpenAIToolCall, OpenAITool, OpenAIStreamChunk, } from './openai-compatible.js';
|
|
13
13
|
export { OpenAIProvider, createOpenAIProvider } from './openai.js';
|
|
14
14
|
export type { OpenAIProviderConfig } from './openai.js';
|
|
15
|
-
export { GeminiProvider, createGeminiProvider } from './gemini.js';
|
|
16
|
-
export type { GeminiProviderConfig } from './gemini.js';
|
|
15
|
+
export { GeminiProvider as GeminiLegacyProvider, createGeminiProvider as createGeminiLegacyProvider, } from './gemini.js';
|
|
16
|
+
export type { GeminiProviderConfig as GeminiLegacyProviderConfig } from './gemini.js';
|
|
17
|
+
export { GeminiNativeProvider, createGeminiNativeProvider } from './gemini-native.js';
|
|
18
|
+
export type { GeminiNativeProviderConfig } from './gemini-native.js';
|
|
19
|
+
export { GeminiNativeProvider as GeminiProvider } from './gemini-native.js';
|
|
20
|
+
export { createGeminiNativeProvider as createGeminiProvider } from './gemini-native.js';
|
|
21
|
+
export type { GeminiNativeProviderConfig as GeminiProviderConfig } from './gemini-native.js';
|
|
22
|
+
export { TogetherProvider, createTogetherProvider } from './together.js';
|
|
23
|
+
export type { TogetherProviderConfig } from './together.js';
|
|
24
|
+
export { GroqProvider, createGroqProvider } from './groq.js';
|
|
25
|
+
export type { GroqProviderConfig } from './groq.js';
|
|
26
|
+
export { FireworksProvider, createFireworksProvider } from './fireworks.js';
|
|
27
|
+
export type { FireworksProviderConfig } from './fireworks.js';
|
|
28
|
+
export { PerplexityProvider, createPerplexityProvider } from './perplexity.js';
|
|
29
|
+
export type { PerplexityProviderConfig } from './perplexity.js';
|
|
30
|
+
export { OpenRouterProvider, createOpenRouterProvider } from './openrouter.js';
|
|
31
|
+
export type { OpenRouterProviderConfig } from './openrouter.js';
|
package/dist/providers/index.js
CHANGED
|
@@ -11,5 +11,16 @@ export { OllamaProvider, createOllamaProvider } from './ollama.js';
|
|
|
11
11
|
export { OpenAICompatibleProvider } from './openai-compatible.js';
|
|
12
12
|
// OpenAI provider
|
|
13
13
|
export { OpenAIProvider, createOpenAIProvider } from './openai.js';
|
|
14
|
-
// Gemini provider
|
|
15
|
-
export { GeminiProvider, createGeminiProvider } from './gemini.js';
|
|
14
|
+
// Gemini provider (legacy OpenAI-compatible endpoint)
|
|
15
|
+
export { GeminiProvider as GeminiLegacyProvider, createGeminiProvider as createGeminiLegacyProvider, } from './gemini.js';
|
|
16
|
+
// Gemini native provider (recommended - supports Gemini 3 thought signatures)
|
|
17
|
+
export { GeminiNativeProvider, createGeminiNativeProvider } from './gemini-native.js';
|
|
18
|
+
// Re-export native as default Gemini provider
|
|
19
|
+
export { GeminiNativeProvider as GeminiProvider } from './gemini-native.js';
|
|
20
|
+
export { createGeminiNativeProvider as createGeminiProvider } from './gemini-native.js';
|
|
21
|
+
// "Others" providers - OpenAI-compatible cloud APIs
|
|
22
|
+
export { TogetherProvider, createTogetherProvider } from './together.js';
|
|
23
|
+
export { GroqProvider, createGroqProvider } from './groq.js';
|
|
24
|
+
export { FireworksProvider, createFireworksProvider } from './fireworks.js';
|
|
25
|
+
export { PerplexityProvider, createPerplexityProvider } from './perplexity.js';
|
|
26
|
+
export { OpenRouterProvider, createOpenRouterProvider } from './openrouter.js';
|
|
@@ -57,6 +57,15 @@ export class OpenAICompatibleProvider {
|
|
|
57
57
|
const openaiMessages = this.convertMessages(messages);
|
|
58
58
|
// Convert tools if provided
|
|
59
59
|
const tools = options?.tools ? this.convertTools(options.tools) : undefined;
|
|
60
|
+
// Calculate payload sizes for debugging
|
|
61
|
+
// Note: OpenAI format has system message in messages array, not separate
|
|
62
|
+
const systemMsg = openaiMessages.find((m) => m.role === 'system');
|
|
63
|
+
const systemChars = systemMsg && typeof systemMsg.content === 'string' ? systemMsg.content.length : 0;
|
|
64
|
+
const debugPayload = {
|
|
65
|
+
systemChars,
|
|
66
|
+
contentsChars: JSON.stringify(openaiMessages).length,
|
|
67
|
+
toolsChars: tools ? JSON.stringify(tools).length : 0,
|
|
68
|
+
};
|
|
60
69
|
// Build request body
|
|
61
70
|
const body = {
|
|
62
71
|
model,
|
|
@@ -143,7 +152,9 @@ export class OpenAICompatibleProvider {
|
|
|
143
152
|
// Yield done chunk with usage
|
|
144
153
|
yield {
|
|
145
154
|
type: 'done',
|
|
146
|
-
usage
|
|
155
|
+
usage: usage
|
|
156
|
+
? { ...usage, debugPayload }
|
|
157
|
+
: { inputTokens: 0, outputTokens: 0, debugPayload },
|
|
147
158
|
};
|
|
148
159
|
}
|
|
149
160
|
catch (error) {
|