@yeaft/webchat-agent 0.1.1033 → 0.1.1034
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/config.js +12 -3
- package/yeaft/llm/router.js +28 -12
- package/yeaft/models.js +55 -11
package/package.json
CHANGED
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
|
|
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
|
}
|
package/yeaft/llm/router.js
CHANGED
|
@@ -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
|
|
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
|
|
546
|
+
const resolved = await this.#resolveAdapter(params.model);
|
|
547
|
+
const filtered = filterEffortForModel({ ...params, model: resolved.modelId }, resolved);
|
|
531
548
|
const sanitized = sanitizeMessagesForWire(filtered);
|
|
532
|
-
|
|
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
|
|
559
|
+
const resolved = await this.#resolveAdapter(params.model);
|
|
560
|
+
const filtered = filterEffortForModel({ ...params, model: resolved.modelId }, resolved);
|
|
544
561
|
const sanitized = sanitizeMessagesForWire(filtered);
|
|
545
|
-
|
|
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
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
|
|
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
|
-
|
|
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)
|