universal-llm-client 4.5.0 → 4.5.1
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/CHANGELOG.md +12 -0
- package/README.md +2 -0
- package/dist/ai-model.d.ts +0 -1
- package/dist/ai-model.js +0 -1
- package/dist/auditor.d.ts +0 -1
- package/dist/auditor.js +0 -1
- package/dist/client.d.ts +0 -1
- package/dist/client.js +0 -1
- package/dist/gemma-channel.d.ts +0 -1
- package/dist/gemma-channel.js +0 -1
- package/dist/gemma-diffusion.d.ts +0 -1
- package/dist/gemma-diffusion.js +0 -1
- package/dist/http.d.ts +0 -1
- package/dist/http.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/interfaces.d.ts +0 -1
- package/dist/interfaces.js +0 -1
- package/dist/mcp.d.ts +0 -1
- package/dist/mcp.js +0 -1
- package/dist/providers/anthropic.d.ts +0 -1
- package/dist/providers/anthropic.js +0 -1
- package/dist/providers/google.d.ts +0 -1
- package/dist/providers/google.js +0 -1
- package/dist/providers/index.d.ts +0 -1
- package/dist/providers/index.js +0 -1
- package/dist/providers/ollama.d.ts +0 -1
- package/dist/providers/ollama.js +0 -1
- package/dist/providers/openai.d.ts +2 -1
- package/dist/providers/openai.js +303 -74
- package/dist/router.d.ts +0 -1
- package/dist/router.js +0 -1
- package/dist/stream-decoder.d.ts +0 -1
- package/dist/stream-decoder.js +0 -1
- package/dist/structured-output.d.ts +0 -1
- package/dist/structured-output.js +0 -1
- package/dist/thinking.d.ts +0 -1
- package/dist/thinking.js +0 -1
- package/dist/tools.d.ts +0 -1
- package/dist/tools.js +0 -1
- package/dist/zod-adapter.d.ts +0 -1
- package/dist/zod-adapter.js +0 -1
- package/package.json +1 -2
- package/dist/ai-model.d.ts.map +0 -1
- package/dist/ai-model.js.map +0 -1
- package/dist/auditor.d.ts.map +0 -1
- package/dist/auditor.js.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/gemma-channel.d.ts.map +0 -1
- package/dist/gemma-channel.js.map +0 -1
- package/dist/gemma-diffusion.d.ts.map +0 -1
- package/dist/gemma-diffusion.js.map +0 -1
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/interfaces.d.ts.map +0 -1
- package/dist/interfaces.js.map +0 -1
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js.map +0 -1
- package/dist/providers/anthropic.d.ts.map +0 -1
- package/dist/providers/anthropic.js.map +0 -1
- package/dist/providers/google.d.ts.map +0 -1
- package/dist/providers/google.js.map +0 -1
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.js.map +0 -1
- package/dist/providers/ollama.d.ts.map +0 -1
- package/dist/providers/ollama.js.map +0 -1
- package/dist/providers/openai.d.ts.map +0 -1
- package/dist/providers/openai.js.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/router.js.map +0 -1
- package/dist/stream-decoder.d.ts.map +0 -1
- package/dist/stream-decoder.js.map +0 -1
- package/dist/structured-output.d.ts.map +0 -1
- package/dist/structured-output.js.map +0 -1
- package/dist/thinking.d.ts.map +0 -1
- package/dist/thinking.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/zod-adapter.d.ts.map +0 -1
- package/dist/zod-adapter.js.map +0 -1
- package/src/ai-model.ts +0 -400
- package/src/auditor.ts +0 -213
- package/src/client.ts +0 -402
- package/src/debug/debug-google-streaming.ts +0 -97
- package/src/debug/debug-tool-execution.ts +0 -86
- package/src/debug/test-lmstudio-tools.ts +0 -155
- package/src/demos/README.md +0 -47
- package/src/demos/basic/universal-llm-examples.ts +0 -161
- package/src/demos/diffusion-gemma/.env +0 -29
- package/src/demos/diffusion-gemma/.env.example +0 -27
- package/src/demos/diffusion-gemma/CLAUDE.md +0 -95
- package/src/demos/diffusion-gemma/README.md +0 -59
- package/src/demos/diffusion-gemma/canvas.ts +0 -1606
- package/src/demos/diffusion-gemma/docker-compose.yml +0 -29
- package/src/demos/diffusion-gemma/probe-stream.ts +0 -51
- package/src/demos/diffusion-gemma/probe-tools.ts +0 -55
- package/src/demos/diffusion-gemma/server.ts +0 -1205
- package/src/demos/diffusion-gemma/start-vllm.sh +0 -98
- package/src/demos/mcp/astrid-memory-demo.ts +0 -295
- package/src/demos/mcp/astrid-persona-memory.ts +0 -357
- package/src/demos/mcp/mcp-mongodb-demo.ts +0 -275
- package/src/demos/mcp/simple-astrid-memory.ts +0 -148
- package/src/demos/mcp/simple-mcp-demo.ts +0 -68
- package/src/demos/mcp/working-mcp-demo.ts +0 -62
- package/src/demos/model-alias-demo.ts +0 -0
- package/src/demos/tools/RAG_MEMORY_INTEGRATION.md +0 -267
- package/src/demos/tools/astrid-memory-demo.ts +0 -270
- package/src/demos/tools/astrid-production-memory-clean.ts +0 -785
- package/src/demos/tools/astrid-production-memory.ts +0 -558
- package/src/demos/tools/basic-translation-test.ts +0 -66
- package/src/demos/tools/chromadb-similarity-tuning.ts +0 -390
- package/src/demos/tools/clean-multilingual-conversation.ts +0 -209
- package/src/demos/tools/clean-translation-test.ts +0 -119
- package/src/demos/tools/clean-universal-multilingual-test.ts +0 -131
- package/src/demos/tools/complete-rag-demo.ts +0 -369
- package/src/demos/tools/complete-tool-demo.ts +0 -132
- package/src/demos/tools/demo-tool-calling.ts +0 -124
- package/src/demos/tools/dynamic-language-switching-test.ts +0 -251
- package/src/demos/tools/hybrid-thinking-test.ts +0 -154
- package/src/demos/tools/memory-integration-test.ts +0 -420
- package/src/demos/tools/multilingual-memory-system.ts +0 -802
- package/src/demos/tools/ondemand-translation-demo.ts +0 -655
- package/src/demos/tools/production-tool-demo.ts +0 -245
- package/src/demos/tools/revolutionary-multilingual-test.ts +0 -151
- package/src/demos/tools/rigorous-language-analysis.ts +0 -218
- package/src/demos/tools/test-universal-memory-system.ts +0 -126
- package/src/demos/tools/translation-integration-guide.ts +0 -346
- package/src/demos/tools/universal-memory-system.ts +0 -560
- package/src/gemma-channel.ts +0 -47
- package/src/gemma-diffusion.ts +0 -167
- package/src/http.ts +0 -261
- package/src/index.ts +0 -180
- package/src/interfaces.ts +0 -843
- package/src/mcp.ts +0 -345
- package/src/providers/anthropic.ts +0 -796
- package/src/providers/google.ts +0 -840
- package/src/providers/index.ts +0 -8
- package/src/providers/ollama.ts +0 -503
- package/src/providers/openai.ts +0 -587
- package/src/router.ts +0 -785
- package/src/stream-decoder.ts +0 -535
- package/src/structured-output.ts +0 -759
- package/src/test-scripts/test-advanced-tools.ts +0 -310
- package/src/test-scripts/test-google-deep-research.ts +0 -33
- package/src/test-scripts/test-google-streaming-enhanced.ts +0 -147
- package/src/test-scripts/test-google-streaming.ts +0 -63
- package/src/test-scripts/test-google-system-prompt-comprehensive.ts +0 -189
- package/src/test-scripts/test-google-thinking.ts +0 -46
- package/src/test-scripts/test-mcp-config.ts +0 -28
- package/src/test-scripts/test-mcp-connection.ts +0 -29
- package/src/test-scripts/test-system-message-positions.ts +0 -163
- package/src/test-scripts/test-system-prompt-improvement-demo.ts +0 -83
- package/src/test-scripts/test-tool-calling.ts +0 -231
- package/src/test-scripts/test-vllm-qwen36.ts +0 -256
- package/src/tests/ai-model.test.ts +0 -1614
- package/src/tests/auditor.test.ts +0 -224
- package/src/tests/gemma-diffusion.test.ts +0 -115
- package/src/tests/http.test.ts +0 -200
- package/src/tests/interfaces.test.ts +0 -117
- package/src/tests/providers/anthropic.test.ts +0 -118
- package/src/tests/providers/google.test.ts +0 -841
- package/src/tests/providers/ollama.test.ts +0 -1034
- package/src/tests/providers/openai.test.ts +0 -1511
- package/src/tests/router.test.ts +0 -254
- package/src/tests/stream-decoder.test.ts +0 -263
- package/src/tests/structured-output.test.ts +0 -1450
- package/src/tests/thinking.test.ts +0 -65
- package/src/tests/tools.test.ts +0 -175
- package/src/thinking.ts +0 -73
- package/src/tools.ts +0 -246
- package/src/zod-adapter.ts +0 -72
package/src/gemma-diffusion.ts
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DiffusionGemma (vLLM) native-protocol adapter.
|
|
3
|
-
*
|
|
4
|
-
* Trimmed vLLM builds that serve DiffusionGemma ship with NO reasoning parser
|
|
5
|
-
* and NO tool-call parser module, and they reject OpenAI-style `tools` unless
|
|
6
|
-
* `--tool-call-parser` is configured. Everything therefore has to be handled
|
|
7
|
-
* client-side, against the model's native channel format (visible only when
|
|
8
|
-
* the request sets `skip_special_tokens: false`):
|
|
9
|
-
*
|
|
10
|
-
* <|channel>thought ...reasoning... <channel|> reasoning channel
|
|
11
|
-
* <|tool_call>call:name{k:<|"|>v<|"|>,n:3}<tool_call|> tool call
|
|
12
|
-
*
|
|
13
|
-
* Tool-call arguments are NOT JSON: keys are bare, strings are wrapped in the
|
|
14
|
-
* <|"|> quote token, numbers/booleans are bare (see the model's
|
|
15
|
-
* chat_template.jinja `format_argument` macro). `gemmaArgsToJson` converts
|
|
16
|
-
* that into a standard JSON string.
|
|
17
|
-
*
|
|
18
|
-
* Request-side protocol (implemented in the OpenAI provider):
|
|
19
|
-
* - always send `skip_special_tokens: false`
|
|
20
|
-
* - send `tools` with `tool_choice: 'none'` — vLLM still renders the
|
|
21
|
-
* declarations into the chat template, it just skips its (absent) parser
|
|
22
|
-
* - send history tool turns structurally (assistant `tool_calls` +
|
|
23
|
-
* `role: 'tool'` messages) — the chat template renders them natively
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { extractGemmaThoughtChannels } from './gemma-channel.js';
|
|
27
|
-
|
|
28
|
-
export interface GemmaParsedToolCall {
|
|
29
|
-
readonly name: string;
|
|
30
|
-
/** JSON-encoded arguments object, ready for LLMToolCall.function.arguments */
|
|
31
|
-
readonly argumentsJson: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface GemmaDiffusionParsed {
|
|
35
|
-
/** Final answer with reasoning, tool-call blocks and special tokens removed */
|
|
36
|
-
readonly content: string;
|
|
37
|
-
readonly reasoning: string;
|
|
38
|
-
readonly toolCalls: readonly GemmaParsedToolCall[];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Models that speak this native protocol when served by vLLM. */
|
|
42
|
-
export function isGemmaDiffusionModel(model: string): boolean {
|
|
43
|
-
return /diffusion[-_]?gemma/i.test(model);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const TOOL_CALL_BLOCK = /<\|tool_call>\s*call:([a-zA-Z0-9_.-]+)\s*\{([\s\S]*?)\}\s*<tool_call\|>/g;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Residual control tokens that may leak into text output — including stray
|
|
50
|
-
* unbalanced channel markers (the model occasionally emits an extra
|
|
51
|
-
* <channel|> closer mid-answer).
|
|
52
|
-
*/
|
|
53
|
-
const RESIDUAL_SPECIAL = /<\|?(?:turn|think|image|audio|video|tool_response|tool_call|tool|channel)\b[^>]*?\|?>|<(?:turn|channel|tool_response|tool_call|tool)\|>/g;
|
|
54
|
-
|
|
55
|
-
const QUOTE_TOKEN = '<|"|>';
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Convert the Gemma template's pseudo-JSON argument syntax to a JSON string.
|
|
59
|
-
* Lenient by design: bare words that aren't numbers/booleans become strings,
|
|
60
|
-
* since the model occasionally omits the quote token.
|
|
61
|
-
*/
|
|
62
|
-
export function gemmaArgsToJson(body: string): string {
|
|
63
|
-
// Argument bodies arrive without their outer braces (the regex strips them)
|
|
64
|
-
const src = `{${body}}`;
|
|
65
|
-
let i = 0;
|
|
66
|
-
const n = src.length;
|
|
67
|
-
|
|
68
|
-
function skipWs(): void {
|
|
69
|
-
while (i < n && /\s/.test(src[i]!)) i++;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function parseQuoted(): string {
|
|
73
|
-
// positioned at the start of QUOTE_TOKEN
|
|
74
|
-
i += QUOTE_TOKEN.length;
|
|
75
|
-
const end = src.indexOf(QUOTE_TOKEN, i);
|
|
76
|
-
const raw = end === -1 ? src.slice(i) : src.slice(i, end);
|
|
77
|
-
i = end === -1 ? n : end + QUOTE_TOKEN.length;
|
|
78
|
-
return raw;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function parseBare(stops: string): string {
|
|
82
|
-
const start = i;
|
|
83
|
-
while (i < n && !stops.includes(src[i]!) && !src.startsWith(QUOTE_TOKEN, i)) i++;
|
|
84
|
-
return src.slice(start, i).trim();
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function parseValue(): string {
|
|
88
|
-
skipWs();
|
|
89
|
-
if (src.startsWith(QUOTE_TOKEN, i)) return JSON.stringify(parseQuoted());
|
|
90
|
-
const c = src[i];
|
|
91
|
-
if (c === '{') return parseObject();
|
|
92
|
-
if (c === '[') return parseArray();
|
|
93
|
-
const bare = parseBare(',}]');
|
|
94
|
-
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(bare)) return bare;
|
|
95
|
-
if (bare === 'true' || bare === 'false' || bare === 'null') return bare;
|
|
96
|
-
return JSON.stringify(bare);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function parseObject(): string {
|
|
100
|
-
i++; // consume {
|
|
101
|
-
const parts: string[] = [];
|
|
102
|
-
skipWs();
|
|
103
|
-
while (i < n && src[i] !== '}') {
|
|
104
|
-
skipWs();
|
|
105
|
-
const key = src.startsWith(QUOTE_TOKEN, i) ? parseQuoted() : parseBare(':');
|
|
106
|
-
skipWs();
|
|
107
|
-
if (src[i] === ':') i++;
|
|
108
|
-
const value = parseValue();
|
|
109
|
-
parts.push(`${JSON.stringify(key.trim())}:${value}`);
|
|
110
|
-
skipWs();
|
|
111
|
-
if (src[i] === ',') i++;
|
|
112
|
-
skipWs();
|
|
113
|
-
}
|
|
114
|
-
i++; // consume }
|
|
115
|
-
return `{${parts.join(',')}}`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function parseArray(): string {
|
|
119
|
-
i++; // consume [
|
|
120
|
-
const parts: string[] = [];
|
|
121
|
-
skipWs();
|
|
122
|
-
while (i < n && src[i] !== ']') {
|
|
123
|
-
parts.push(parseValue());
|
|
124
|
-
skipWs();
|
|
125
|
-
if (src[i] === ',') i++;
|
|
126
|
-
skipWs();
|
|
127
|
-
}
|
|
128
|
-
i++; // consume ]
|
|
129
|
-
return `[${parts.join(',')}]`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
skipWs();
|
|
133
|
-
return parseObject();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Parse a complete raw DiffusionGemma output into reasoning, tool calls and
|
|
138
|
-
* clean answer text.
|
|
139
|
-
*/
|
|
140
|
-
export function parseGemmaDiffusionOutput(raw: string): GemmaDiffusionParsed {
|
|
141
|
-
if (!raw) return { content: raw, reasoning: '', toolCalls: [] };
|
|
142
|
-
|
|
143
|
-
const toolCalls: GemmaParsedToolCall[] = [];
|
|
144
|
-
let text = raw.replace(TOOL_CALL_BLOCK, (_m, name: string, args: string) => {
|
|
145
|
-
toolCalls.push({ name, argumentsJson: gemmaArgsToJson(args) });
|
|
146
|
-
return '';
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const channels = extractGemmaThoughtChannels(text);
|
|
150
|
-
text = channels.content;
|
|
151
|
-
|
|
152
|
-
// Unterminated thought channel (model hit max_tokens mid-reasoning)
|
|
153
|
-
let reasoning = channels.reasoning;
|
|
154
|
-
const danglingThought = text.match(/<\|channel>\s*thought\s*\r?\n?([\s\S]*)$/i);
|
|
155
|
-
if (danglingThought) {
|
|
156
|
-
reasoning = reasoning ? `${reasoning}\n\n${danglingThought[1]!.trim()}` : danglingThought[1]!.trim();
|
|
157
|
-
text = text.slice(0, danglingThought.index);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
text = text.replace(RESIDUAL_SPECIAL, '');
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
content: text.trim(),
|
|
164
|
-
reasoning,
|
|
165
|
-
toolCalls,
|
|
166
|
-
};
|
|
167
|
-
}
|
package/src/http.ts
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Universal LLM Client v3 — HTTP Utilities
|
|
3
|
-
*
|
|
4
|
-
* Zero-dependency HTTP layer using native fetch.
|
|
5
|
-
* Works on Node 22+, Bun, Deno, and browsers.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { LLMClientOptions } from './interfaces.js';
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Types
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
export interface HttpRequestOptions {
|
|
15
|
-
method?: 'GET' | 'POST';
|
|
16
|
-
headers?: Record<string, string>;
|
|
17
|
-
body?: unknown;
|
|
18
|
-
timeout?: number;
|
|
19
|
-
signal?: AbortSignal;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface HttpResponse<T = unknown> {
|
|
23
|
-
ok: boolean;
|
|
24
|
-
status: number;
|
|
25
|
-
data: T;
|
|
26
|
-
/** Response headers (when available) */
|
|
27
|
-
headers?: Headers;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ============================================================================
|
|
31
|
-
// HTTP Request
|
|
32
|
-
// ============================================================================
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Make an HTTP request with timeout and error handling.
|
|
36
|
-
* Uses native fetch (available in all target runtimes).
|
|
37
|
-
*/
|
|
38
|
-
export async function httpRequest<T = unknown>(
|
|
39
|
-
url: string,
|
|
40
|
-
options: HttpRequestOptions = {},
|
|
41
|
-
): Promise<HttpResponse<T>> {
|
|
42
|
-
const { method = 'GET', headers = {}, body, timeout = 30000, signal } = options;
|
|
43
|
-
|
|
44
|
-
const controller = new AbortController();
|
|
45
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
46
|
-
|
|
47
|
-
// Combine external signal with timeout
|
|
48
|
-
const combinedSignal = signal
|
|
49
|
-
? AbortSignal.any([signal, controller.signal])
|
|
50
|
-
: controller.signal;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const response = await fetch(url, {
|
|
54
|
-
method,
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json',
|
|
57
|
-
...headers,
|
|
58
|
-
},
|
|
59
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
60
|
-
signal: combinedSignal,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
clearTimeout(timeoutId);
|
|
64
|
-
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
const errorText = await response.text().catch(() => 'Unknown error');
|
|
67
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const data = (await response.json()) as T;
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
ok: response.ok,
|
|
74
|
-
status: response.status,
|
|
75
|
-
data,
|
|
76
|
-
headers: response.headers,
|
|
77
|
-
};
|
|
78
|
-
} catch (error) {
|
|
79
|
-
clearTimeout(timeoutId);
|
|
80
|
-
|
|
81
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
82
|
-
throw new Error(`Request timeout after ${timeout}ms: ${url}`);
|
|
83
|
-
}
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ============================================================================
|
|
89
|
-
// Streaming HTTP
|
|
90
|
-
// ============================================================================
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Make a streaming HTTP request.
|
|
94
|
-
* Yields raw string chunks as they arrive.
|
|
95
|
-
*/
|
|
96
|
-
export async function* httpStream(
|
|
97
|
-
url: string,
|
|
98
|
-
options: HttpRequestOptions = {},
|
|
99
|
-
): AsyncGenerator<string, void, unknown> {
|
|
100
|
-
const { method = 'POST', headers = {}, body, timeout = 120000, signal } = options;
|
|
101
|
-
|
|
102
|
-
const controller = new AbortController();
|
|
103
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
104
|
-
|
|
105
|
-
const combinedSignal = signal
|
|
106
|
-
? AbortSignal.any([signal, controller.signal])
|
|
107
|
-
: controller.signal;
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const response = await fetch(url, {
|
|
111
|
-
method,
|
|
112
|
-
headers: {
|
|
113
|
-
'Content-Type': 'application/json',
|
|
114
|
-
...headers,
|
|
115
|
-
},
|
|
116
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
117
|
-
signal: combinedSignal,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
clearTimeout(timeoutId);
|
|
121
|
-
|
|
122
|
-
if (!response.ok) {
|
|
123
|
-
const errorText = await response.text().catch(() => 'Unknown error');
|
|
124
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!response.body) {
|
|
128
|
-
throw new Error('No response body for streaming');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const reader = response.body.getReader();
|
|
132
|
-
const decoder = new TextDecoder();
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
while (true) {
|
|
136
|
-
const { done, value } = await reader.read();
|
|
137
|
-
if (done) break;
|
|
138
|
-
yield decoder.decode(value, { stream: true });
|
|
139
|
-
}
|
|
140
|
-
} finally {
|
|
141
|
-
reader.releaseLock();
|
|
142
|
-
}
|
|
143
|
-
} catch (error) {
|
|
144
|
-
clearTimeout(timeoutId);
|
|
145
|
-
|
|
146
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
147
|
-
throw new Error(`Stream timeout after ${timeout}ms: ${url}`);
|
|
148
|
-
}
|
|
149
|
-
throw error;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ============================================================================
|
|
154
|
-
// Protocol Parsers
|
|
155
|
-
// ============================================================================
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Parse NDJSON (newline-delimited JSON) stream.
|
|
159
|
-
* Used by Ollama's streaming API.
|
|
160
|
-
*/
|
|
161
|
-
export async function* parseNDJSON<T = unknown>(
|
|
162
|
-
stream: AsyncGenerator<string>,
|
|
163
|
-
): AsyncGenerator<T, void, unknown> {
|
|
164
|
-
let buffer = '';
|
|
165
|
-
|
|
166
|
-
for await (const chunk of stream) {
|
|
167
|
-
buffer += chunk;
|
|
168
|
-
const lines = buffer.split('\n');
|
|
169
|
-
buffer = lines.pop() ?? '';
|
|
170
|
-
|
|
171
|
-
for (const line of lines) {
|
|
172
|
-
const trimmed = line.trim();
|
|
173
|
-
if (!trimmed) continue;
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
yield JSON.parse(trimmed) as T;
|
|
177
|
-
} catch {
|
|
178
|
-
// Skip invalid JSON lines
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Handle remaining buffer
|
|
184
|
-
if (buffer.trim()) {
|
|
185
|
-
try {
|
|
186
|
-
yield JSON.parse(buffer) as T;
|
|
187
|
-
} catch {
|
|
188
|
-
// Skip invalid JSON
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Parse Server-Sent Events stream.
|
|
195
|
-
* Used by OpenAI-compatible APIs and LlamaCpp/vLLM.
|
|
196
|
-
*/
|
|
197
|
-
export async function* parseSSE(
|
|
198
|
-
stream: AsyncGenerator<string>,
|
|
199
|
-
): AsyncGenerator<{ event?: string; data: string }, void, unknown> {
|
|
200
|
-
let buffer = '';
|
|
201
|
-
|
|
202
|
-
for await (const chunk of stream) {
|
|
203
|
-
buffer += chunk;
|
|
204
|
-
|
|
205
|
-
// Split by double newlines (SSE event delimiter)
|
|
206
|
-
const events = buffer.split('\n\n');
|
|
207
|
-
buffer = events.pop() ?? '';
|
|
208
|
-
|
|
209
|
-
for (const event of events) {
|
|
210
|
-
const lines = event.split('\n');
|
|
211
|
-
let eventType: string | undefined;
|
|
212
|
-
const dataLines: string[] = [];
|
|
213
|
-
|
|
214
|
-
for (const line of lines) {
|
|
215
|
-
if (line.startsWith('event:')) {
|
|
216
|
-
eventType = line.slice(6).trim();
|
|
217
|
-
} else if (line.startsWith('data:')) {
|
|
218
|
-
dataLines.push(line.slice(5).trim());
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const data = dataLines.join('\n');
|
|
223
|
-
if (data && data !== '[DONE]') {
|
|
224
|
-
yield { event: eventType, data };
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ============================================================================
|
|
231
|
-
// Header Utilities
|
|
232
|
-
// ============================================================================
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Build standard headers for LLM API requests.
|
|
236
|
-
* Merges any provider-specific extraHeaders (from ProviderConfig) on top.
|
|
237
|
-
* Provider clients can still fully override (e.g. Anthropic uses x-api-key).
|
|
238
|
-
*
|
|
239
|
-
* Respects authHeader / authPrefix from config for Azure-style or gateway auth.
|
|
240
|
-
*/
|
|
241
|
-
export function buildHeaders(options: LLMClientOptions): Record<string, string> {
|
|
242
|
-
const headers: Record<string, string> = {
|
|
243
|
-
'Content-Type': 'application/json',
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
if (options.apiKey) {
|
|
247
|
-
const headerName = options.authHeader || 'Authorization';
|
|
248
|
-
// Sensible default prefix: Bearer for Authorization, nothing for api-key / x-api-key etc.
|
|
249
|
-
const defaultPrefix = headerName.toLowerCase() === 'authorization' ? 'Bearer ' : '';
|
|
250
|
-
const prefix = options.authPrefix !== undefined ? options.authPrefix : defaultPrefix;
|
|
251
|
-
headers[headerName] = `${prefix}${options.apiKey}`.trim();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Merge provider-specific extras (e.g. Azure 'api-key', custom gateway headers).
|
|
255
|
-
// Later entries win on conflicts, allowing complete override of auth.
|
|
256
|
-
if (options.extraHeaders) {
|
|
257
|
-
Object.assign(headers, options.extraHeaders);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return headers;
|
|
261
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Universal LLM Client v3
|
|
3
|
-
*
|
|
4
|
-
* A universal LLM client with transparent provider failover,
|
|
5
|
-
* streaming tool execution, pluggable reasoning, and native observability.
|
|
6
|
-
*
|
|
7
|
-
* @module universal-llm-client
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Public API — The Universal Client
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
export { AIModel } from './ai-model.js';
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Types & Interfaces
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
export {
|
|
21
|
-
// Enums
|
|
22
|
-
AIModelApiType,
|
|
23
|
-
AIModelType,
|
|
24
|
-
// Config
|
|
25
|
-
type AIModelConfig,
|
|
26
|
-
type ProviderConfig,
|
|
27
|
-
type LLMClientOptions,
|
|
28
|
-
// Messages
|
|
29
|
-
type LLMChatMessage,
|
|
30
|
-
type LLMMessageContent,
|
|
31
|
-
type LLMContentPart,
|
|
32
|
-
type LLMTextContent,
|
|
33
|
-
type LLMImageContent,
|
|
34
|
-
type LLMAudioContent,
|
|
35
|
-
// Responses
|
|
36
|
-
type LLMChatResponse,
|
|
37
|
-
type TokenUsageInfo,
|
|
38
|
-
// Tools
|
|
39
|
-
type LLMToolCall,
|
|
40
|
-
type LLMToolDefinition,
|
|
41
|
-
type LLMFunction,
|
|
42
|
-
type ToolHandler,
|
|
43
|
-
type ToolExecutionResult,
|
|
44
|
-
type ToolRegistry,
|
|
45
|
-
type ToolRegistryEntry,
|
|
46
|
-
// Options
|
|
47
|
-
type ChatOptions,
|
|
48
|
-
type ResponseFormat,
|
|
49
|
-
type OutputOptions,
|
|
50
|
-
// Thinking / reasoning
|
|
51
|
-
type ThinkingLevel,
|
|
52
|
-
// Deep Research (Gemini)
|
|
53
|
-
type DeepResearchOptions,
|
|
54
|
-
type DeepResearchResult,
|
|
55
|
-
type DeepResearchStep,
|
|
56
|
-
type DeepResearchEvent,
|
|
57
|
-
// Model info
|
|
58
|
-
type ModelMetadata,
|
|
59
|
-
// Helpers
|
|
60
|
-
textContent,
|
|
61
|
-
imageContent,
|
|
62
|
-
multimodalMessage,
|
|
63
|
-
extractTextContent,
|
|
64
|
-
hasImages,
|
|
65
|
-
audioContent,
|
|
66
|
-
hasAudio,
|
|
67
|
-
} from './interfaces.js';
|
|
68
|
-
|
|
69
|
-
// ============================================================================
|
|
70
|
-
// Observability
|
|
71
|
-
// ============================================================================
|
|
72
|
-
|
|
73
|
-
export {
|
|
74
|
-
type Auditor,
|
|
75
|
-
type AuditEvent,
|
|
76
|
-
type AuditEventType,
|
|
77
|
-
NoopAuditor,
|
|
78
|
-
ConsoleAuditor,
|
|
79
|
-
BufferedAuditor,
|
|
80
|
-
} from './auditor.js';
|
|
81
|
-
|
|
82
|
-
// ============================================================================
|
|
83
|
-
// Stream Decoding
|
|
84
|
-
// ============================================================================
|
|
85
|
-
|
|
86
|
-
export {
|
|
87
|
-
type StreamDecoder,
|
|
88
|
-
type DecodedEvent,
|
|
89
|
-
type DecoderCallback,
|
|
90
|
-
type DecoderType,
|
|
91
|
-
type DecoderOptions,
|
|
92
|
-
type DecoderFactory,
|
|
93
|
-
createDecoder,
|
|
94
|
-
registerDecoder,
|
|
95
|
-
getRegisteredDecoders,
|
|
96
|
-
PassthroughDecoder,
|
|
97
|
-
StandardChatDecoder,
|
|
98
|
-
InterleavedReasoningDecoder,
|
|
99
|
-
} from './stream-decoder.js';
|
|
100
|
-
|
|
101
|
-
// ============================================================================
|
|
102
|
-
// Tool Utilities
|
|
103
|
-
// ============================================================================
|
|
104
|
-
|
|
105
|
-
export {
|
|
106
|
-
ToolBuilder,
|
|
107
|
-
ToolExecutor,
|
|
108
|
-
createTimeTool,
|
|
109
|
-
createRandomNumberTool,
|
|
110
|
-
} from './tools.js';
|
|
111
|
-
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// HTTP Utilities (for advanced use cases)
|
|
114
|
-
// ============================================================================
|
|
115
|
-
|
|
116
|
-
export {
|
|
117
|
-
httpRequest,
|
|
118
|
-
httpStream,
|
|
119
|
-
parseNDJSON,
|
|
120
|
-
parseSSE,
|
|
121
|
-
buildHeaders,
|
|
122
|
-
type HttpRequestOptions,
|
|
123
|
-
type HttpResponse,
|
|
124
|
-
} from './http.js';
|
|
125
|
-
|
|
126
|
-
// ============================================================================
|
|
127
|
-
// DiffusionGemma Native Protocol (vLLM without server-side parsers)
|
|
128
|
-
// ============================================================================
|
|
129
|
-
|
|
130
|
-
export {
|
|
131
|
-
isGemmaDiffusionModel,
|
|
132
|
-
parseGemmaDiffusionOutput,
|
|
133
|
-
gemmaArgsToJson,
|
|
134
|
-
type GemmaDiffusionParsed,
|
|
135
|
-
type GemmaParsedToolCall,
|
|
136
|
-
} from './gemma-diffusion.js';
|
|
137
|
-
|
|
138
|
-
// ============================================================================
|
|
139
|
-
// MCP Integration
|
|
140
|
-
// ============================================================================
|
|
141
|
-
|
|
142
|
-
export {
|
|
143
|
-
MCPToolBridge,
|
|
144
|
-
type MCPBridgeConfig,
|
|
145
|
-
type MCPServerConfig,
|
|
146
|
-
type MCPTool,
|
|
147
|
-
} from './mcp.js';
|
|
148
|
-
|
|
149
|
-
// ============================================================================
|
|
150
|
-
// Structured Output
|
|
151
|
-
// ============================================================================
|
|
152
|
-
|
|
153
|
-
export {
|
|
154
|
-
StructuredOutputError,
|
|
155
|
-
type StructuredOutputErrorOptions,
|
|
156
|
-
type StructuredOutputOptions,
|
|
157
|
-
type StructuredOutputResult,
|
|
158
|
-
type StructuredOutputSuccess,
|
|
159
|
-
type StructuredOutputFailure,
|
|
160
|
-
type JSONSchema,
|
|
161
|
-
type SchemaProvider,
|
|
162
|
-
type ProviderSchema,
|
|
163
|
-
type SchemaConfig,
|
|
164
|
-
isStructuredOutputSuccess,
|
|
165
|
-
isStructuredOutputFailure,
|
|
166
|
-
// Schema conversion utilities
|
|
167
|
-
normalizeJsonSchema,
|
|
168
|
-
convertToProviderSchema,
|
|
169
|
-
stripUnsupportedFeatures,
|
|
170
|
-
getJsonSchema,
|
|
171
|
-
getJsonSchemaFromConfig,
|
|
172
|
-
// Validation functions
|
|
173
|
-
parseStructured,
|
|
174
|
-
tryParseStructured,
|
|
175
|
-
validateStructuredOutput,
|
|
176
|
-
stripJsonFences,
|
|
177
|
-
// Streaming parser
|
|
178
|
-
StreamingJsonParser,
|
|
179
|
-
type StreamingStructuredResult,
|
|
180
|
-
} from './structured-output.js';
|