@yeaft/webchat-agent 0.1.1010 → 0.1.1012
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/package.json +1 -1
- package/yeaft/llm/anthropic.js +28 -17
- package/yeaft/llm/router.js +38 -3
package/package.json
CHANGED
package/yeaft/llm/anthropic.js
CHANGED
|
@@ -62,11 +62,29 @@ const API_VERSION = '2023-06-01';
|
|
|
62
62
|
export class AnthropicAdapter extends LLMAdapter {
|
|
63
63
|
#apiKey;
|
|
64
64
|
#baseUrl;
|
|
65
|
+
#authHeaderMode;
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
/**
|
|
68
|
+
* @param {{ apiKey: string, baseUrl?: string, authHeaderMode?: 'x-api-key'|'bearer' }} config
|
|
69
|
+
*/
|
|
70
|
+
constructor({ apiKey, baseUrl = DEFAULT_BASE_URL, authHeaderMode = 'x-api-key' }) {
|
|
67
71
|
super({ apiKey, baseUrl });
|
|
68
72
|
this.#apiKey = apiKey;
|
|
69
73
|
this.#baseUrl = baseUrl;
|
|
74
|
+
this.#authHeaderMode = authHeaderMode === 'bearer' ? 'bearer' : 'x-api-key';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#headers() {
|
|
78
|
+
const headers = {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
'anthropic-version': API_VERSION,
|
|
81
|
+
};
|
|
82
|
+
if (this.#authHeaderMode === 'bearer') {
|
|
83
|
+
headers.Authorization = `Bearer ${this.#apiKey}`;
|
|
84
|
+
} else {
|
|
85
|
+
headers['x-api-key'] = this.#apiKey;
|
|
86
|
+
}
|
|
87
|
+
return headers;
|
|
70
88
|
}
|
|
71
89
|
|
|
72
90
|
/**
|
|
@@ -159,23 +177,24 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
159
177
|
* @param {string} body
|
|
160
178
|
*/
|
|
161
179
|
#classifyError(status, body) {
|
|
180
|
+
const authHint = `auth=${this.#authHeaderMode}`;
|
|
162
181
|
if (status === 401 || status === 403) {
|
|
163
|
-
return new LLMAuthError(`Anthropic auth error: ${body}`, status);
|
|
182
|
+
return new LLMAuthError(`Anthropic auth error (${authHint}): ${body}`, status);
|
|
164
183
|
}
|
|
165
184
|
if (status === 429) {
|
|
166
185
|
const retryAfter = null; // Could parse retry-after header
|
|
167
|
-
return new LLMRateLimitError(`Anthropic rate limit: ${body}`, status, retryAfter);
|
|
186
|
+
return new LLMRateLimitError(`Anthropic rate limit (${authHint}): ${body}`, status, retryAfter);
|
|
168
187
|
}
|
|
169
188
|
if (status === 529) {
|
|
170
|
-
return new LLMRateLimitError(`Anthropic overloaded: ${body}`, status);
|
|
189
|
+
return new LLMRateLimitError(`Anthropic overloaded (${authHint}): ${body}`, status);
|
|
171
190
|
}
|
|
172
191
|
if (body.includes('prompt is too long') || body.includes('max_tokens')) {
|
|
173
|
-
return new LLMContextError(`Anthropic context error: ${body}`);
|
|
192
|
+
return new LLMContextError(`Anthropic context error (${authHint}): ${body}`);
|
|
174
193
|
}
|
|
175
194
|
if (status >= 500) {
|
|
176
|
-
return new LLMServerError(`Anthropic server error: ${body}`, status);
|
|
195
|
+
return new LLMServerError(`Anthropic server error (${authHint}): ${body}`, status);
|
|
177
196
|
}
|
|
178
|
-
return new Error(`Anthropic API error ${status}: ${body}`);
|
|
197
|
+
return new Error(`Anthropic API error ${status} (${authHint}): ${body}`);
|
|
179
198
|
}
|
|
180
199
|
|
|
181
200
|
/**
|
|
@@ -205,11 +224,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
205
224
|
if (translatedTools) body.tools = translatedTools;
|
|
206
225
|
|
|
207
226
|
const url = `${this.#baseUrl}/v1/messages`;
|
|
208
|
-
const headers =
|
|
209
|
-
'Content-Type': 'application/json',
|
|
210
|
-
'x-api-key': this.#apiKey,
|
|
211
|
-
'anthropic-version': API_VERSION,
|
|
212
|
-
};
|
|
227
|
+
const headers = this.#headers();
|
|
213
228
|
|
|
214
229
|
// Expose the raw request (auth-redacted) for the debug panel. The body
|
|
215
230
|
// is captured verbatim — never truncated — so "copy request" matches
|
|
@@ -456,11 +471,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
456
471
|
|
|
457
472
|
const response = await fetch(`${this.#baseUrl}/v1/messages`, {
|
|
458
473
|
method: 'POST',
|
|
459
|
-
headers:
|
|
460
|
-
'Content-Type': 'application/json',
|
|
461
|
-
'x-api-key': this.#apiKey,
|
|
462
|
-
'anthropic-version': API_VERSION,
|
|
463
|
-
},
|
|
474
|
+
headers: this.#headers(),
|
|
464
475
|
body: JSON.stringify(body),
|
|
465
476
|
signal,
|
|
466
477
|
});
|
package/yeaft/llm/router.js
CHANGED
|
@@ -23,7 +23,12 @@
|
|
|
23
23
|
|
|
24
24
|
import { LLMAdapter } from './adapter.js';
|
|
25
25
|
import { getModelEffortOptions, getThinkingCapability, normalizeEffort, parseModelRef } from '../models.js';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
GITHUB_COPILOT_BASE_URL,
|
|
28
|
+
GITHUB_COPILOT_CREDENTIAL_PROVIDER,
|
|
29
|
+
GITHUB_COPILOT_PROVIDER_NAME,
|
|
30
|
+
normalizeKnownProviderForRuntime,
|
|
31
|
+
} from './known-providers.js';
|
|
27
32
|
import { pairSanitize } from '../pair-sanitize.js';
|
|
28
33
|
|
|
29
34
|
/**
|
|
@@ -182,6 +187,31 @@ function sliceUnchanged(original, cleaned) {
|
|
|
182
187
|
return true;
|
|
183
188
|
}
|
|
184
189
|
|
|
190
|
+
function normalizedOrigin(url) {
|
|
191
|
+
try {
|
|
192
|
+
return new URL(url).origin;
|
|
193
|
+
} catch {
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Anthropic native uses x-api-key. Copilot's Anthropic-compatible endpoint uses
|
|
200
|
+
* a bearer token, and dynamic credential providers generally hand back bearer
|
|
201
|
+
* tokens unless explicitly overridden.
|
|
202
|
+
*
|
|
203
|
+
* @param {object} provider
|
|
204
|
+
* @returns {'x-api-key'|'bearer'}
|
|
205
|
+
*/
|
|
206
|
+
export function anthropicAuthHeaderModeForProvider(provider) {
|
|
207
|
+
const explicit = provider?.anthropicAuthHeaderMode || provider?.authHeaderMode;
|
|
208
|
+
if (explicit === 'bearer' || explicit === 'x-api-key') return explicit;
|
|
209
|
+
if (provider?.credentialProvider) return 'bearer';
|
|
210
|
+
if (provider?.name === GITHUB_COPILOT_PROVIDER_NAME) return 'bearer';
|
|
211
|
+
if (normalizedOrigin(provider?.baseUrl) === GITHUB_COPILOT_BASE_URL) return 'bearer';
|
|
212
|
+
return 'x-api-key';
|
|
213
|
+
}
|
|
214
|
+
|
|
185
215
|
/**
|
|
186
216
|
* AdapterRouter — Implements LLMAdapter, routes by model → provider.
|
|
187
217
|
*/
|
|
@@ -393,7 +423,11 @@ export class AdapterRouter extends LLMAdapter {
|
|
|
393
423
|
// header. For static providers the apiKey never changes so this stays
|
|
394
424
|
// a one-time-build cache exactly like before.
|
|
395
425
|
const apiKeyFp = apiKey ? this.#shortFingerprint(apiKey) : 'none';
|
|
396
|
-
const
|
|
426
|
+
const anthropicAuthHeaderMode = protocol === 'anthropic'
|
|
427
|
+
? anthropicAuthHeaderModeForProvider(provider)
|
|
428
|
+
: null;
|
|
429
|
+
const authModeKey = anthropicAuthHeaderMode || 'default';
|
|
430
|
+
const cacheKey = `${provider.name}::${protocol}::${authModeKey}::${apiKeyFp}`;
|
|
397
431
|
const cached = this.#adapterCache.get(cacheKey);
|
|
398
432
|
if (cached) return { adapter: cached, modelId: entry.id };
|
|
399
433
|
|
|
@@ -403,7 +437,7 @@ export class AdapterRouter extends LLMAdapter {
|
|
|
403
437
|
// (Copilot tokens rotate every ~30 min). Static-apiKey providers never
|
|
404
438
|
// change fingerprint, so this loop never finds anything to evict for
|
|
405
439
|
// them — back-compat preserved.
|
|
406
|
-
const prefix = `${provider.name}::${protocol}::`;
|
|
440
|
+
const prefix = `${provider.name}::${protocol}::${authModeKey}::`;
|
|
407
441
|
for (const key of this.#adapterCache.keys()) {
|
|
408
442
|
if (key.startsWith(prefix)) this.#adapterCache.delete(key);
|
|
409
443
|
}
|
|
@@ -415,6 +449,7 @@ export class AdapterRouter extends LLMAdapter {
|
|
|
415
449
|
adapter = new AnthropicAdapter({
|
|
416
450
|
apiKey,
|
|
417
451
|
baseUrl: provider.baseUrl,
|
|
452
|
+
authHeaderMode: anthropicAuthHeaderMode,
|
|
418
453
|
});
|
|
419
454
|
} else if (protocol === 'openai-responses') {
|
|
420
455
|
// OpenAI Responses API (/v1/responses) — canonical OpenAI-compatible path.
|