universal-llm-client 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/ai-model.d.ts +20 -22
  2. package/dist/ai-model.d.ts.map +1 -1
  3. package/dist/ai-model.js +26 -23
  4. package/dist/ai-model.js.map +1 -1
  5. package/dist/client.d.ts +5 -5
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +17 -9
  8. package/dist/client.js.map +1 -1
  9. package/dist/http.d.ts +2 -0
  10. package/dist/http.d.ts.map +1 -1
  11. package/dist/http.js +1 -0
  12. package/dist/http.js.map +1 -1
  13. package/dist/index.d.ts +3 -3
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +4 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/interfaces.d.ts +49 -11
  18. package/dist/interfaces.d.ts.map +1 -1
  19. package/dist/interfaces.js +14 -0
  20. package/dist/interfaces.js.map +1 -1
  21. package/dist/providers/anthropic.d.ts +56 -0
  22. package/dist/providers/anthropic.d.ts.map +1 -0
  23. package/dist/providers/anthropic.js +524 -0
  24. package/dist/providers/anthropic.js.map +1 -0
  25. package/dist/providers/google.d.ts +5 -0
  26. package/dist/providers/google.d.ts.map +1 -1
  27. package/dist/providers/google.js +64 -8
  28. package/dist/providers/google.js.map +1 -1
  29. package/dist/providers/index.d.ts +1 -0
  30. package/dist/providers/index.d.ts.map +1 -1
  31. package/dist/providers/index.js +1 -0
  32. package/dist/providers/index.js.map +1 -1
  33. package/dist/providers/ollama.d.ts.map +1 -1
  34. package/dist/providers/ollama.js +38 -11
  35. package/dist/providers/ollama.js.map +1 -1
  36. package/dist/providers/openai.d.ts.map +1 -1
  37. package/dist/providers/openai.js +9 -7
  38. package/dist/providers/openai.js.map +1 -1
  39. package/dist/router.d.ts +13 -33
  40. package/dist/router.d.ts.map +1 -1
  41. package/dist/router.js +33 -57
  42. package/dist/router.js.map +1 -1
  43. package/dist/stream-decoder.d.ts +29 -2
  44. package/dist/stream-decoder.d.ts.map +1 -1
  45. package/dist/stream-decoder.js +39 -11
  46. package/dist/stream-decoder.js.map +1 -1
  47. package/dist/structured-output.d.ts +107 -181
  48. package/dist/structured-output.d.ts.map +1 -1
  49. package/dist/structured-output.js +137 -192
  50. package/dist/structured-output.js.map +1 -1
  51. package/dist/zod-adapter.d.ts +44 -0
  52. package/dist/zod-adapter.d.ts.map +1 -0
  53. package/dist/zod-adapter.js +61 -0
  54. package/dist/zod-adapter.js.map +1 -0
  55. package/package.json +9 -1
  56. package/src/ai-model.ts +350 -0
  57. package/src/auditor.ts +213 -0
  58. package/src/client.ts +402 -0
  59. package/src/debug/debug-google-streaming.ts +97 -0
  60. package/src/debug/debug-tool-execution.ts +86 -0
  61. package/src/debug/test-lmstudio-tools.ts +155 -0
  62. package/src/demos/README.md +47 -0
  63. package/src/demos/basic/universal-llm-examples.ts +161 -0
  64. package/src/demos/mcp/astrid-memory-demo.ts +295 -0
  65. package/src/demos/mcp/astrid-persona-memory.ts +357 -0
  66. package/src/demos/mcp/mcp-mongodb-demo.ts +275 -0
  67. package/src/demos/mcp/simple-astrid-memory.ts +148 -0
  68. package/src/demos/mcp/simple-mcp-demo.ts +68 -0
  69. package/src/demos/mcp/working-mcp-demo.ts +62 -0
  70. package/src/demos/model-alias-demo.ts +0 -0
  71. package/src/demos/tools/RAG_MEMORY_INTEGRATION.md +267 -0
  72. package/src/demos/tools/astrid-memory-demo.ts +270 -0
  73. package/src/demos/tools/astrid-production-memory-clean.ts +785 -0
  74. package/src/demos/tools/astrid-production-memory.ts +558 -0
  75. package/src/demos/tools/basic-translation-test.ts +66 -0
  76. package/src/demos/tools/chromadb-similarity-tuning.ts +390 -0
  77. package/src/demos/tools/clean-multilingual-conversation.ts +209 -0
  78. package/src/demos/tools/clean-translation-test.ts +119 -0
  79. package/src/demos/tools/clean-universal-multilingual-test.ts +131 -0
  80. package/src/demos/tools/complete-rag-demo.ts +369 -0
  81. package/src/demos/tools/complete-tool-demo.ts +132 -0
  82. package/src/demos/tools/demo-tool-calling.ts +124 -0
  83. package/src/demos/tools/dynamic-language-switching-test.ts +251 -0
  84. package/src/demos/tools/hybrid-thinking-test.ts +154 -0
  85. package/src/demos/tools/memory-integration-test.ts +420 -0
  86. package/src/demos/tools/multilingual-memory-system.ts +802 -0
  87. package/src/demos/tools/ondemand-translation-demo.ts +655 -0
  88. package/src/demos/tools/production-tool-demo.ts +245 -0
  89. package/src/demos/tools/revolutionary-multilingual-test.ts +151 -0
  90. package/src/demos/tools/rigorous-language-analysis.ts +218 -0
  91. package/src/demos/tools/test-universal-memory-system.ts +126 -0
  92. package/src/demos/tools/translation-integration-guide.ts +346 -0
  93. package/src/demos/tools/universal-memory-system.ts +560 -0
  94. package/src/http.ts +247 -0
  95. package/src/index.ts +161 -0
  96. package/src/interfaces.ts +657 -0
  97. package/src/mcp.ts +345 -0
  98. package/src/providers/anthropic.ts +762 -0
  99. package/src/providers/google.ts +620 -0
  100. package/src/providers/index.ts +8 -0
  101. package/src/providers/ollama.ts +469 -0
  102. package/src/providers/openai.ts +392 -0
  103. package/src/router.ts +780 -0
  104. package/src/stream-decoder.ts +361 -0
  105. package/src/structured-output.ts +759 -0
  106. package/src/test-scripts/test-advanced-tools.ts +310 -0
  107. package/src/test-scripts/test-google-streaming-enhanced.ts +147 -0
  108. package/src/test-scripts/test-google-streaming.ts +63 -0
  109. package/src/test-scripts/test-google-system-prompt-comprehensive.ts +189 -0
  110. package/src/test-scripts/test-mcp-config.ts +28 -0
  111. package/src/test-scripts/test-mcp-connection.ts +29 -0
  112. package/src/test-scripts/test-system-message-positions.ts +163 -0
  113. package/src/test-scripts/test-system-prompt-improvement-demo.ts +83 -0
  114. package/src/test-scripts/test-tool-calling.ts +231 -0
  115. package/src/tests/ai-model.test.ts +1614 -0
  116. package/src/tests/auditor.test.ts +224 -0
  117. package/src/tests/http.test.ts +200 -0
  118. package/src/tests/interfaces.test.ts +117 -0
  119. package/src/tests/providers/google.test.ts +660 -0
  120. package/src/tests/providers/ollama.test.ts +954 -0
  121. package/src/tests/providers/openai.test.ts +1122 -0
  122. package/src/tests/router.test.ts +254 -0
  123. package/src/tests/stream-decoder.test.ts +179 -0
  124. package/src/tests/structured-output.test.ts +1450 -0
  125. package/src/tests/tools.test.ts +175 -0
  126. package/src/tools.ts +246 -0
  127. package/src/zod-adapter.ts +72 -0
@@ -0,0 +1,657 @@
1
+ /**
2
+ * Universal LLM Client v3 — Core Interfaces
3
+ *
4
+ * All types, enums, and helper functions used throughout the library.
5
+ * Zero dependencies — pure TypeScript types.
6
+ */
7
+
8
+ // ============================================================================
9
+ // Enums
10
+ // ============================================================================
11
+
12
+ export enum AIModelType {
13
+ Chat = 'chat',
14
+ Embedding = 'embedding',
15
+ }
16
+
17
+ export enum AIModelApiType {
18
+ Ollama = 'ollama',
19
+ OpenAI = 'openai',
20
+ Google = 'google',
21
+ Vertex = 'vertex',
22
+ LlamaCpp = 'llamacpp',
23
+ Anthropic = 'anthropic',
24
+ }
25
+
26
+ // ============================================================================
27
+ // Model Metadata
28
+ // ============================================================================
29
+
30
+ export interface ModelMetadata {
31
+ /** Model name as reported by provider */
32
+ model?: string;
33
+ /** Context window size in tokens */
34
+ contextLength: number;
35
+ /** Model architecture (e.g., "llama", "mistral3") */
36
+ architecture?: string;
37
+ /** Parameter count */
38
+ parameterCount?: number;
39
+ /** Model capabilities reported by provider (e.g., "tools", "vision", "thinking") */
40
+ capabilities?: string[];
41
+ }
42
+
43
+ // ============================================================================
44
+ // Provider Configuration (user-facing)
45
+ // ============================================================================
46
+
47
+ export interface ProviderConfig {
48
+ /** Provider type */
49
+ type: AIModelApiType | 'ollama' | 'openai' | 'google' | 'vertex' | 'llamacpp' | 'anthropic';
50
+ /** Provider endpoint URL (has sensible defaults per type) */
51
+ url?: string;
52
+ /** API key or Bearer token */
53
+ apiKey?: string;
54
+ /** Override model name for this specific provider */
55
+ model?: string;
56
+ /** Explicit priority (default: array order, lower = higher priority) */
57
+ priority?: number;
58
+ /** Vertex AI region (e.g., "us-central1") */
59
+ region?: string;
60
+ /** Google API version (default: "v1beta") */
61
+ apiVersion?: 'v1' | 'v1beta';
62
+ }
63
+
64
+ // ============================================================================
65
+ // AIModel Configuration (user-facing)
66
+ // ============================================================================
67
+
68
+ export interface AIModelConfig {
69
+ /** Model name (used across all providers unless overridden) */
70
+ model: string;
71
+ /** Ordered list of providers (first = highest priority) */
72
+ providers: ProviderConfig[];
73
+ /** Default parameters for all requests (temperature, top_p, etc.) */
74
+ defaultParameters?: Record<string, unknown>;
75
+ /** Enable thinking/reasoning mode */
76
+ thinking?: boolean;
77
+ /** Request timeout in ms (default: 30000) */
78
+ timeout?: number;
79
+ /** Retries per provider before failover (default: 2) */
80
+ retries?: number;
81
+ /** Observability hooks */
82
+ auditor?: import('./auditor.js').Auditor;
83
+ /** Enable debug logging */
84
+ debug?: boolean;
85
+ }
86
+
87
+ // ============================================================================
88
+ // Internal Client Options
89
+ // ============================================================================
90
+
91
+ export interface LLMClientOptions {
92
+ /** Model name */
93
+ model: string;
94
+ /** Base URL for the API */
95
+ url: string;
96
+ /** API type for protocol variations */
97
+ apiType: AIModelApiType;
98
+ /** Model type (chat or embedding) */
99
+ modelType?: AIModelType;
100
+ /** Default parameters for requests */
101
+ defaultParameters?: Record<string, unknown>;
102
+ /** Enable thinking/reasoning mode */
103
+ thinking?: boolean;
104
+ /** Request timeout in ms */
105
+ timeout?: number;
106
+ /** Number of retries for failed requests */
107
+ retries?: number;
108
+ /** API key for authenticated endpoints */
109
+ apiKey?: string;
110
+ /** Enable debug logging */
111
+ debug?: boolean;
112
+ /** Vertex AI region */
113
+ region?: string;
114
+ /** Google API version */
115
+ apiVersion?: 'v1' | 'v1beta';
116
+ }
117
+
118
+ // ============================================================================
119
+ // Multimodal Content Types
120
+ // ============================================================================
121
+
122
+ export interface LLMTextContent {
123
+ type: 'text';
124
+ text: string;
125
+ }
126
+
127
+ export interface LLMImageContent {
128
+ type: 'image_url';
129
+ image_url: {
130
+ url: string;
131
+ detail?: 'auto' | 'low' | 'high';
132
+ };
133
+ }
134
+
135
+ export interface LLMAudioContent {
136
+ type: 'audio';
137
+ audio: {
138
+ /** Raw base64-encoded audio data */
139
+ data: string;
140
+ /** MIME type (e.g. 'audio/ogg', 'audio/wav', 'audio/mp3') */
141
+ mimeType: string;
142
+ };
143
+ }
144
+
145
+ export type LLMContentPart = LLMTextContent | LLMImageContent | LLMAudioContent;
146
+ export type LLMMessageContent = string | LLMContentPart[];
147
+
148
+ // ============================================================================
149
+ // Chat Message Types
150
+ // ============================================================================
151
+
152
+ export interface LLMChatMessage {
153
+ role: 'system' | 'user' | 'assistant' | 'tool';
154
+ content: LLMMessageContent;
155
+ tool_call_id?: string;
156
+ tool_calls?: LLMToolCall[];
157
+ }
158
+
159
+ // ============================================================================
160
+ // Tool Types
161
+ // ============================================================================
162
+
163
+ export interface LLMToolCall {
164
+ id: string;
165
+ type: 'function';
166
+ function: {
167
+ name: string;
168
+ arguments: string;
169
+ };
170
+ /**
171
+ * Gemini 3.x thought signature — encrypted reasoning context.
172
+ * Must be echoed back exactly when sending conversation history
173
+ * during multi-turn function calling. Mandatory for Gemini 3,
174
+ * optional for Gemini 2.5, ignored by other providers.
175
+ */
176
+ thoughtSignature?: string;
177
+ }
178
+
179
+ export interface LLMFunction {
180
+ name: string;
181
+ description: string;
182
+ parameters: {
183
+ type: 'object';
184
+ properties?: Record<string, unknown>;
185
+ required?: string[];
186
+ };
187
+ }
188
+
189
+ export interface LLMToolDefinition {
190
+ type: 'function';
191
+ function: LLMFunction;
192
+ }
193
+
194
+ export interface ToolExecutionResult {
195
+ tool_call_id: string;
196
+ output: unknown;
197
+ error?: string;
198
+ duration?: number;
199
+ }
200
+
201
+ export type ToolHandler = (args: unknown) => Promise<unknown> | unknown;
202
+
203
+ export interface ToolRegistryEntry {
204
+ definition: LLMFunction;
205
+ handler: ToolHandler;
206
+ }
207
+
208
+ export interface ToolRegistry {
209
+ [toolName: string]: ToolRegistryEntry;
210
+ }
211
+
212
+ // ============================================================================
213
+ // Chat Options (per-call overrides)
214
+ // ============================================================================
215
+
216
+ /**
217
+ * Response format for structured output.
218
+ *
219
+ * For json_schema mode, use: { type: 'json_schema', json_schema: { name, schema, strict } }
220
+ * For json_object mode (legacy), use: { type: 'json_object' }
221
+ */
222
+ export interface ResponseFormat {
223
+ /** Response format type */
224
+ type: 'json_object' | 'json_schema';
225
+ /** JSON Schema definition (required for json_schema type) */
226
+ json_schema?: {
227
+ /** Name of the schema (for LLM guidance) */
228
+ name: string;
229
+ /** Schema description (optional, for LLM guidance) */
230
+ description?: string;
231
+ /** The JSON Schema */
232
+ schema: Record<string, unknown>;
233
+ /** Enable strict mode (required for reliable structured output) */
234
+ strict?: boolean;
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Output options for structured output in chat responses.
240
+ *
241
+ * When provided, the response will include a `structured` property with
242
+ * the validated, typed result. This is the recommended way to request
243
+ * structured output via the chat() method.
244
+ *
245
+ * @example
246
+ * ```typescript
247
+ * const UserSchema = z.object({
248
+ * name: z.string(),
249
+ * age: z.number(),
250
+ * });
251
+ *
252
+ * const response = await model.chat(messages, {
253
+ * output: { schema: UserSchema },
254
+ * });
255
+ *
256
+ * // response.structured is typed as { name: string, age: number }
257
+ * console.log(response.structured?.name);
258
+ * ```
259
+ */
260
+ export interface OutputOptions<T = unknown> {
261
+ /**
262
+ * Schema configuration for structured output.
263
+ * Use `fromZod()` from `universal-llm-client/zod` to create from a Zod schema,
264
+ * or provide a raw SchemaConfig with jsonSchema + optional validate function.
265
+ */
266
+ schema?: import('./structured-output.js').SchemaConfig<T>;
267
+
268
+ /**
269
+ * Raw JSON Schema for structured output.
270
+ * Alternative to `schema` when you have a pre-defined schema.
271
+ */
272
+ jsonSchema?: import('./structured-output.js').JSONSchema;
273
+
274
+ /**
275
+ * Optional name for the schema.
276
+ * Used by providers like OpenAI for better LLM guidance.
277
+ */
278
+ name?: string;
279
+
280
+ /**
281
+ * Optional description for the schema.
282
+ * Used by providers like OpenAI for better LLM guidance.
283
+ */
284
+ description?: string;
285
+
286
+ /**
287
+ * Whether to use strict mode for schema validation (OpenAI only).
288
+ * When true, OpenAI enforces the schema exactly (no additional properties,
289
+ * limited schema subset). Defaults to `true`.
290
+ */
291
+ strict?: boolean;
292
+ }
293
+
294
+ export interface ChatOptions {
295
+ /** Override temperature */
296
+ temperature?: number;
297
+ /** Max tokens to generate */
298
+ maxTokens?: number;
299
+ /** Tool definitions (auto-populated from registry if not set) */
300
+ tools?: LLMToolDefinition[];
301
+ /** Tool choice mode */
302
+ toolChoice?: 'none' | 'auto' | 'required';
303
+ /** Additional provider-specific parameters */
304
+ parameters?: Record<string, unknown>;
305
+ /** Abort signal for cancellation (forwarded to HTTP layer) */
306
+ signal?: AbortSignal;
307
+ /** Enable/disable tool execution for chatWithTools */
308
+ executeTools?: boolean;
309
+ /** Enable prompt caching (Provider specific feature, opt-in for Anthropic) */
310
+ enablePromptCaching?: boolean;
311
+ /** Maximum tool execution rounds (default: 10) */
312
+ maxIterations?: number;
313
+ /**
314
+ * Stream decoder selection. Accepts:
315
+ * - A built-in type name: 'passthrough' | 'standard-chat' | 'interleaved-reasoning'
316
+ * - A custom type name registered via `registerDecoder()`
317
+ * - A pre-built `StreamDecoder` instance for full control
318
+ */
319
+ decoder?: import('./stream-decoder.js').DecoderType | string | import('./stream-decoder.js').StreamDecoder;
320
+
321
+ // ========================================================================
322
+ // Structured Output Options
323
+ // ========================================================================
324
+
325
+ /**
326
+ * Structured output options for chat responses.
327
+ * When provided, the response will include a `structured` property
328
+ * with the validated result.
329
+ *
330
+ * **Note**: `output` and `tools` cannot be used together.
331
+ * If both are provided, an error will be thrown.
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * const response = await model.chat(messages, {
336
+ * output: { schema: UserSchema },
337
+ * });
338
+ * console.log(response.structured);
339
+ * ```
340
+ */
341
+ output?: OutputOptions;
342
+
343
+ /**
344
+ * Schema configuration for structured output.
345
+ * When provided, the response is validated against this schema.
346
+ *
347
+ * @deprecated Use `output.schema` or `generateStructured()` instead.
348
+ */
349
+ schema?: import('./structured-output.js').SchemaConfig<unknown>;
350
+
351
+ /**
352
+ * Raw JSON Schema for structured output.
353
+ * Alternative to `schema` when you have a pre-defined schema.
354
+ *
355
+ * @deprecated Use `output.jsonSchema` or `generateStructured()` instead.
356
+ */
357
+ jsonSchema?: import('./structured-output.js').JSONSchema;
358
+
359
+ /**
360
+ * Name for the schema (optional, used for LLM guidance).
361
+ * Required by some providers (e.g., OpenAI strict mode).
362
+ *
363
+ * @deprecated Use `output.name` or `generateStructured()` instead.
364
+ */
365
+ schemaName?: string;
366
+
367
+ /**
368
+ * Description for the schema (optional, used for LLM guidance).
369
+ *
370
+ * @deprecated Use `output.description` or `generateStructured()` instead.
371
+ */
372
+ schemaDescription?: string;
373
+
374
+ /**
375
+ * Response format for structured output (legacy json_object mode).
376
+ * For new code, prefer `output` or `generateStructured()`.
377
+ *
378
+ * Use { type: 'json_object' } for legacy JSON mode without schema validation.
379
+ */
380
+ responseFormat?: ResponseFormat;
381
+
382
+ // ========================================================================
383
+ // Inference Tier Selection
384
+ // ========================================================================
385
+
386
+ /** Inference tier selection (provider-specific; Google supports 'flex' and 'priority').
387
+ * - 'flex': 50% cost reduction, best-effort, higher latency (background tasks)
388
+ * - 'priority': Premium pricing, lowest latency, highest reliability (interactive)
389
+ * - 'standard': Default behavior (omitted from request) */
390
+ serviceTier?: 'flex' | 'priority' | 'standard';
391
+ }
392
+
393
+ // ============================================================================
394
+ // Token Usage
395
+ // ============================================================================
396
+
397
+ export interface TokenUsageInfo {
398
+ inputTokens: number;
399
+ outputTokens: number;
400
+ totalTokens: number;
401
+ cachedTokens?: number;
402
+ }
403
+
404
+ // ============================================================================
405
+ // Response Types
406
+ // ============================================================================
407
+
408
+ export interface LLMChatResponse<T = unknown> {
409
+ message: LLMChatMessage;
410
+ /** Reasoning/thinking content from the model (if supported) */
411
+ reasoning?: string;
412
+ /** Token usage info */
413
+ usage?: TokenUsageInfo;
414
+ /** Tool execution trace (populated by chatWithTools) */
415
+ toolExecutions?: ToolExecutionResult[];
416
+ /** Which provider served this response */
417
+ provider?: string;
418
+ /**
419
+ * Validated structured output when `output` parameter is provided to chat().
420
+ * This is the same type as inferred from the schema provided in `output.schema`.
421
+ *
422
+ * Undefined when:
423
+ * - No `output` parameter was provided
424
+ * - Structured output validation failed (throws StructuredOutputError instead)
425
+ *
426
+ * @example
427
+ * ```typescript
428
+ * const response = await model.chat(messages, {
429
+ * output: { schema: UserSchema },
430
+ * });
431
+ * if (response.structured) {
432
+ * console.log(response.structured.name); // Fully typed!
433
+ * }
434
+ * ```
435
+ */
436
+ structured?: T;
437
+ /** Which inference tier actually served this response (from provider response headers, e.g. x-gemini-service-tier) */
438
+ serviceTier?: 'flex' | 'priority' | 'standard';
439
+ }
440
+
441
+ // ============================================================================
442
+ // Provider Response Types (internal)
443
+ // ============================================================================
444
+
445
+ export interface OllamaResponse {
446
+ model: string;
447
+ created_at: string;
448
+ message: {
449
+ role: string;
450
+ content: string;
451
+ thinking?: string;
452
+ tool_calls?: LLMToolCall[];
453
+ };
454
+ done: boolean;
455
+ prompt_eval_count?: number;
456
+ eval_count?: number;
457
+ prompt_eval_duration?: number;
458
+ eval_duration?: number;
459
+ }
460
+
461
+ export interface OpenAIResponse {
462
+ id: string;
463
+ object: string;
464
+ created: number;
465
+ model: string;
466
+ choices: Array<{
467
+ index: number;
468
+ message: {
469
+ role: string;
470
+ content: string | null;
471
+ tool_calls?: LLMToolCall[];
472
+ };
473
+ finish_reason: string;
474
+ }>;
475
+ usage?: {
476
+ prompt_tokens: number;
477
+ completion_tokens: number;
478
+ total_tokens: number;
479
+ prompt_tokens_details?: {
480
+ cached_tokens?: number;
481
+ audio_tokens?: number;
482
+ };
483
+ };
484
+ }
485
+
486
+ export interface OllamaModelInfo {
487
+ name: string;
488
+ size: number;
489
+ digest: string;
490
+ details: {
491
+ format: string;
492
+ family: string;
493
+ families: string[];
494
+ parameter_size: string;
495
+ quantization_level: string;
496
+ };
497
+ modified_at: string;
498
+ }
499
+
500
+ export interface OpenAIModelInfo {
501
+ id: string;
502
+ object: string;
503
+ created: number;
504
+ owned_by: string;
505
+ }
506
+
507
+ // ============================================================================
508
+ // Google API Types
509
+ // ============================================================================
510
+
511
+ export interface GooglePart {
512
+ text?: string;
513
+ functionCall?: {
514
+ name: string;
515
+ args: Record<string, unknown>;
516
+ };
517
+ functionResponse?: {
518
+ name: string;
519
+ response: Record<string, unknown>;
520
+ };
521
+ inlineData?: {
522
+ mimeType: string;
523
+ data: string;
524
+ };
525
+ /** Gemini 3.x thought signature — must be echoed back on functionCall parts */
526
+ thoughtSignature?: string;
527
+ }
528
+
529
+ export interface GoogleContent {
530
+ role: 'user' | 'model' | 'function';
531
+ parts: GooglePart[];
532
+ }
533
+
534
+ export interface GoogleGenerationConfig {
535
+ responseMimeType?: string;
536
+ temperature?: number;
537
+ maxOutputTokens?: number;
538
+ topK?: number;
539
+ topP?: number;
540
+ thinkingConfig?: {
541
+ thinkingBudget?: number;
542
+ };
543
+ }
544
+
545
+ export interface GoogleFunctionDeclaration {
546
+ name: string;
547
+ description: string;
548
+ parameters: {
549
+ type: 'object';
550
+ properties: Record<string, unknown>;
551
+ required?: string[];
552
+ };
553
+ }
554
+
555
+ export interface GoogleToolConfig {
556
+ functionCallingConfig?: {
557
+ mode: 'AUTO' | 'ANY' | 'NONE';
558
+ allowedFunctionNames?: string[];
559
+ };
560
+ }
561
+
562
+ export interface GoogleRequest {
563
+ contents: GoogleContent[];
564
+ generationConfig?: GoogleGenerationConfig;
565
+ systemInstruction?: { parts: Array<{ text: string }> };
566
+ tools?: Array<{
567
+ functionDeclarations: GoogleFunctionDeclaration[];
568
+ }>;
569
+ toolConfig?: GoogleToolConfig;
570
+ /** Inference tier: FLEX (50% off, best-effort) or PRIORITY (premium, highest reliability) */
571
+ service_tier?: 'FLEX' | 'PRIORITY' | 'STANDARD';
572
+ }
573
+
574
+ export interface GoogleCandidate {
575
+ content: {
576
+ parts: GooglePart[];
577
+ role: string;
578
+ };
579
+ finishReason?: string;
580
+ index: number;
581
+ }
582
+
583
+ export interface GoogleResponse {
584
+ candidates: GoogleCandidate[];
585
+ usageMetadata?: {
586
+ promptTokenCount: number;
587
+ candidatesTokenCount: number;
588
+ totalTokenCount: number;
589
+ cachedContentTokenCount?: number;
590
+ };
591
+ }
592
+
593
+ // ============================================================================
594
+ // Helper Functions
595
+ // ============================================================================
596
+
597
+ /** Create a text content part */
598
+ export function textContent(text: string): LLMTextContent {
599
+ return { type: 'text', text };
600
+ }
601
+
602
+ /** Create an image content part from base64 data or URL */
603
+ export function imageContent(
604
+ base64DataOrUrl: string,
605
+ mimeType: string = 'image/jpeg',
606
+ detail?: 'auto' | 'low' | 'high',
607
+ ): LLMImageContent {
608
+ const url = base64DataOrUrl.startsWith('data:') || base64DataOrUrl.startsWith('http')
609
+ ? base64DataOrUrl
610
+ : `data:${mimeType};base64,${base64DataOrUrl}`;
611
+ return {
612
+ type: 'image_url',
613
+ image_url: { url, detail },
614
+ };
615
+ }
616
+
617
+ /** Create a multimodal user message with text and images */
618
+ export function multimodalMessage(
619
+ text: string,
620
+ images: string[],
621
+ mimeType: string = 'image/jpeg',
622
+ ): LLMChatMessage {
623
+ const content: LLMContentPart[] = [
624
+ textContent(text),
625
+ ...images.map(img => imageContent(img, mimeType)),
626
+ ];
627
+ return { role: 'user', content };
628
+ }
629
+
630
+ /** Extract text content from a message content value */
631
+ export function extractTextContent(content: LLMMessageContent): string {
632
+ if (typeof content === 'string') return content;
633
+ return content
634
+ .filter((part): part is LLMTextContent => part.type === 'text')
635
+ .map(part => part.text)
636
+ .join('');
637
+ }
638
+
639
+ /** Check if message content contains images */
640
+ export function hasImages(content: LLMMessageContent): boolean {
641
+ if (typeof content === 'string') return false;
642
+ return content.some(part => part.type === 'image_url');
643
+ }
644
+
645
+ /** Create an audio content part from raw base64 data */
646
+ export function audioContent(base64Data: string, mimeType: string): LLMAudioContent {
647
+ return {
648
+ type: 'audio',
649
+ audio: { data: base64Data, mimeType },
650
+ };
651
+ }
652
+
653
+ /** Check if message content contains audio */
654
+ export function hasAudio(content: LLMMessageContent): boolean {
655
+ if (typeof content === 'string') return false;
656
+ return content.some(part => part.type === 'audio');
657
+ }