@yeaft/webchat-agent 0.1.1011 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.1011",
3
+ "version": "0.1.1012",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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
- constructor({ apiKey, baseUrl = DEFAULT_BASE_URL }) {
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
  });
@@ -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 { normalizeKnownProviderForRuntime } from './known-providers.js';
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 cacheKey = `${provider.name}::${protocol}::${apiKeyFp}`;
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.