@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.
Files changed (44) hide show
  1. package/dist/agent.d.ts +107 -2
  2. package/dist/agent.js +151 -22
  3. package/dist/context/manager.d.ts +8 -0
  4. package/dist/context/manager.js +25 -2
  5. package/dist/errors.d.ts +20 -1
  6. package/dist/errors.js +44 -2
  7. package/dist/index.d.ts +16 -1
  8. package/dist/index.js +13 -1
  9. package/dist/messages/index.d.ts +12 -5
  10. package/dist/messages/index.js +53 -15
  11. package/dist/providers/claude.js +7 -0
  12. package/dist/providers/fireworks.d.ts +86 -0
  13. package/dist/providers/fireworks.js +123 -0
  14. package/dist/providers/gemini-native.d.ts +86 -0
  15. package/dist/providers/gemini-native.js +374 -0
  16. package/dist/providers/groq.d.ts +86 -0
  17. package/dist/providers/groq.js +123 -0
  18. package/dist/providers/index.d.ts +17 -2
  19. package/dist/providers/index.js +13 -2
  20. package/dist/providers/openai-compatible.js +12 -1
  21. package/dist/providers/openrouter.d.ts +95 -0
  22. package/dist/providers/openrouter.js +138 -0
  23. package/dist/providers/perplexity.d.ts +86 -0
  24. package/dist/providers/perplexity.js +123 -0
  25. package/dist/providers/together.d.ts +86 -0
  26. package/dist/providers/together.js +123 -0
  27. package/dist/providers/types.d.ts +19 -0
  28. package/dist/state/agent-state.d.ts +1 -0
  29. package/dist/state/agent-state.js +2 -0
  30. package/dist/state/serializer.js +20 -2
  31. package/dist/state/types.d.ts +5 -0
  32. package/dist/tools/builtin/ask-user-simple.js +1 -0
  33. package/dist/tools/builtin/ask-user.js +1 -0
  34. package/dist/tools/builtin/bash.js +123 -2
  35. package/dist/tools/builtin/shell-manager.d.ts +15 -0
  36. package/dist/tools/builtin/shell-manager.js +51 -0
  37. package/dist/tools/builtin/suggest.js +1 -0
  38. package/dist/tools/builtin/todo.js +2 -0
  39. package/dist/tools/define.d.ts +6 -0
  40. package/dist/tools/define.js +1 -0
  41. package/dist/tools/types.d.ts +19 -0
  42. package/dist/utils/index.d.ts +119 -4
  43. package/dist/utils/index.js +164 -13
  44. 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';
@@ -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) {