@xiaozhiclaw/provider-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/adapters/aliyun-oss-file-upload-adapter.d.ts +44 -0
  2. package/dist/adapters/aliyun-oss-file-upload-adapter.js +96 -0
  3. package/dist/adapters/gemini-file-upload-adapter.d.ts +26 -0
  4. package/dist/adapters/gemini-file-upload-adapter.js +92 -0
  5. package/dist/adapters/hub-oss-file-upload-adapter.d.ts +29 -0
  6. package/dist/adapters/hub-oss-file-upload-adapter.js +53 -0
  7. package/dist/adapters/index.d.ts +10 -0
  8. package/dist/adapters/index.js +10 -0
  9. package/dist/adapters/openai-file-upload-adapter.d.ts +38 -0
  10. package/dist/adapters/openai-file-upload-adapter.js +56 -0
  11. package/dist/adapters/volcengine-file-upload-adapter.d.ts +24 -0
  12. package/dist/adapters/volcengine-file-upload-adapter.js +45 -0
  13. package/dist/builtin-providers.d.ts +8 -0
  14. package/dist/builtin-providers.js +2237 -0
  15. package/dist/constants.d.ts +1 -0
  16. package/dist/constants.js +1 -0
  17. package/dist/credentials.d.ts +1 -0
  18. package/dist/credentials.js +8 -0
  19. package/dist/debug-transport.d.ts +12 -0
  20. package/dist/debug-transport.js +99 -0
  21. package/dist/errors.d.ts +11 -0
  22. package/dist/errors.js +12 -0
  23. package/dist/events.d.ts +48 -0
  24. package/dist/events.js +1 -0
  25. package/dist/file-upload-service.d.ts +68 -0
  26. package/dist/file-upload-service.js +110 -0
  27. package/dist/gemini-schema-utils.d.ts +17 -0
  28. package/dist/gemini-schema-utils.js +76 -0
  29. package/dist/index.d.ts +37 -0
  30. package/dist/index.js +33 -0
  31. package/dist/llm-client.d.ts +43 -0
  32. package/dist/llm-client.js +217 -0
  33. package/dist/media-client.d.ts +42 -0
  34. package/dist/media-client.js +174 -0
  35. package/dist/media-transport.d.ts +176 -0
  36. package/dist/media-transport.js +16 -0
  37. package/dist/media.d.ts +2 -0
  38. package/dist/media.js +1 -0
  39. package/dist/model-detection.d.ts +22 -0
  40. package/dist/model-detection.js +28 -0
  41. package/dist/paths.d.ts +2 -0
  42. package/dist/paths.js +11 -0
  43. package/dist/provider-def.d.ts +220 -0
  44. package/dist/provider-def.js +9 -0
  45. package/dist/provider-registry.d.ts +51 -0
  46. package/dist/provider-registry.js +130 -0
  47. package/dist/provider-tool-api.d.ts +44 -0
  48. package/dist/provider-tool-api.js +9 -0
  49. package/dist/provider-variant-resolver.d.ts +35 -0
  50. package/dist/provider-variant-resolver.js +174 -0
  51. package/dist/retry.d.ts +37 -0
  52. package/dist/retry.js +71 -0
  53. package/dist/transport.d.ts +281 -0
  54. package/dist/transport.js +27 -0
  55. package/dist/transports/anthropic-messages.d.ts +65 -0
  56. package/dist/transports/anthropic-messages.js +1004 -0
  57. package/dist/transports/gemini-cache-api.d.ts +86 -0
  58. package/dist/transports/gemini-cache-api.js +141 -0
  59. package/dist/transports/gemini-file-api.d.ts +90 -0
  60. package/dist/transports/gemini-file-api.js +164 -0
  61. package/dist/transports/gemini-generatecontent.d.ts +56 -0
  62. package/dist/transports/gemini-generatecontent.js +688 -0
  63. package/dist/transports/gemini-lyria-realtime.d.ts +117 -0
  64. package/dist/transports/gemini-lyria-realtime.js +295 -0
  65. package/dist/transports/gemini-media.d.ts +53 -0
  66. package/dist/transports/gemini-media.js +383 -0
  67. package/dist/transports/media-resolve.d.ts +50 -0
  68. package/dist/transports/media-resolve.js +91 -0
  69. package/dist/transports/minimax-media.d.ts +56 -0
  70. package/dist/transports/minimax-media.js +433 -0
  71. package/dist/transports/openai-chat.d.ts +81 -0
  72. package/dist/transports/openai-chat.js +782 -0
  73. package/dist/transports/openai-media.d.ts +24 -0
  74. package/dist/transports/openai-media.js +118 -0
  75. package/dist/transports/openai-responses.d.ts +63 -0
  76. package/dist/transports/openai-responses.js +778 -0
  77. package/dist/transports/qwen-media.d.ts +59 -0
  78. package/dist/transports/qwen-media.js +411 -0
  79. package/dist/transports/realtime-transport.d.ts +183 -0
  80. package/dist/transports/realtime-transport.js +332 -0
  81. package/dist/transports/volcengine-grounding.d.ts +58 -0
  82. package/dist/transports/volcengine-grounding.js +69 -0
  83. package/dist/transports/volcengine-media.d.ts +94 -0
  84. package/dist/transports/volcengine-media.js +801 -0
  85. package/dist/transports/volcengine-responses.d.ts +64 -0
  86. package/dist/transports/volcengine-responses.js +797 -0
  87. package/dist/transports/zhipu-media.d.ts +82 -0
  88. package/dist/transports/zhipu-media.js +522 -0
  89. package/dist/transports/zhipu-tool-api.d.ts +35 -0
  90. package/dist/transports/zhipu-tool-api.js +126 -0
  91. package/dist/wire-types.d.ts +51 -0
  92. package/dist/wire-types.js +1 -0
  93. package/package.json +33 -0
@@ -0,0 +1 @@
1
+ export declare const MEDIA_MAX_UPLOAD_SIZE: number;
@@ -0,0 +1 @@
1
+ export const MEDIA_MAX_UPLOAD_SIZE = 50 * 1024 * 1024;
@@ -0,0 +1 @@
1
+ export declare function resolveApiKey(envVarNames: string[], env?: NodeJS.ProcessEnv): string | undefined;
@@ -0,0 +1,8 @@
1
+ export function resolveApiKey(envVarNames, env = process.env) {
2
+ for (const name of envVarNames) {
3
+ const value = env[name];
4
+ if (value)
5
+ return value;
6
+ }
7
+ return undefined;
8
+ }
@@ -0,0 +1,12 @@
1
+ import type { LLMTransport } from "./transport.js";
2
+ /**
3
+ * Check whether debug transport should be enabled.
4
+ */
5
+ export declare function isDebugTransportEnabled(): boolean;
6
+ /**
7
+ * Create a DebugTransport decorator that wraps a base LLMTransport,
8
+ * logging request metadata + timing + usage to JSONL.
9
+ *
10
+ * CC parity: createDumpPromptsFetch 鈥?but vendor-agnostic via transport abstraction.
11
+ */
12
+ export declare function createDebugTransport(base: LLMTransport, sessionId: string): LLMTransport;
@@ -0,0 +1,99 @@
1
+ // ============================================================
2
+ // DebugTransport 鈥?Diagnostic decorator for LLMTransport
3
+ //
4
+ // CC parity: dumpPrompts.ts (request JSONL logging)
5
+ // Wraps any LLMTransport, logging request metadata + timing + usage
6
+ // to a JSONL file at ~/.qlogicagent/debug-logs/{sessionId}.jsonl
7
+ //
8
+ // Enable via: QLOGICAGENT_DEBUG=1 (env var)
9
+ // ============================================================
10
+ import { join } from "node:path";
11
+ import { mkdirSync, appendFileSync } from "node:fs";
12
+ import { getUserDebugLogsDir } from "./paths.js";
13
+ // 鈹€鈹€ Configuration 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
14
+ const DEBUG_LOG_DIR = getUserDebugLogsDir();
15
+ /**
16
+ * Check whether debug transport should be enabled.
17
+ */
18
+ export function isDebugTransportEnabled() {
19
+ return process.env.QLOGICAGENT_DEBUG === "1";
20
+ }
21
+ /**
22
+ * Create a DebugTransport decorator that wraps a base LLMTransport,
23
+ * logging request metadata + timing + usage to JSONL.
24
+ *
25
+ * CC parity: createDumpPromptsFetch 鈥?but vendor-agnostic via transport abstraction.
26
+ */
27
+ export function createDebugTransport(base, sessionId) {
28
+ const logPath = join(DEBUG_LOG_DIR, `${sessionId}.jsonl`);
29
+ let dirEnsured = false;
30
+ function ensureDir() {
31
+ if (!dirEnsured) {
32
+ mkdirSync(DEBUG_LOG_DIR, { recursive: true });
33
+ dirEnsured = true;
34
+ }
35
+ }
36
+ function writeEntry(entry) {
37
+ try {
38
+ ensureDir();
39
+ appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf8");
40
+ }
41
+ catch {
42
+ // Diagnostic logging is best-effort 鈥?never crash the agent
43
+ }
44
+ }
45
+ return {
46
+ async *stream(request, apiKey, signal) {
47
+ const startMs = Date.now();
48
+ // Log request metadata (strip apiKey for safety)
49
+ writeEntry({
50
+ type: "request",
51
+ timestamp: new Date(startMs).toISOString(),
52
+ model: request.model,
53
+ messageCount: request.messages.length,
54
+ toolCount: request.tools?.length ?? 0,
55
+ toolChoice: request.toolChoice,
56
+ temperature: request.temperature,
57
+ maxTokens: request.maxTokens,
58
+ reasoning: request.reasoning,
59
+ });
60
+ let promptTokens = 0;
61
+ let completionTokens = 0;
62
+ let reasoningTokens = 0;
63
+ let finishReason = "";
64
+ let error;
65
+ try {
66
+ for await (const chunk of base.stream(request, apiKey, signal)) {
67
+ // Capture usage/done metadata
68
+ if (chunk.type === "usage") {
69
+ promptTokens = chunk.promptTokens;
70
+ completionTokens = chunk.completionTokens;
71
+ reasoningTokens = chunk.reasoningTokens ?? 0;
72
+ }
73
+ else if (chunk.type === "done") {
74
+ finishReason = chunk.finishReason;
75
+ }
76
+ yield chunk;
77
+ }
78
+ }
79
+ catch (err) {
80
+ error = err instanceof Error ? err.message : String(err);
81
+ throw err;
82
+ }
83
+ finally {
84
+ const elapsedMs = Date.now() - startMs;
85
+ writeEntry({
86
+ type: "response",
87
+ timestamp: new Date().toISOString(),
88
+ model: request.model,
89
+ elapsedMs,
90
+ promptTokens,
91
+ completionTokens,
92
+ reasoningTokens,
93
+ finishReason,
94
+ ...(error ? { error } : {}),
95
+ });
96
+ }
97
+ },
98
+ };
99
+ }
@@ -0,0 +1,11 @@
1
+ export declare class ProviderCoreError extends Error {
2
+ readonly status?: number;
3
+ readonly code?: string;
4
+ readonly provider?: string;
5
+ constructor(message: string, opts?: {
6
+ status?: number;
7
+ code?: string;
8
+ provider?: string;
9
+ cause?: unknown;
10
+ });
11
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,12 @@
1
+ export class ProviderCoreError extends Error {
2
+ status;
3
+ code;
4
+ provider;
5
+ constructor(message, opts) {
6
+ super(message, { cause: opts?.cause });
7
+ this.name = "ProviderCoreError";
8
+ this.status = opts?.status;
9
+ this.code = opts?.code;
10
+ this.provider = opts?.provider;
11
+ }
12
+ }
@@ -0,0 +1,48 @@
1
+ export interface ProviderUsage {
2
+ promptTokens?: number;
3
+ completionTokens?: number;
4
+ reasoningTokens?: number;
5
+ cacheReadTokens?: number;
6
+ cacheCreationTokens?: number;
7
+ }
8
+ export interface ProviderErrorPayload {
9
+ message: string;
10
+ code?: string;
11
+ status?: number;
12
+ provider?: string;
13
+ }
14
+ export type ProviderEvent = {
15
+ type: "response.created";
16
+ id: string;
17
+ model: string;
18
+ provider: string;
19
+ credentialId?: string;
20
+ credentialSource?: string;
21
+ credentialEgressMode?: string;
22
+ credentialEgressRegion?: string;
23
+ } | {
24
+ type: "content.delta";
25
+ index: number;
26
+ text: string;
27
+ } | {
28
+ type: "reasoning.delta";
29
+ text: string;
30
+ } | {
31
+ type: "tool.call.delta";
32
+ id: string;
33
+ name?: string;
34
+ argumentsDelta?: string;
35
+ } | {
36
+ type: "usage.delta";
37
+ usage: ProviderUsage;
38
+ } | {
39
+ type: "annotation";
40
+ annotation: Record<string, unknown>;
41
+ } | {
42
+ type: "response.completed";
43
+ usage?: ProviderUsage;
44
+ finishReason?: string;
45
+ } | {
46
+ type: "error";
47
+ error: ProviderErrorPayload;
48
+ };
package/dist/events.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * FileUploadService 鈥?Unified file upload abstraction for all LLM providers.
3
+ *
4
+ * Replaces the base64 approach in media-resolve.ts. When a local URL is
5
+ * encountered, this service uploads the file to the current provider's
6
+ * File API and returns a public URL or file_id.
7
+ *
8
+ * Supported providers:
9
+ * - OpenAI-compatible (OpenAI, Kimi, Minimax, Qwen, GLM) 鈥?POST /v1/files
10
+ * - Volcengine 鈥?POST /v3/files
11
+ * - Google Gemini 鈥?Resumable upload protocol
12
+ *
13
+ * Architecture:
14
+ * Transport layer calls resolveLocalMedia() before sending messages.
15
+ * resolveLocalMedia() fetches local content and uploads to provider,
16
+ * returning a public URL the LLM can access.
17
+ */
18
+ export interface FileUploadResult {
19
+ /** Public URL accessible by the LLM provider. */
20
+ url: string;
21
+ /** Provider-specific file identifier (for reference/deletion). */
22
+ fileId: string;
23
+ /** Original filename. */
24
+ filename: string;
25
+ /** File size in bytes. */
26
+ bytes: number;
27
+ /** MIME type of the uploaded file. */
28
+ mimeType: string;
29
+ /** Provider that stored the file. */
30
+ provider: string;
31
+ }
32
+ /**
33
+ * Provider-specific upload adapter.
34
+ * Each transport implements this to upload files to its own File API.
35
+ */
36
+ export interface FileUploadAdapter {
37
+ /**
38
+ * Upload a file buffer and return a publicly accessible URL.
39
+ * @param buffer - File content
40
+ * @param filename - Original filename (used for MIME detection)
41
+ * @param mimeType - MIME type of the file
42
+ * @param apiKey - Provider API key
43
+ * @param signal - Abort signal
44
+ */
45
+ uploadFile(buffer: Buffer, filename: string, mimeType: string, apiKey: string, signal?: AbortSignal): Promise<FileUploadResult>;
46
+ }
47
+ /** Guess MIME type from filename extension. */
48
+ export declare function guessMimeType(filename: string): string;
49
+ /** Check if a URL points to a local/private address that cloud APIs cannot reach. */
50
+ export declare function isLocalUrl(url: string): boolean;
51
+ /**
52
+ * Resolve a local URL by uploading its content to the provider's File API.
53
+ * Public URLs are returned as-is (the LLM API can fetch them directly).
54
+ *
55
+ * @param url - The URL to resolve (may be local or public)
56
+ * @param adapter - Provider-specific upload adapter
57
+ * @param apiKey - API key for the upload
58
+ * @param signal - Optional abort signal
59
+ * @returns Public URL accessible by the LLM
60
+ */
61
+ export declare function resolveLocalMedia(url: string, adapter: FileUploadAdapter, apiKey: string, signal?: AbortSignal): Promise<string>;
62
+ /**
63
+ * Batch-resolve multiple URLs concurrently. Returns array in same order.
64
+ * Local URLs are uploaded; public URLs pass through unchanged.
65
+ * Supports concurrent uploads (up to `concurrency` limit, default 3).
66
+ * Failures throw (caller should handle).
67
+ */
68
+ export declare function resolveLocalMediaBatch(urls: string[], adapter: FileUploadAdapter, apiKey: string, signal?: AbortSignal, concurrency?: number): Promise<string[]>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * FileUploadService 鈥?Unified file upload abstraction for all LLM providers.
3
+ *
4
+ * Replaces the base64 approach in media-resolve.ts. When a local URL is
5
+ * encountered, this service uploads the file to the current provider's
6
+ * File API and returns a public URL or file_id.
7
+ *
8
+ * Supported providers:
9
+ * - OpenAI-compatible (OpenAI, Kimi, Minimax, Qwen, GLM) 鈥?POST /v1/files
10
+ * - Volcengine 鈥?POST /v3/files
11
+ * - Google Gemini 鈥?Resumable upload protocol
12
+ *
13
+ * Architecture:
14
+ * Transport layer calls resolveLocalMedia() before sending messages.
15
+ * resolveLocalMedia() fetches local content and uploads to provider,
16
+ * returning a public URL the LLM can access.
17
+ */
18
+ // 鈹€鈹€ MIME Detection 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
19
+ const EXTENSION_MIME_MAP = {
20
+ ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
21
+ ".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml",
22
+ ".bmp": "image/bmp", ".avif": "image/avif", ".heic": "image/heic",
23
+ ".mp4": "video/mp4", ".webm": "video/webm", ".mov": "video/quicktime",
24
+ ".avi": "video/x-msvideo", ".mkv": "video/x-matroska",
25
+ ".mp3": "audio/mpeg", ".wav": "audio/wav", ".ogg": "audio/ogg",
26
+ ".aac": "audio/aac", ".flac": "audio/flac", ".m4a": "audio/mp4",
27
+ ".opus": "audio/opus",
28
+ ".pdf": "application/pdf",
29
+ ".doc": "application/msword",
30
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
31
+ ".xls": "application/vnd.ms-excel",
32
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
33
+ ".ppt": "application/vnd.ms-powerpoint",
34
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
35
+ ".txt": "text/plain", ".csv": "text/csv", ".json": "application/json",
36
+ };
37
+ /** Guess MIME type from filename extension. */
38
+ export function guessMimeType(filename) {
39
+ const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
40
+ return EXTENSION_MIME_MAP[ext] || "application/octet-stream";
41
+ }
42
+ // 鈹€鈹€ Local URL Detection 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
43
+ const LOCAL_URL_RE = /^https?:\/\/(127\.0\.0\.1|localhost|0\.0\.0\.0|\[::1\])(:\d+)?/i;
44
+ /** Check if a URL points to a local/private address that cloud APIs cannot reach. */
45
+ export function isLocalUrl(url) {
46
+ return LOCAL_URL_RE.test(url);
47
+ }
48
+ // 鈹€鈹€ Core Resolution 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
49
+ /**
50
+ * Resolve a local URL by uploading its content to the provider's File API.
51
+ * Public URLs are returned as-is (the LLM API can fetch them directly).
52
+ *
53
+ * @param url - The URL to resolve (may be local or public)
54
+ * @param adapter - Provider-specific upload adapter
55
+ * @param apiKey - API key for the upload
56
+ * @param signal - Optional abort signal
57
+ * @returns Public URL accessible by the LLM
58
+ */
59
+ export async function resolveLocalMedia(url, adapter, apiKey, signal) {
60
+ // Already a data URL 鈥?should not happen in new architecture, but handle gracefully
61
+ if (url.startsWith("data:"))
62
+ return url;
63
+ // Public URL 鈥?LLM API can fetch directly, no upload needed
64
+ if (!isLocalUrl(url))
65
+ return url;
66
+ // Local URL 鈥?fetch content and upload to provider
67
+ const res = await fetch(url, { signal });
68
+ if (!res.ok) {
69
+ throw new Error(`Failed to fetch local media ${url}: ${res.status}`);
70
+ }
71
+ const buffer = Buffer.from(await res.arrayBuffer());
72
+ const filename = extractFilename(url);
73
+ const contentType = res.headers.get("content-type") || guessMimeType(filename);
74
+ const result = await adapter.uploadFile(buffer, filename, contentType, apiKey, signal);
75
+ return result.url;
76
+ }
77
+ /**
78
+ * Batch-resolve multiple URLs concurrently. Returns array in same order.
79
+ * Local URLs are uploaded; public URLs pass through unchanged.
80
+ * Supports concurrent uploads (up to `concurrency` limit, default 3).
81
+ * Failures throw (caller should handle).
82
+ */
83
+ export async function resolveLocalMediaBatch(urls, adapter, apiKey, signal, concurrency = 3) {
84
+ if (urls.length <= concurrency) {
85
+ // All fit within concurrency limit 鈥?resolve all at once
86
+ return Promise.all(urls.map((url) => resolveLocalMedia(url, adapter, apiKey, signal)));
87
+ }
88
+ // Concurrency-limited parallel uploads
89
+ const results = new Array(urls.length);
90
+ let nextIndex = 0;
91
+ const worker = async () => {
92
+ while (nextIndex < urls.length) {
93
+ const i = nextIndex++;
94
+ results[i] = await resolveLocalMedia(urls[i], adapter, apiKey, signal);
95
+ }
96
+ };
97
+ await Promise.all(Array.from({ length: concurrency }, () => worker()));
98
+ return results;
99
+ }
100
+ // 鈹€鈹€ Helpers 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
101
+ function extractFilename(url) {
102
+ try {
103
+ const pathname = new URL(url).pathname;
104
+ const last = pathname.split("/").pop();
105
+ return last && last.includes(".") ? last : "upload";
106
+ }
107
+ catch {
108
+ return "upload";
109
+ }
110
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Gemini JSON Schema cleaning utilities.
3
+ *
4
+ * Gemini's generateContent API rejects several standard JSON Schema keywords.
5
+ * This module provides a shared recursive cleaner used by:
6
+ * - GeminiGenerateContentTransport (tool declarations in request body)
7
+ * - tool-schema.ts (provider-aware schema normalization at orchestration level)
8
+ *
9
+ * Keeping a single source of truth avoids drift between the two consumers.
10
+ */
11
+ /** Keywords that Gemini's generateContent API does not accept in tool schemas. */
12
+ export declare const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS: Set<string>;
13
+ /**
14
+ * Recursively strip unsupported JSON Schema keywords from a schema object.
15
+ * Safe to call multiple times (idempotent).
16
+ */
17
+ export declare function cleanSchemaForGemini(schema: unknown): unknown;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Gemini JSON Schema cleaning utilities.
3
+ *
4
+ * Gemini's generateContent API rejects several standard JSON Schema keywords.
5
+ * This module provides a shared recursive cleaner used by:
6
+ * - GeminiGenerateContentTransport (tool declarations in request body)
7
+ * - tool-schema.ts (provider-aware schema normalization at orchestration level)
8
+ *
9
+ * Keeping a single source of truth avoids drift between the two consumers.
10
+ */
11
+ /** Keywords that Gemini's generateContent API does not accept in tool schemas. */
12
+ export const GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS = new Set([
13
+ "patternProperties",
14
+ "additionalProperties",
15
+ "$schema",
16
+ "$id",
17
+ "$ref",
18
+ "$defs",
19
+ "definitions",
20
+ "examples",
21
+ "minLength",
22
+ "maxLength",
23
+ "minimum",
24
+ "maximum",
25
+ "multipleOf",
26
+ "pattern",
27
+ "format",
28
+ "minItems",
29
+ "maxItems",
30
+ "uniqueItems",
31
+ "minProperties",
32
+ "maxProperties",
33
+ "const",
34
+ ]);
35
+ /**
36
+ * Recursively strip unsupported JSON Schema keywords from a schema object.
37
+ * Safe to call multiple times (idempotent).
38
+ */
39
+ export function cleanSchemaForGemini(schema) {
40
+ if (!schema || typeof schema !== "object") {
41
+ return schema;
42
+ }
43
+ if (Array.isArray(schema)) {
44
+ return schema.map((item) => cleanSchemaForGemini(item));
45
+ }
46
+ const record = schema;
47
+ const cleaned = {};
48
+ for (const [key, value] of Object.entries(record)) {
49
+ if (GEMINI_UNSUPPORTED_SCHEMA_KEYWORDS.has(key))
50
+ continue;
51
+ if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
52
+ cleaned[key] = Object.fromEntries(Object.entries(value).map(([nestedKey, nestedValue]) => [nestedKey, cleanSchemaForGemini(nestedValue)]));
53
+ continue;
54
+ }
55
+ if (key === "items" && value && typeof value === "object") {
56
+ cleaned[key] = Array.isArray(value)
57
+ ? value.map((item) => cleanSchemaForGemini(item))
58
+ : cleanSchemaForGemini(value);
59
+ continue;
60
+ }
61
+ if ((key === "anyOf" || key === "oneOf" || key === "allOf") && Array.isArray(value)) {
62
+ cleaned[key] = value.map((item) => cleanSchemaForGemini(item));
63
+ continue;
64
+ }
65
+ if (value && typeof value === "object" && !Array.isArray(value)) {
66
+ cleaned[key] = cleanSchemaForGemini(value);
67
+ continue;
68
+ }
69
+ if (Array.isArray(value)) {
70
+ cleaned[key] = value.map((item) => cleanSchemaForGemini(item));
71
+ continue;
72
+ }
73
+ cleaned[key] = value;
74
+ }
75
+ return cleaned;
76
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * LLM Provider layer 鈥?public API surface.
3
+ *
4
+ * Provides: ProviderDef + LLMTransport + ProviderRegistry + LLMClient factory
5
+ */
6
+ export type { ProviderDef, ModelInfo, TransportType, AuthType, MediaCapability, MediaCapabilities, VideoCapabilities, ImageCapabilities, MusicCapabilities, TtsCapabilities, ThreeDCapabilities, SttCapabilities, EmbeddingCapabilities, VideoUnderstandingCapabilities, ImageUnderstandingCapabilities, VoiceCloneCapabilities, RerankCapabilities, DocumentParsingCapabilities, RealtimeAudioCapabilities, VideoOperation, ImageOperation, MusicOperation, TtsOperation, ThreeDOperation, ProviderVariantKind, ProviderBillingChannelKind, ProviderVariantCapability } from "./provider-def.js";
7
+ export type { LLMTransport, LLMRequest, LLMChunk, AccumulatedToolCall, } from "./transport.js";
8
+ export { accumulateToolCalls } from "./transport.js";
9
+ export type { ChatMessage, ChatMessageRole, ThinkingBlock, ToolCallMessage, ToolDefinition } from "./wire-types.js";
10
+ export type { ProviderEvent, ProviderErrorPayload, ProviderUsage } from "./events.js";
11
+ export { ProviderCoreError } from "./errors.js";
12
+ export { resolveApiKey } from "./credentials.js";
13
+ export type { MediaTransport, MediaRequest, MediaResult, MediaType } from "./media-transport.js";
14
+ export { isAsyncMediaTransport } from "./media-transport.js";
15
+ export { MediaClient, type MediaClientConfig, type ResolvedMediaModel } from "./media-client.js";
16
+ export type { ProviderToolAPI, WebSearchResult, ReaderResult, TokenizerResult, ModerationResult, ProviderToolCapability } from "./provider-tool-api.js";
17
+ export { ProviderRegistry } from "./provider-registry.js";
18
+ export { BUILTIN_PROVIDERS } from "./builtin-providers.js";
19
+ export { ProviderVariantResolver, type ProviderVariantResolverInput, type ProviderVariantResolution, type RequestedProviderProtocol } from "./provider-variant-resolver.js";
20
+ export type { LLMClientConfig, LLMClient } from "./llm-client.js";
21
+ export { createLLMClient } from "./llm-client.js";
22
+ export { OpenAIChatTransport } from "./transports/openai-chat.js";
23
+ export { AnthropicMessagesTransport } from "./transports/anthropic-messages.js";
24
+ export { GeminiGenerateContentTransport } from "./transports/gemini-generatecontent.js";
25
+ export { VolcengineMediaTransport } from "./transports/volcengine-media.js";
26
+ export { OpenAIMediaTransport } from "./transports/openai-media.js";
27
+ export { MiniMaxMediaTransport } from "./transports/minimax-media.js";
28
+ export { GeminiMediaTransport, type GeminiMediaConfig } from "./transports/gemini-media.js";
29
+ export { GeminiLyriaRealtimeSession, generateRealtimeMusic } from "./transports/gemini-lyria-realtime.js";
30
+ export type { WeightedPrompt, MusicGenerationConfig, MusicScale, MusicGenerationMode, LyriaRealtimeConfig, LyriaRealtimeSessionOptions, AudioChunk } from "./transports/gemini-lyria-realtime.js";
31
+ export { RealtimeTransport } from "./transports/realtime-transport.js";
32
+ export type { RealtimeConfig, RealtimeTool, RealtimeEvent, RealtimeUsage } from "./transports/realtime-transport.js";
33
+ export { GeminiCacheAPI } from "./transports/gemini-cache-api.js";
34
+ export type { GeminiCachedContent, GeminiCacheCreateOptions } from "./transports/gemini-cache-api.js";
35
+ export { GeminiFileAPI } from "./transports/gemini-file-api.js";
36
+ export type { GeminiFileInfo } from "./transports/gemini-file-api.js";
37
+ export { isDebugTransportEnabled, createDebugTransport } from "./debug-transport.js";
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * LLM Provider layer 鈥?public API surface.
3
+ *
4
+ * Provides: ProviderDef + LLMTransport + ProviderRegistry + LLMClient factory
5
+ */
6
+ export { accumulateToolCalls } from "./transport.js";
7
+ export { ProviderCoreError } from "./errors.js";
8
+ export { resolveApiKey } from "./credentials.js";
9
+ export { isAsyncMediaTransport } from "./media-transport.js";
10
+ export { MediaClient } from "./media-client.js";
11
+ // Registry
12
+ export { ProviderRegistry } from "./provider-registry.js";
13
+ export { BUILTIN_PROVIDERS } from "./builtin-providers.js";
14
+ export { ProviderVariantResolver } from "./provider-variant-resolver.js";
15
+ export { createLLMClient } from "./llm-client.js";
16
+ // Transports (chat)
17
+ export { OpenAIChatTransport } from "./transports/openai-chat.js";
18
+ export { AnthropicMessagesTransport } from "./transports/anthropic-messages.js";
19
+ export { GeminiGenerateContentTransport } from "./transports/gemini-generatecontent.js";
20
+ // Transports (media generation)
21
+ export { VolcengineMediaTransport } from "./transports/volcengine-media.js";
22
+ export { OpenAIMediaTransport } from "./transports/openai-media.js";
23
+ export { MiniMaxMediaTransport } from "./transports/minimax-media.js";
24
+ export { GeminiMediaTransport } from "./transports/gemini-media.js";
25
+ // Gemini Lyria RealTime (WebSocket streaming music)
26
+ export { GeminiLyriaRealtimeSession, generateRealtimeMusic } from "./transports/gemini-lyria-realtime.js";
27
+ // OpenAI Realtime (WebSocket bidirectional audio/voice)
28
+ export { RealtimeTransport } from "./transports/realtime-transport.js";
29
+ // Gemini utility APIs (Context Caching + File API)
30
+ export { GeminiCacheAPI } from "./transports/gemini-cache-api.js";
31
+ export { GeminiFileAPI } from "./transports/gemini-file-api.js";
32
+ // Debug
33
+ export { isDebugTransportEnabled, createDebugTransport } from "./debug-transport.js";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * LLM Client factory 鈥?resolves ProviderDef 鈫?creates LLMTransport instance.
3
+ *
4
+ * Replaces the old createAdminInferProxyClient() call chain.
5
+ * User API key + ProviderDef 鈫?direct provider connection.
6
+ */
7
+ import type { ProviderDef } from "./provider-def.js";
8
+ import type { LLMTransport } from "./transport.js";
9
+ import { ProviderRegistry } from "./provider-registry.js";
10
+ export interface LLMClientConfig {
11
+ /** Provider id, e.g. "deepseek" */
12
+ provider: string;
13
+ /** Model id, e.g. "deepseek-v4-flash" */
14
+ model: string;
15
+ /** User API key */
16
+ apiKey: string;
17
+ /** Optional base URL override */
18
+ baseUrl?: string;
19
+ }
20
+ export interface LLMClient {
21
+ transport: LLMTransport;
22
+ apiKey: string;
23
+ resolvedModel: string;
24
+ providerDef: ProviderDef;
25
+ }
26
+ /**
27
+ * Create an LLM client from config + registry.
28
+ *
29
+ * 1. Look up provider in registry
30
+ * 2. Apply baseUrl override if provided
31
+ * 3. Instantiate the correct transport
32
+ */
33
+ export declare function createLLMClient(config: LLMClientConfig, registry: ProviderRegistry): LLMClient;
34
+ /**
35
+ * Auto-detect provider from API key environment variables.
36
+ * Scans registry for providers whose env vars are set.
37
+ * Returns the first match.
38
+ */
39
+ export declare function autoDetectProvider(registry: ProviderRegistry): {
40
+ providerId: string;
41
+ apiKey: string;
42
+ defaultModel: string;
43
+ } | undefined;