@aituber-onair/chat 0.56.0 → 0.58.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 (35) hide show
  1. package/README.ja.md +27 -2
  2. package/README.md +31 -2
  3. package/dist/cjs/constants/claude.d.ts +1 -0
  4. package/dist/cjs/constants/claude.d.ts.map +1 -1
  5. package/dist/cjs/constants/claude.js +4 -1
  6. package/dist/cjs/constants/claude.js.map +1 -1
  7. package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.d.ts.map +1 -1
  8. package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.js +1 -0
  9. package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.js.map +1 -1
  10. package/dist/cjs/utils/index.d.ts +1 -0
  11. package/dist/cjs/utils/index.d.ts.map +1 -1
  12. package/dist/cjs/utils/index.js +1 -0
  13. package/dist/cjs/utils/index.js.map +1 -1
  14. package/dist/cjs/utils/openaiCompatibleEndpoint.d.ts +57 -0
  15. package/dist/cjs/utils/openaiCompatibleEndpoint.d.ts.map +1 -0
  16. package/dist/cjs/utils/openaiCompatibleEndpoint.js +193 -0
  17. package/dist/cjs/utils/openaiCompatibleEndpoint.js.map +1 -0
  18. package/dist/esm/constants/claude.d.ts +1 -0
  19. package/dist/esm/constants/claude.d.ts.map +1 -1
  20. package/dist/esm/constants/claude.js +3 -0
  21. package/dist/esm/constants/claude.js.map +1 -1
  22. package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.d.ts.map +1 -1
  23. package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.js +2 -1
  24. package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.js.map +1 -1
  25. package/dist/esm/utils/index.d.ts +1 -0
  26. package/dist/esm/utils/index.d.ts.map +1 -1
  27. package/dist/esm/utils/index.js +1 -0
  28. package/dist/esm/utils/index.js.map +1 -1
  29. package/dist/esm/utils/openaiCompatibleEndpoint.d.ts +57 -0
  30. package/dist/esm/utils/openaiCompatibleEndpoint.d.ts.map +1 -0
  31. package/dist/esm/utils/openaiCompatibleEndpoint.js +186 -0
  32. package/dist/esm/utils/openaiCompatibleEndpoint.js.map +1 -0
  33. package/dist/umd/aituber-onair-chat.js +199 -3
  34. package/dist/umd/aituber-onair-chat.min.js +8 -8
  35. package/package.json +1 -1
@@ -7,6 +7,7 @@ export * from './screenplay';
7
7
  export * from './emotionParser';
8
8
  export * from './runOnce';
9
9
  export * from './openaiCompatibleSse';
10
+ export * from './openaiCompatibleEndpoint';
10
11
  export * from './openaiCompatibleTools';
11
12
  export * from './processChatFlow';
12
13
  export * from './visionModelResolver';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,57 @@
1
+ /** A local server with an OpenAI-compatible Chat Completions endpoint. */
2
+ export type OpenAICompatibleLocalPresetId = 'ollama' | 'lmStudio' | 'llamaCpp' | 'vllm';
3
+ export interface OpenAICompatibleLocalPreset {
4
+ id: OpenAICompatibleLocalPresetId;
5
+ label: string;
6
+ baseUrl: string;
7
+ docsUrl: string;
8
+ corsHint?: string;
9
+ }
10
+ /** Default local URLs. Change the port when your server uses a custom one. */
11
+ export declare const OPENAI_COMPATIBLE_LOCAL_PRESETS: readonly OpenAICompatibleLocalPreset[];
12
+ export type OpenAICompatibleEndpointErrorCode = 'invalid-url' | 'network' | 'aborted' | 'timeout' | 'http' | 'invalid-response';
13
+ /** Error returned by endpoint resolution and model discovery. */
14
+ export declare class OpenAICompatibleEndpointError extends Error {
15
+ readonly code: OpenAICompatibleEndpointErrorCode;
16
+ readonly status?: number;
17
+ constructor(code: OpenAICompatibleEndpointErrorCode, message: string, status?: number);
18
+ }
19
+ export interface ResolvedOpenAICompatibleEndpoint {
20
+ baseUrl: string;
21
+ chatCompletionsUrl: string;
22
+ modelsUrl: string;
23
+ }
24
+ /**
25
+ * Accepts an origin, API base URL, or full Chat Completions URL.
26
+ * Query strings and fragments are rejected rather than discarded.
27
+ */
28
+ export declare function resolveOpenAICompatibleEndpoint(input: string): ResolvedOpenAICompatibleEndpoint;
29
+ export interface ListOpenAICompatibleModelsOptions {
30
+ endpoint: string;
31
+ apiKey?: string;
32
+ timeoutMs?: number;
33
+ fetch?: typeof fetch;
34
+ signal?: AbortSignal;
35
+ }
36
+ /**
37
+ * Lists model IDs from GET /models, preserving server order.
38
+ * @throws {RangeError} If timeoutMs is not a positive finite number.
39
+ */
40
+ export declare function listOpenAICompatibleModels(options: ListOpenAICompatibleModelsOptions): Promise<string[]>;
41
+ export type OpenAICompatibleConnectionResult = {
42
+ ok: true;
43
+ latencyMs: number;
44
+ models: string[];
45
+ modelFound?: boolean;
46
+ } | {
47
+ ok: false;
48
+ error: OpenAICompatibleEndpointError;
49
+ };
50
+ /**
51
+ * Tests GET /models without generating tokens.
52
+ * @throws {RangeError} If timeoutMs is not a positive finite number.
53
+ */
54
+ export declare function testOpenAICompatibleConnection(options: ListOpenAICompatibleModelsOptions & {
55
+ model?: string;
56
+ }): Promise<OpenAICompatibleConnectionResult>;
57
+ //# sourceMappingURL=openaiCompatibleEndpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiCompatibleEndpoint.d.ts","sourceRoot":"","sources":["../../../src/utils/openaiCompatibleEndpoint.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,MAAM,MAAM,6BAA6B,GACrC,QAAQ,GACR,UAAU,GACV,UAAU,GACV,MAAM,CAAC;AAEX,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,6BAA6B,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,8EAA8E;AAC9E,eAAO,MAAM,+BAA+B,EAAE,SAAS,2BAA2B,EAmC/E,CAAC;AAEJ,MAAM,MAAM,iCAAiC,GACzC,aAAa,GACb,SAAS,GACT,SAAS,GACT,SAAS,GACT,MAAM,GACN,kBAAkB,CAAC;AAEvB,iEAAiE;AACjE,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,QAAQ,CAAC,IAAI,EAAE,iCAAiC,CAAC;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAGvB,IAAI,EAAE,iCAAiC,EACvC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM;CAOlB;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,MAAM,GACZ,gCAAgC,CA4ClC;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,MAAM,EAAE,CAAC,CA2GnB;AAED,MAAM,MAAM,gCAAgC,GACxC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,6BAA6B,CAAA;CAAE,CAAC;AAExD;;;GAGG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,iCAAiC,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9D,OAAO,CAAC,gCAAgC,CAAC,CAyB3C"}
@@ -0,0 +1,186 @@
1
+ /** Default local URLs. Change the port when your server uses a custom one. */
2
+ export const OPENAI_COMPATIBLE_LOCAL_PRESETS = [
3
+ {
4
+ id: 'ollama',
5
+ label: 'Ollama',
6
+ baseUrl: 'http://localhost:11434/v1',
7
+ docsUrl: 'https://docs.ollama.com/api/openai-compatibility',
8
+ corsHint: 'Set OLLAMA_ORIGINS to allow additional browser origins.',
9
+ },
10
+ {
11
+ id: 'lmStudio',
12
+ label: 'LM Studio',
13
+ baseUrl: 'http://localhost:1234/v1',
14
+ docsUrl: 'https://lmstudio.ai/docs/developer/openai-compat',
15
+ corsHint: 'Start the server with lms server start --cors for browser access.',
16
+ },
17
+ {
18
+ id: 'llamaCpp',
19
+ label: 'llama.cpp',
20
+ baseUrl: 'http://127.0.0.1:8080/v1',
21
+ docsUrl: 'https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md',
22
+ corsHint: 'All origins are allowed by default; use --cors-origins to restrict them.',
23
+ },
24
+ {
25
+ id: 'vllm',
26
+ label: 'vLLM',
27
+ baseUrl: 'http://localhost:8000/v1',
28
+ docsUrl: 'https://docs.vllm.ai/en/latest/serving/online_serving/openai_compatible_server/',
29
+ corsHint: 'All origins are allowed by default; use --allowed-origins to restrict them.',
30
+ },
31
+ ];
32
+ /** Error returned by endpoint resolution and model discovery. */
33
+ export class OpenAICompatibleEndpointError extends Error {
34
+ constructor(code, message, status) {
35
+ super(message);
36
+ this.name = 'OpenAICompatibleEndpointError';
37
+ this.code = code;
38
+ this.status = status;
39
+ }
40
+ }
41
+ /**
42
+ * Accepts an origin, API base URL, or full Chat Completions URL.
43
+ * Query strings and fragments are rejected rather than discarded.
44
+ */
45
+ export function resolveOpenAICompatibleEndpoint(input) {
46
+ let url;
47
+ try {
48
+ url = new URL(input.trim());
49
+ }
50
+ catch {
51
+ throw new OpenAICompatibleEndpointError('invalid-url', 'Enter a valid HTTP or HTTPS endpoint URL.');
52
+ }
53
+ if ((url.protocol !== 'http:' && url.protocol !== 'https:') ||
54
+ url.search ||
55
+ url.hash ||
56
+ url.username ||
57
+ url.password) {
58
+ throw new OpenAICompatibleEndpointError('invalid-url', 'Use an HTTP or HTTPS endpoint URL without credentials, a query, or a fragment.');
59
+ }
60
+ const path = url.pathname.replace(/\/+$/, '');
61
+ if (path.endsWith('/api/chat') || path.endsWith('/api/generate')) {
62
+ throw new OpenAICompatibleEndpointError('invalid-url', "This looks like Ollama's native API URL. Use the OpenAI-compatible base URL instead, e.g. http://localhost:11434/v1.");
63
+ }
64
+ const basePath = !path || path === '/'
65
+ ? '/v1'
66
+ : path.endsWith('/chat/completions')
67
+ ? path.slice(0, -'/chat/completions'.length)
68
+ : path;
69
+ const baseUrl = `${url.origin}${basePath}`;
70
+ return {
71
+ baseUrl,
72
+ chatCompletionsUrl: `${baseUrl}/chat/completions`,
73
+ modelsUrl: `${baseUrl}/models`,
74
+ };
75
+ }
76
+ /**
77
+ * Lists model IDs from GET /models, preserving server order.
78
+ * @throws {RangeError} If timeoutMs is not a positive finite number.
79
+ */
80
+ export async function listOpenAICompatibleModels(options) {
81
+ const { modelsUrl } = resolveOpenAICompatibleEndpoint(options.endpoint);
82
+ const timeoutMs = options.timeoutMs ?? 8000;
83
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
84
+ throw new RangeError('timeoutMs must be a positive finite number.');
85
+ }
86
+ if (options.signal?.aborted) {
87
+ throw new OpenAICompatibleEndpointError('aborted', 'The models request was aborted by the caller.');
88
+ }
89
+ const controller = new AbortController();
90
+ let abortCause;
91
+ const onAbort = () => {
92
+ if (!abortCause)
93
+ abortCause = 'caller';
94
+ controller.abort();
95
+ };
96
+ options.signal?.addEventListener('abort', onAbort, { once: true });
97
+ if (options.signal?.aborted)
98
+ onAbort();
99
+ const timeout = setTimeout(() => {
100
+ if (!abortCause)
101
+ abortCause = 'timeout';
102
+ controller.abort();
103
+ }, timeoutMs);
104
+ try {
105
+ const headers = {};
106
+ if (options.apiKey?.trim()) {
107
+ headers.Authorization = `Bearer ${options.apiKey.trim()}`;
108
+ }
109
+ const response = await (options.fetch ?? fetch)(modelsUrl, {
110
+ method: 'GET',
111
+ headers,
112
+ signal: controller.signal,
113
+ });
114
+ if (abortCause) {
115
+ throw new OpenAICompatibleEndpointError('aborted', 'The models request was aborted by the caller.');
116
+ }
117
+ if (!response.ok) {
118
+ throw new OpenAICompatibleEndpointError('http', `Model listing failed with HTTP ${response.status}.`, response.status);
119
+ }
120
+ let body;
121
+ try {
122
+ body = await response.json();
123
+ }
124
+ catch {
125
+ throw new OpenAICompatibleEndpointError('invalid-response', 'The models endpoint did not return valid JSON.');
126
+ }
127
+ if (!body ||
128
+ typeof body !== 'object' ||
129
+ !('data' in body) ||
130
+ !Array.isArray(body.data) ||
131
+ !body.data.every((model) => model &&
132
+ typeof model === 'object' &&
133
+ 'id' in model &&
134
+ typeof model.id === 'string')) {
135
+ throw new OpenAICompatibleEndpointError('invalid-response', 'The models response must contain data with model IDs.');
136
+ }
137
+ if (abortCause) {
138
+ throw new OpenAICompatibleEndpointError('aborted', 'The models request was aborted by the caller.');
139
+ }
140
+ return [...new Set(body.data.map((model) => model.id))];
141
+ }
142
+ catch (error) {
143
+ if (abortCause === 'caller') {
144
+ throw new OpenAICompatibleEndpointError('aborted', 'The models request was aborted by the caller.');
145
+ }
146
+ if (abortCause === 'timeout') {
147
+ throw new OpenAICompatibleEndpointError('timeout', `The models request timed out after ${timeoutMs} ms.`);
148
+ }
149
+ if (error instanceof OpenAICompatibleEndpointError)
150
+ throw error;
151
+ throw new OpenAICompatibleEndpointError('network', 'Could not reach the models endpoint. In a browser, this is usually a CORS restriction or the server is not running.');
152
+ }
153
+ finally {
154
+ clearTimeout(timeout);
155
+ options.signal?.removeEventListener('abort', onAbort);
156
+ }
157
+ }
158
+ /**
159
+ * Tests GET /models without generating tokens.
160
+ * @throws {RangeError} If timeoutMs is not a positive finite number.
161
+ */
162
+ export async function testOpenAICompatibleConnection(options) {
163
+ const startedAt = Date.now();
164
+ try {
165
+ const models = await listOpenAICompatibleModels(options);
166
+ return {
167
+ ok: true,
168
+ latencyMs: Date.now() - startedAt,
169
+ models,
170
+ ...(options.model !== undefined
171
+ ? { modelFound: models.includes(options.model) }
172
+ : {}),
173
+ };
174
+ }
175
+ catch (error) {
176
+ if (error instanceof RangeError)
177
+ throw error;
178
+ return {
179
+ ok: false,
180
+ error: error instanceof OpenAICompatibleEndpointError
181
+ ? error
182
+ : new OpenAICompatibleEndpointError('network', 'Could not reach the models endpoint. In a browser, this is usually a CORS restriction or the server is not running.'),
183
+ };
184
+ }
185
+ }
186
+ //# sourceMappingURL=openaiCompatibleEndpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiCompatibleEndpoint.js","sourceRoot":"","sources":["../../../src/utils/openaiCompatibleEndpoint.ts"],"names":[],"mappings":"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"}
@@ -100,6 +100,7 @@ var AITuberOnAirChat = (() => {
100
100
  MODEL_CLAUDE_4_SONNET: () => MODEL_CLAUDE_4_SONNET,
101
101
  MODEL_CLAUDE_5_1_FABLE: () => MODEL_CLAUDE_5_1_FABLE,
102
102
  MODEL_CLAUDE_5_5_OPUS: () => MODEL_CLAUDE_5_5_OPUS,
103
+ MODEL_CLAUDE_5_5_SONNET: () => MODEL_CLAUDE_5_5_SONNET,
103
104
  MODEL_CLAUDE_5_FABLE: () => MODEL_CLAUDE_5_FABLE,
104
105
  MODEL_CLAUDE_5_OPUS: () => MODEL_CLAUDE_5_OPUS,
105
106
  MODEL_CLAUDE_5_SONNET: () => MODEL_CLAUDE_5_SONNET,
@@ -252,6 +253,7 @@ var AITuberOnAirChat = (() => {
252
253
  MODEL_ZAI_GLM_5_3_FLASH: () => MODEL_ZAI_GLM_5_3_FLASH,
253
254
  MistralChatService: () => MistralChatService,
254
255
  MistralChatServiceProvider: () => MistralChatServiceProvider,
256
+ OPENAI_COMPATIBLE_LOCAL_PRESETS: () => OPENAI_COMPATIBLE_LOCAL_PRESETS,
255
257
  OPENROUTER_CREDITS_THRESHOLD: () => OPENROUTER_CREDITS_THRESHOLD,
256
258
  OPENROUTER_DEPRECATED_MODELS: () => OPENROUTER_DEPRECATED_MODELS,
257
259
  OPENROUTER_FREE_DAILY_LIMIT_HIGH_CREDITS: () => OPENROUTER_FREE_DAILY_LIMIT_HIGH_CREDITS,
@@ -262,6 +264,7 @@ var AITuberOnAirChat = (() => {
262
264
  OpenAIChatService: () => OpenAIChatService,
263
265
  OpenAIChatServiceProvider: () => OpenAIChatServiceProvider,
264
266
  OpenAICompatibleChatServiceProvider: () => OpenAICompatibleChatServiceProvider,
267
+ OpenAICompatibleEndpointError: () => OpenAICompatibleEndpointError,
265
268
  OpenRouterChatService: () => OpenRouterChatService,
266
269
  OpenRouterChatServiceProvider: () => OpenRouterChatServiceProvider,
267
270
  PLAMO_API_BASE_URL: () => PLAMO_API_BASE_URL,
@@ -333,6 +336,7 @@ var AITuberOnAirChat = (() => {
333
336
  isZaiReasoningEffortModel: () => isZaiReasoningEffortModel,
334
337
  isZaiToolStreamModel: () => isZaiToolStreamModel,
335
338
  isZaiVisionModel: () => isZaiVisionModel,
339
+ listOpenAICompatibleModels: () => listOpenAICompatibleModels,
336
340
  normalizeClaudeReasoningEffort: () => normalizeClaudeReasoningEffort,
337
341
  normalizeDeepSeekReasoningEffort: () => normalizeDeepSeekReasoningEffort,
338
342
  normalizeGeminiReasoningEffort: () => normalizeGeminiReasoningEffort,
@@ -344,11 +348,13 @@ var AITuberOnAirChat = (() => {
344
348
  parseOpenAICompatibleToolStream: () => parseOpenAICompatibleToolStream,
345
349
  processChatWithOptionalTools: () => processChatWithOptionalTools,
346
350
  refreshOpenRouterFreeModels: () => refreshOpenRouterFreeModels,
351
+ resolveOpenAICompatibleEndpoint: () => resolveOpenAICompatibleEndpoint,
347
352
  resolveVisionModel: () => resolveVisionModel,
348
353
  runOnceText: () => runOnceText,
349
354
  safeJsonParse: () => safeJsonParse,
350
355
  safeParseToolCallInput: () => safeParseToolCallInput,
351
356
  screenplayToText: () => screenplayToText,
357
+ testOpenAICompatibleConnection: () => testOpenAICompatibleConnection,
352
358
  textToScreenplay: () => textToScreenplay,
353
359
  textsToScreenplay: () => textsToScreenplay
354
360
  });
@@ -570,6 +576,7 @@ var AITuberOnAirChat = (() => {
570
576
  var MODEL_CLAUDE_5_SONNET = "claude-sonnet-5";
571
577
  var MODEL_CLAUDE_5_OPUS = "claude-opus-5";
572
578
  var MODEL_CLAUDE_5_5_OPUS = "claude-opus-5-5";
579
+ var MODEL_CLAUDE_5_5_SONNET = "claude-sonnet-5-5";
573
580
  var CLAUDE_DEPRECATED_MODELS = [
574
581
  MODEL_CLAUDE_3_HAIKU,
575
582
  MODEL_CLAUDE_3_5_HAIKU,
@@ -601,7 +608,8 @@ var AITuberOnAirChat = (() => {
601
608
  [MODEL_CLAUDE_5_1_FABLE]: CLAUDE_XHIGH_REASONING_EFFORTS,
602
609
  [MODEL_CLAUDE_5_FABLE]: CLAUDE_XHIGH_REASONING_EFFORTS,
603
610
  [MODEL_CLAUDE_5_SONNET]: CLAUDE_XHIGH_REASONING_EFFORTS,
604
- [MODEL_CLAUDE_5_OPUS]: CLAUDE_XHIGH_REASONING_EFFORTS
611
+ [MODEL_CLAUDE_5_OPUS]: CLAUDE_XHIGH_REASONING_EFFORTS,
612
+ [MODEL_CLAUDE_5_5_SONNET]: CLAUDE_XHIGH_REASONING_EFFORTS
605
613
  };
606
614
  var CLAUDE_VISION_SUPPORTED_MODELS = [
607
615
  MODEL_CLAUDE_5_1_FABLE,
@@ -615,7 +623,8 @@ var AITuberOnAirChat = (() => {
615
623
  MODEL_CLAUDE_5_FABLE,
616
624
  MODEL_CLAUDE_5_SONNET,
617
625
  MODEL_CLAUDE_5_OPUS,
618
- MODEL_CLAUDE_5_5_OPUS
626
+ MODEL_CLAUDE_5_5_OPUS,
627
+ MODEL_CLAUDE_5_5_SONNET
619
628
  ];
620
629
  function getClaudeSupportedReasoningEfforts(model) {
621
630
  return CLAUDE_REASONING_EFFORTS_BY_MODEL[model] ?? [];
@@ -1826,6 +1835,192 @@ If it's in another language, summarize in that language.
1826
1835
  };
1827
1836
  }
1828
1837
 
1838
+ // src/utils/openaiCompatibleEndpoint.ts
1839
+ var OPENAI_COMPATIBLE_LOCAL_PRESETS = [
1840
+ {
1841
+ id: "ollama",
1842
+ label: "Ollama",
1843
+ baseUrl: "http://localhost:11434/v1",
1844
+ docsUrl: "https://docs.ollama.com/api/openai-compatibility",
1845
+ corsHint: "Set OLLAMA_ORIGINS to allow additional browser origins."
1846
+ },
1847
+ {
1848
+ id: "lmStudio",
1849
+ label: "LM Studio",
1850
+ baseUrl: "http://localhost:1234/v1",
1851
+ docsUrl: "https://lmstudio.ai/docs/developer/openai-compat",
1852
+ corsHint: "Start the server with lms server start --cors for browser access."
1853
+ },
1854
+ {
1855
+ id: "llamaCpp",
1856
+ label: "llama.cpp",
1857
+ baseUrl: "http://127.0.0.1:8080/v1",
1858
+ docsUrl: "https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md",
1859
+ corsHint: "All origins are allowed by default; use --cors-origins to restrict them."
1860
+ },
1861
+ {
1862
+ id: "vllm",
1863
+ label: "vLLM",
1864
+ baseUrl: "http://localhost:8000/v1",
1865
+ docsUrl: "https://docs.vllm.ai/en/latest/serving/online_serving/openai_compatible_server/",
1866
+ corsHint: "All origins are allowed by default; use --allowed-origins to restrict them."
1867
+ }
1868
+ ];
1869
+ var OpenAICompatibleEndpointError = class extends Error {
1870
+ constructor(code, message, status) {
1871
+ super(message);
1872
+ this.name = "OpenAICompatibleEndpointError";
1873
+ this.code = code;
1874
+ this.status = status;
1875
+ }
1876
+ };
1877
+ function resolveOpenAICompatibleEndpoint(input) {
1878
+ let url;
1879
+ try {
1880
+ url = new URL(input.trim());
1881
+ } catch {
1882
+ throw new OpenAICompatibleEndpointError(
1883
+ "invalid-url",
1884
+ "Enter a valid HTTP or HTTPS endpoint URL."
1885
+ );
1886
+ }
1887
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.search || url.hash || url.username || url.password) {
1888
+ throw new OpenAICompatibleEndpointError(
1889
+ "invalid-url",
1890
+ "Use an HTTP or HTTPS endpoint URL without credentials, a query, or a fragment."
1891
+ );
1892
+ }
1893
+ const path = url.pathname.replace(/\/+$/, "");
1894
+ if (path.endsWith("/api/chat") || path.endsWith("/api/generate")) {
1895
+ throw new OpenAICompatibleEndpointError(
1896
+ "invalid-url",
1897
+ "This looks like Ollama's native API URL. Use the OpenAI-compatible base URL instead, e.g. http://localhost:11434/v1."
1898
+ );
1899
+ }
1900
+ const basePath = !path || path === "/" ? "/v1" : path.endsWith("/chat/completions") ? path.slice(0, -"/chat/completions".length) : path;
1901
+ const baseUrl = `${url.origin}${basePath}`;
1902
+ return {
1903
+ baseUrl,
1904
+ chatCompletionsUrl: `${baseUrl}/chat/completions`,
1905
+ modelsUrl: `${baseUrl}/models`
1906
+ };
1907
+ }
1908
+ async function listOpenAICompatibleModels(options) {
1909
+ const { modelsUrl } = resolveOpenAICompatibleEndpoint(options.endpoint);
1910
+ const timeoutMs = options.timeoutMs ?? 8e3;
1911
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1912
+ throw new RangeError("timeoutMs must be a positive finite number.");
1913
+ }
1914
+ if (options.signal?.aborted) {
1915
+ throw new OpenAICompatibleEndpointError(
1916
+ "aborted",
1917
+ "The models request was aborted by the caller."
1918
+ );
1919
+ }
1920
+ const controller = new AbortController();
1921
+ let abortCause;
1922
+ const onAbort = () => {
1923
+ if (!abortCause) abortCause = "caller";
1924
+ controller.abort();
1925
+ };
1926
+ options.signal?.addEventListener("abort", onAbort, { once: true });
1927
+ if (options.signal?.aborted) onAbort();
1928
+ const timeout = setTimeout(() => {
1929
+ if (!abortCause) abortCause = "timeout";
1930
+ controller.abort();
1931
+ }, timeoutMs);
1932
+ try {
1933
+ const headers = {};
1934
+ if (options.apiKey?.trim()) {
1935
+ headers.Authorization = `Bearer ${options.apiKey.trim()}`;
1936
+ }
1937
+ const response = await (options.fetch ?? fetch)(modelsUrl, {
1938
+ method: "GET",
1939
+ headers,
1940
+ signal: controller.signal
1941
+ });
1942
+ if (abortCause) {
1943
+ throw new OpenAICompatibleEndpointError(
1944
+ "aborted",
1945
+ "The models request was aborted by the caller."
1946
+ );
1947
+ }
1948
+ if (!response.ok) {
1949
+ throw new OpenAICompatibleEndpointError(
1950
+ "http",
1951
+ `Model listing failed with HTTP ${response.status}.`,
1952
+ response.status
1953
+ );
1954
+ }
1955
+ let body;
1956
+ try {
1957
+ body = await response.json();
1958
+ } catch {
1959
+ throw new OpenAICompatibleEndpointError(
1960
+ "invalid-response",
1961
+ "The models endpoint did not return valid JSON."
1962
+ );
1963
+ }
1964
+ if (!body || typeof body !== "object" || !("data" in body) || !Array.isArray(body.data) || !body.data.every(
1965
+ (model) => model && typeof model === "object" && "id" in model && typeof model.id === "string"
1966
+ )) {
1967
+ throw new OpenAICompatibleEndpointError(
1968
+ "invalid-response",
1969
+ "The models response must contain data with model IDs."
1970
+ );
1971
+ }
1972
+ if (abortCause) {
1973
+ throw new OpenAICompatibleEndpointError(
1974
+ "aborted",
1975
+ "The models request was aborted by the caller."
1976
+ );
1977
+ }
1978
+ return [...new Set(body.data.map((model) => model.id))];
1979
+ } catch (error) {
1980
+ if (abortCause === "caller") {
1981
+ throw new OpenAICompatibleEndpointError(
1982
+ "aborted",
1983
+ "The models request was aborted by the caller."
1984
+ );
1985
+ }
1986
+ if (abortCause === "timeout") {
1987
+ throw new OpenAICompatibleEndpointError(
1988
+ "timeout",
1989
+ `The models request timed out after ${timeoutMs} ms.`
1990
+ );
1991
+ }
1992
+ if (error instanceof OpenAICompatibleEndpointError) throw error;
1993
+ throw new OpenAICompatibleEndpointError(
1994
+ "network",
1995
+ "Could not reach the models endpoint. In a browser, this is usually a CORS restriction or the server is not running."
1996
+ );
1997
+ } finally {
1998
+ clearTimeout(timeout);
1999
+ options.signal?.removeEventListener("abort", onAbort);
2000
+ }
2001
+ }
2002
+ async function testOpenAICompatibleConnection(options) {
2003
+ const startedAt = Date.now();
2004
+ try {
2005
+ const models = await listOpenAICompatibleModels(options);
2006
+ return {
2007
+ ok: true,
2008
+ latencyMs: Date.now() - startedAt,
2009
+ models,
2010
+ ...options.model !== void 0 ? { modelFound: models.includes(options.model) } : {}
2011
+ };
2012
+ } catch (error) {
2013
+ if (error instanceof RangeError) throw error;
2014
+ return {
2015
+ ok: false,
2016
+ error: error instanceof OpenAICompatibleEndpointError ? error : new OpenAICompatibleEndpointError(
2017
+ "network",
2018
+ "Could not reach the models endpoint. In a browser, this is usually a CORS restriction or the server is not running."
2019
+ )
2020
+ };
2021
+ }
2022
+ }
2023
+
1829
2024
  // src/utils/openaiCompatibleTools.ts
1830
2025
  var buildOpenAICompatibleTools = (tools, format = "chat-completions") => {
1831
2026
  if (tools.length === 0) return [];
@@ -2644,7 +2839,8 @@ If it's in another language, summarize in that language.
2644
2839
  MODEL_CLAUDE_5_FABLE,
2645
2840
  MODEL_CLAUDE_5_SONNET,
2646
2841
  MODEL_CLAUDE_5_OPUS,
2647
- MODEL_CLAUDE_5_5_OPUS
2842
+ MODEL_CLAUDE_5_5_OPUS,
2843
+ MODEL_CLAUDE_5_5_SONNET
2648
2844
  ];
2649
2845
  }
2650
2846
  /**