@yeaft/webchat-agent 0.1.1033 → 0.1.1035

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.1033",
3
+ "version": "0.1.1035",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/yeaft/config.js CHANGED
@@ -23,6 +23,7 @@ import { existsSync, readFileSync } from 'fs';
23
23
  import { join } from 'path';
24
24
  import { DEFAULT_YEAFT_DIR } from './init.js';
25
25
  import { getModelEffortOptions, getThinkingCapability, modelSupportsEffort, resolveModel, parseModelRef, normalizeProviderModels, resolveContextWindow, resolveMaxOutputTokens } from './models.js';
26
+ import { inferProtocolFromModelId } from './llm/router.js';
26
27
  import { normalizeKnownProviderForRuntime } from './llm/known-providers.js';
27
28
 
28
29
  /** Default configuration values. */
@@ -456,10 +457,18 @@ export function loadConfig(overrides = {}) {
456
457
  };
457
458
  if (m.contextWindow !== undefined) entry.contextWindow = m.contextWindow;
458
459
  if (m.maxOutput !== undefined) entry.maxOutput = m.maxOutput;
459
- const effortOptions = getModelEffortOptions(m.id);
460
+ const protocol = m.protocol || p.protocol || inferProtocolFromModelId(m.id) || 'openai-responses';
461
+ const effortContext = {
462
+ protocol,
463
+ supportsEffort: m.supportsEffort,
464
+ effortOptions: m.effortOptions,
465
+ thinkingProtocol: m.thinkingProtocol,
466
+ maxBudgetTokens: m.maxBudgetTokens,
467
+ };
468
+ const effortOptions = getModelEffortOptions(m.id, effortContext);
460
469
  if (effortOptions.length > 0) {
461
- const cap = getThinkingCapability(m.id);
462
- entry.supportsEffort = modelSupportsEffort(m.id);
470
+ const cap = getThinkingCapability(m.id, effortContext);
471
+ entry.supportsEffort = modelSupportsEffort(m.id, effortContext);
463
472
  entry.effortOptions = effortOptions;
464
473
  entry.effortProtocol = cap.thinkingProtocol;
465
474
  }
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { LLMAdapter } from './adapter.js';
25
- import { getModelEffortOptions, getThinkingCapability, normalizeEffort, parseModelRef } from '../models.js';
25
+ import { getModelEffortOptions, getThinkingCapability, normalizeEffort, normalizeEffortOptions, parseModelRef } from '../models.js';
26
26
  import {
27
27
  GITHUB_COPILOT_BASE_URL,
28
28
  GITHUB_COPILOT_CREDENTIAL_PROVIDER,
@@ -48,6 +48,15 @@ export function normalizeModelEntry(entry) {
48
48
  if (typeof entry.protocol === 'string' && entry.protocol) {
49
49
  out.protocol = entry.protocol;
50
50
  }
51
+ if (entry.supportsEffort === true) out.supportsEffort = true;
52
+ if (typeof entry.thinkingProtocol === 'string' && entry.thinkingProtocol) {
53
+ out.thinkingProtocol = entry.thinkingProtocol;
54
+ }
55
+ const effortOptions = normalizeEffortOptions(entry.effortOptions);
56
+ if (effortOptions) out.effortOptions = effortOptions;
57
+ if (Number.isFinite(entry.maxBudgetTokens) && entry.maxBudgetTokens > 0) {
58
+ out.maxBudgetTokens = entry.maxBudgetTokens;
59
+ }
51
60
  return out;
52
61
  }
53
62
  return null;
@@ -101,7 +110,7 @@ function thinkingV1Enabled() {
101
110
  * @param {object} params
102
111
  * @returns {object} new params object with effort possibly removed
103
112
  */
104
- export function filterEffortForModel(params) {
113
+ export function filterEffortForModel(params, context = {}) {
105
114
  if (!params || !('effort' in params)) return params;
106
115
  if (!thinkingV1Enabled()) {
107
116
  if (params.effortSource !== 'user') {
@@ -115,12 +124,19 @@ export function filterEffortForModel(params) {
115
124
  return rest;
116
125
  }
117
126
  const modelId = parseModelRef(params.model).modelId;
118
- const cap = getThinkingCapability(modelId);
127
+ const effortContext = {
128
+ protocol: context.protocol,
129
+ supportsEffort: context.entry?.supportsEffort,
130
+ effortOptions: context.entry?.effortOptions,
131
+ thinkingProtocol: context.entry?.thinkingProtocol,
132
+ maxBudgetTokens: context.entry?.maxBudgetTokens,
133
+ };
134
+ const cap = getThinkingCapability(modelId, effortContext);
119
135
  if (!cap.supportsThinking || cap.thinkingProtocol === 'none') {
120
136
  const { effort: _drop, effortSource: _source, ...rest } = params;
121
137
  return rest;
122
138
  }
123
- if (!getModelEffortOptions(modelId).includes(norm)) {
139
+ if (!getModelEffortOptions(modelId, effortContext).includes(norm)) {
124
140
  const { effort: _drop, effortSource: _source, ...rest } = params;
125
141
  return rest;
126
142
  }
@@ -429,7 +445,7 @@ export class AdapterRouter extends LLMAdapter {
429
445
  const authModeKey = anthropicAuthHeaderMode || 'default';
430
446
  const cacheKey = `${provider.name}::${protocol}::${authModeKey}::${apiKeyFp}`;
431
447
  const cached = this.#adapterCache.get(cacheKey);
432
- if (cached) return { adapter: cached, modelId: entry.id };
448
+ if (cached) return { adapter: cached, modelId: entry.id, protocol, entry };
433
449
 
434
450
  // Token rotation eviction: when a credential provider hands us a NEW
435
451
  // fingerprint for the same (provider, protocol) pair, drop the stale
@@ -467,7 +483,7 @@ export class AdapterRouter extends LLMAdapter {
467
483
  }
468
484
 
469
485
  this.#adapterCache.set(cacheKey, adapter);
470
- return { adapter, modelId: entry.id };
486
+ return { adapter, modelId: entry.id, protocol, entry };
471
487
  }
472
488
 
473
489
  /**
@@ -527,10 +543,10 @@ export class AdapterRouter extends LLMAdapter {
527
543
  * @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
528
544
  */
529
545
  async *stream(params) {
530
- const filtered = filterEffortForModel(params);
546
+ const resolved = await this.#resolveAdapter(params.model);
547
+ const filtered = filterEffortForModel({ ...params, model: resolved.modelId }, resolved);
531
548
  const sanitized = sanitizeMessagesForWire(filtered);
532
- const { adapter, modelId } = await this.#resolveAdapter(sanitized.model);
533
- yield* adapter.stream({ ...sanitized, model: modelId });
549
+ yield* resolved.adapter.stream({ ...sanitized, model: resolved.modelId });
534
550
  }
535
551
 
536
552
  /**
@@ -540,10 +556,10 @@ export class AdapterRouter extends LLMAdapter {
540
556
  * @returns {Promise<{ text: string, usage: { inputTokens: number, outputTokens: number } }>}
541
557
  */
542
558
  async call(params) {
543
- const filtered = filterEffortForModel(params);
559
+ const resolved = await this.#resolveAdapter(params.model);
560
+ const filtered = filterEffortForModel({ ...params, model: resolved.modelId }, resolved);
544
561
  const sanitized = sanitizeMessagesForWire(filtered);
545
- const { adapter, modelId } = await this.#resolveAdapter(sanitized.model);
546
- return adapter.call({ ...sanitized, model: modelId });
562
+ return resolved.adapter.call({ ...sanitized, model: resolved.modelId });
547
563
  }
548
564
 
549
565
  /**
package/yeaft/models.js CHANGED
@@ -436,6 +436,20 @@ export const ANTHROPIC_ADAPTIVE_MAX_EFFORT_OPTIONS = ['low', 'medium', 'high', '
436
436
  // keep the user-facing scale to the three levels the user expects.
437
437
  export const DEEPSEEK_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high'];
438
438
 
439
+ const VALID_EFFORT_OPTIONS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
440
+
441
+ export function normalizeEffortOptions(options) {
442
+ if (!Array.isArray(options)) return null;
443
+ const out = [];
444
+ for (const raw of options) {
445
+ const effort = normalizeEffort(raw);
446
+ if (effort && VALID_EFFORT_OPTIONS.has(effort) && !out.includes(effort)) {
447
+ out.push(effort);
448
+ }
449
+ }
450
+ return out.length ? out : null;
451
+ }
452
+
439
453
  function inferThinkingCapability(model) {
440
454
  const id = parseModelRef(model).modelId.toLowerCase();
441
455
  if (!id) return null;
@@ -475,11 +489,10 @@ function inferThinkingCapability(model) {
475
489
  return { supportsThinking: true, thinkingProtocol: 'anthropic', defaultEffort: null, maxBudgetTokens };
476
490
  }
477
491
 
478
- // DeepSeek reasoning models expose a thinking effort hint. Only the reasoner
479
- // family (deepseek-reasoner / deepseek-r1*) is a reasoning model plain
480
- // deepseek-chat stays effort-less. Effort travels over the openai-reasoning
481
- // `reasoning.effort` path; user-facing scale is low/medium/high.
482
- if (/^deepseek-(reasoner|r1)/.test(id)) {
492
+ // DeepSeek providers/proxies commonly expose reasoning controls through an
493
+ // OpenAI-Responses-compatible surface. The router/config layer still gates
494
+ // this by effective protocol; direct adapter calls use this family fallback.
495
+ if (/^deepseek/.test(id)) {
483
496
  return {
484
497
  supportsThinking: true,
485
498
  thinkingProtocol: 'openai-reasoning',
@@ -522,10 +535,33 @@ export function thinkingBudgetForEffort(model, effort) {
522
535
  * @param {string} model
523
536
  * @returns {{ supportsThinking: boolean, thinkingProtocol: 'anthropic' | 'anthropic-adaptive' | 'openai-reasoning' | 'none', defaultEffort: Effort | null, maxBudgetTokens: number | null, effortOptions: Effort[] }}
524
537
  */
525
- export function getThinkingCapability(model) {
538
+ export function getThinkingCapability(model, context = {}) {
526
539
  const info = MODEL_REGISTRY.get(model);
540
+ const overrideOptions = normalizeEffortOptions(context.effortOptions);
541
+ const overrideProtocol = context.thinkingProtocol || (context.protocol === 'anthropic' ? 'anthropic' : context.protocol === 'openai-responses' ? 'openai-reasoning' : null);
542
+ if (context.supportsEffort === true || overrideOptions) {
543
+ return {
544
+ supportsThinking: true,
545
+ thinkingProtocol: overrideProtocol || 'openai-reasoning',
546
+ defaultEffort: context.defaultEffort ?? null,
547
+ maxBudgetTokens: context.maxBudgetTokens ?? null,
548
+ effortOptions: overrideOptions,
549
+ };
550
+ }
527
551
  const hasExplicitThinking = info && Object.prototype.hasOwnProperty.call(info, 'supportsThinking');
528
- const inferred = hasExplicitThinking ? null : inferThinkingCapability(model);
552
+ let inferred = hasExplicitThinking ? null : inferThinkingCapability(model);
553
+ if (context.protocol === 'openai-responses' && !inferred && /^deepseek/i.test(parseModelRef(model).modelId)) {
554
+ inferred = {
555
+ supportsThinking: true,
556
+ thinkingProtocol: 'openai-reasoning',
557
+ defaultEffort: null,
558
+ maxBudgetTokens: null,
559
+ effortOptions: DEEPSEEK_REASONING_EFFORT_OPTIONS,
560
+ };
561
+ }
562
+ if (context.protocol === 'anthropic' && inferred?.thinkingProtocol === 'openai-reasoning') {
563
+ inferred = null;
564
+ }
529
565
  if ((!info || !info.supportsThinking) && !inferred) {
530
566
  return {
531
567
  supportsThinking: false,
@@ -544,8 +580,8 @@ export function getThinkingCapability(model) {
544
580
  };
545
581
  }
546
582
 
547
- export function getModelEffortOptions(model) {
548
- const cap = getThinkingCapability(model);
583
+ export function getModelEffortOptions(model, context = {}) {
584
+ const cap = getThinkingCapability(model, context);
549
585
  if (!cap.supportsThinking || cap.thinkingProtocol === 'none') return [];
550
586
  if (Array.isArray(cap.effortOptions)) return cap.effortOptions.slice();
551
587
  if (cap.thinkingProtocol === 'openai-reasoning') return OPENAI_REASONING_EFFORT_OPTIONS.slice();
@@ -554,8 +590,8 @@ export function getModelEffortOptions(model) {
554
590
  return [];
555
591
  }
556
592
 
557
- export function modelSupportsEffort(model) {
558
- return getModelEffortOptions(model).length > 0;
593
+ export function modelSupportsEffort(model, context = {}) {
594
+ return getModelEffortOptions(model, context).length > 0;
559
595
  }
560
596
 
561
597
  /**
@@ -613,6 +649,14 @@ export function normalizeProviderModels(provider) {
613
649
  if (typeof entry.protocol === 'string' && entry.protocol.trim()) {
614
650
  norm.protocol = entry.protocol.trim();
615
651
  }
652
+ if (entry.supportsEffort === true) norm.supportsEffort = true;
653
+ if (typeof entry.thinkingProtocol === 'string' && entry.thinkingProtocol.trim()) {
654
+ norm.thinkingProtocol = entry.thinkingProtocol.trim();
655
+ }
656
+ const effortOptions = normalizeEffortOptions(entry.effortOptions);
657
+ if (effortOptions) norm.effortOptions = effortOptions;
658
+ const maxBudget = coercePositiveInt(entry.maxBudgetTokens);
659
+ if (maxBudget !== undefined) norm.maxBudgetTokens = maxBudget;
616
660
  out.push(norm);
617
661
  }
618
662
  // silently skip anything else (null / missing id / numbers)