@yeaft/webchat-agent 0.1.997 → 0.1.999
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 +7 -1
- package/yeaft/dream/segment-extract.js +2 -2
- package/yeaft/engine.js +3 -1
- package/yeaft/llm/anthropic.js +5 -5
- package/yeaft/llm/openai-responses.js +5 -5
- package/yeaft/llm/router.js +7 -5
- package/yeaft/models.js +38 -5
- package/yeaft/prompts.js +4 -7
- package/yeaft/sessions/session-config.js +14 -2
- package/yeaft/templates/identity-yeaft.md +1 -1
- package/yeaft/templates/mode-unified.md +1 -1
- package/yeaft/web-bridge.js +14 -1
package/package.json
CHANGED
package/yeaft/config.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import { existsSync, readFileSync } from 'fs';
|
|
23
23
|
import { join } from 'path';
|
|
24
24
|
import { DEFAULT_YEAFT_DIR } from './init.js';
|
|
25
|
-
import { resolveModel, parseModelRef, normalizeProviderModels, resolveContextWindow, resolveMaxOutputTokens } from './models.js';
|
|
25
|
+
import { getModelEffortOptions, modelSupportsEffort, resolveModel, parseModelRef, normalizeProviderModels, resolveContextWindow, resolveMaxOutputTokens } from './models.js';
|
|
26
26
|
import { normalizeKnownProviderForRuntime } from './llm/known-providers.js';
|
|
27
27
|
|
|
28
28
|
/** Default configuration values. */
|
|
@@ -394,11 +394,17 @@ export function loadConfig(overrides = {}) {
|
|
|
394
394
|
if (!config.availableModels.some(am => am.id === m.id)) {
|
|
395
395
|
const entry = {
|
|
396
396
|
id: m.id,
|
|
397
|
+
ref: p.name ? `${p.name}/${m.id}` : m.id,
|
|
397
398
|
provider: p.name,
|
|
398
399
|
label: m.id,
|
|
399
400
|
};
|
|
400
401
|
if (m.contextWindow !== undefined) entry.contextWindow = m.contextWindow;
|
|
401
402
|
if (m.maxOutput !== undefined) entry.maxOutput = m.maxOutput;
|
|
403
|
+
const effortOptions = getModelEffortOptions(m.id);
|
|
404
|
+
if (effortOptions.length > 0) {
|
|
405
|
+
entry.supportsEffort = modelSupportsEffort(m.id);
|
|
406
|
+
entry.effortOptions = effortOptions;
|
|
407
|
+
}
|
|
402
408
|
config.availableModels.push(entry);
|
|
403
409
|
}
|
|
404
410
|
}
|
|
@@ -303,6 +303,6 @@ function oneLine(text) {
|
|
|
303
303
|
|
|
304
304
|
function extractSystem(language) {
|
|
305
305
|
return String(language || '').toLowerCase().startsWith('zh')
|
|
306
|
-
? '
|
|
307
|
-
: 'You are the
|
|
306
|
+
? '你是梦境记忆抽取器。只输出严格 JSON 数组,不要 Markdown。保留具体事实、决策、偏好、当前状态和证据 message id。'
|
|
307
|
+
: 'You are the dream memory extractor. Return only a strict JSON array, no Markdown. Preserve concrete facts, decisions, preferences, current status, and evidence message ids.';
|
|
308
308
|
}
|
package/yeaft/engine.js
CHANGED
|
@@ -1372,7 +1372,8 @@ export class Engine {
|
|
|
1372
1372
|
// valid prompt prefix.
|
|
1373
1373
|
const parsed = parseEffortPrefix(prompt);
|
|
1374
1374
|
const effectivePrompt = parsed.cleanedPrompt;
|
|
1375
|
-
const
|
|
1375
|
+
const configuredEffort = normalizeEffort(this.#config?.modelEffort);
|
|
1376
|
+
const effectiveUserEffort = normalizeEffort(userEffort) || parsed.effort || configuredEffort || null;
|
|
1376
1377
|
const effectiveCollabToolPolicy = collabToolPolicy === COLLAB_TOOL_POLICY.SINGLE_VP || collabToolPolicy === COLLAB_TOOL_POLICY.MULTI_VP
|
|
1377
1378
|
? collabToolPolicy
|
|
1378
1379
|
: null;
|
|
@@ -1957,6 +1958,7 @@ export class Engine {
|
|
|
1957
1958
|
tools: toolDefs.length > 0 ? toolDefs : undefined,
|
|
1958
1959
|
maxTokens: this.#config.maxOutputTokens || 16384,
|
|
1959
1960
|
effort: resolvedEffort,
|
|
1961
|
+
effortSource: userEffort ? 'user' : 'auto',
|
|
1960
1962
|
signal,
|
|
1961
1963
|
onRawExchange: captureRawExchange,
|
|
1962
1964
|
})) {
|
package/yeaft/llm/anthropic.js
CHANGED
|
@@ -156,10 +156,10 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'max', signal?: AbortSignal }} params
|
|
159
|
+
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'max', effortSource?: 'user'|'auto', signal?: AbortSignal }} params
|
|
160
160
|
* @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
|
|
161
161
|
*/
|
|
162
|
-
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, signal, onRawExchange }) {
|
|
162
|
+
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, effortSource, signal, onRawExchange }) {
|
|
163
163
|
if (signal?.aborted) throw new LLMAbortError();
|
|
164
164
|
|
|
165
165
|
const body = {
|
|
@@ -175,7 +175,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
175
175
|
// 'anthropic' thinking protocol. Unknown models or non-thinking models
|
|
176
176
|
// silently drop the parameter — red line: never error on unsupported.
|
|
177
177
|
const normEffort = normalizeEffort(effort);
|
|
178
|
-
if (thinkingV1Enabled() && normEffort) {
|
|
178
|
+
if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
|
|
179
179
|
const cap = getThinkingCapability(model);
|
|
180
180
|
if (cap.supportsThinking && cap.thinkingProtocol === 'anthropic') {
|
|
181
181
|
const budget = thinkingBudgetForEffort(model, normEffort);
|
|
@@ -427,7 +427,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
427
427
|
* models silently drop the param. max_tokens auto-widens to budget+1024
|
|
428
428
|
* when needed.
|
|
429
429
|
*/
|
|
430
|
-
async call({ model, system, messages, maxTokens = 4096, effort, signal }) {
|
|
430
|
+
async call({ model, system, messages, maxTokens = 4096, effort, effortSource, signal }) {
|
|
431
431
|
if (signal?.aborted) throw new LLMAbortError();
|
|
432
432
|
|
|
433
433
|
const body = {
|
|
@@ -439,7 +439,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
439
439
|
|
|
440
440
|
// task-327c: mirror stream()'s thinking injection for side queries.
|
|
441
441
|
const normEffort = normalizeEffort(effort);
|
|
442
|
-
if (thinkingV1Enabled() && normEffort) {
|
|
442
|
+
if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
|
|
443
443
|
const cap = getThinkingCapability(model);
|
|
444
444
|
if (cap.supportsThinking && cap.thinkingProtocol === 'anthropic') {
|
|
445
445
|
const budget = thinkingBudgetForEffort(model, normEffort);
|
|
@@ -230,7 +230,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
230
230
|
// ─── Streaming ──────────────────────────────────────────
|
|
231
231
|
|
|
232
232
|
/**
|
|
233
|
-
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'max', extraBody?: object, signal?: AbortSignal, onRawExchange?: ({rawRequest, rawResponse}) => void }} params
|
|
233
|
+
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'max', effortSource?: 'user'|'auto', extraBody?: object, signal?: AbortSignal, onRawExchange?: ({rawRequest, rawResponse}) => void }} params
|
|
234
234
|
*
|
|
235
235
|
* NOTE on `extraBody`: any keys you spread here are merged verbatim into
|
|
236
236
|
* the wire body and — because the verbatim debug feature is intentionally
|
|
@@ -239,7 +239,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
239
239
|
* `api-key` headers are auto-redacted (see `redactRawRequest` in
|
|
240
240
|
* `adapter.js`); request-body fields are caller-controlled.
|
|
241
241
|
*/
|
|
242
|
-
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, extraBody, signal, onRawExchange }) {
|
|
242
|
+
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, effortSource, extraBody, signal, onRawExchange }) {
|
|
243
243
|
if (signal?.aborted) throw new LLMAbortError();
|
|
244
244
|
|
|
245
245
|
const body = {
|
|
@@ -257,7 +257,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
257
257
|
// registry entry must declare thinkingProtocol === 'openai-reasoning'.
|
|
258
258
|
// Unknown / unsupported models silently drop the field.
|
|
259
259
|
const normEffort = normalizeEffort(effort);
|
|
260
|
-
if (thinkingV1Enabled() && normEffort) {
|
|
260
|
+
if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
|
|
261
261
|
const cap = getThinkingCapability(model);
|
|
262
262
|
if (cap.supportsThinking && cap.thinkingProtocol === 'openai-reasoning') {
|
|
263
263
|
const wireEffort = effortForResponses(normEffort);
|
|
@@ -478,7 +478,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
478
478
|
* expose them, mirror the stream() instrumentation. Parity with
|
|
479
479
|
* anthropic.js's `call()`.
|
|
480
480
|
*/
|
|
481
|
-
async call({ model, system, messages, maxTokens = 4096, effort, extraBody, signal }) {
|
|
481
|
+
async call({ model, system, messages, maxTokens = 4096, effort, effortSource, extraBody, signal }) {
|
|
482
482
|
if (signal?.aborted) throw new LLMAbortError();
|
|
483
483
|
|
|
484
484
|
const body = {
|
|
@@ -490,7 +490,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
490
490
|
|
|
491
491
|
// Mirror stream()'s thinking injection for non-streaming side queries.
|
|
492
492
|
const normEffort = normalizeEffort(effort);
|
|
493
|
-
if (thinkingV1Enabled() && normEffort) {
|
|
493
|
+
if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
|
|
494
494
|
const cap = getThinkingCapability(model);
|
|
495
495
|
if (cap.supportsThinking && cap.thinkingProtocol === 'openai-reasoning') {
|
|
496
496
|
const wireEffort = effortForResponses(normEffort);
|
package/yeaft/llm/router.js
CHANGED
|
@@ -99,20 +99,22 @@ function thinkingV1Enabled() {
|
|
|
99
99
|
export function filterEffortForModel(params) {
|
|
100
100
|
if (!params || !('effort' in params)) return params;
|
|
101
101
|
if (!thinkingV1Enabled()) {
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
if (params.effortSource !== 'user') {
|
|
103
|
+
const { effort: _drop, effortSource: _source, ...rest } = params;
|
|
104
|
+
return rest;
|
|
105
|
+
}
|
|
104
106
|
}
|
|
105
107
|
const norm = normalizeEffort(params.effort);
|
|
106
108
|
if (!norm) {
|
|
107
|
-
const { effort: _drop, ...rest } = params;
|
|
109
|
+
const { effort: _drop, effortSource: _source, ...rest } = params;
|
|
108
110
|
return rest;
|
|
109
111
|
}
|
|
110
112
|
const cap = getThinkingCapability(parseModelRef(params.model).modelId);
|
|
111
113
|
if (!cap.supportsThinking || cap.thinkingProtocol === 'none') {
|
|
112
|
-
const { effort: _drop, ...rest } = params;
|
|
114
|
+
const { effort: _drop, effortSource: _source, ...rest } = params;
|
|
113
115
|
return rest;
|
|
114
116
|
}
|
|
115
|
-
return { ...params, effort: norm };
|
|
117
|
+
return { ...params, effort: norm, effortSource: params.effortSource };
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
/**
|
package/yeaft/models.js
CHANGED
|
@@ -411,6 +411,27 @@ export function mapEffortToOpenAIReasoning(effort) {
|
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
export const MODEL_EFFORT_OPTIONS = ['low', 'medium', 'high'];
|
|
415
|
+
|
|
416
|
+
function inferThinkingCapability(model) {
|
|
417
|
+
const id = parseModelRef(model).modelId.toLowerCase();
|
|
418
|
+
if (!id) return null;
|
|
419
|
+
|
|
420
|
+
if (/^(gpt-5|o1|o3|o4|chatgpt-|codex-)/.test(id)) {
|
|
421
|
+
return { supportsThinking: true, thinkingProtocol: 'openai-reasoning', defaultEffort: null, maxBudgetTokens: null };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Anthropic extended thinking is available on Claude 3.7+ and Claude 4.x.
|
|
425
|
+
// Be conservative: older Claude 3/3.5/Haiku entries stay unsupported unless
|
|
426
|
+
// explicitly listed in the registry.
|
|
427
|
+
if (/^claude-/.test(id) && (/(^|-)3-7($|-|\.)/.test(id) || /(^|-)4($|-|\.)/.test(id))) {
|
|
428
|
+
const maxBudgetTokens = id.includes('opus') ? 64000 : 32000;
|
|
429
|
+
return { supportsThinking: true, thinkingProtocol: 'anthropic', defaultEffort: null, maxBudgetTokens };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
414
435
|
/**
|
|
415
436
|
* Resolve the Anthropic thinking budget_tokens value for a given (model, effort).
|
|
416
437
|
*
|
|
@@ -443,7 +464,9 @@ export function thinkingBudgetForEffort(model, effort) {
|
|
|
443
464
|
*/
|
|
444
465
|
export function getThinkingCapability(model) {
|
|
445
466
|
const info = MODEL_REGISTRY.get(model);
|
|
446
|
-
|
|
467
|
+
const hasExplicitThinking = info && Object.prototype.hasOwnProperty.call(info, 'supportsThinking');
|
|
468
|
+
const inferred = hasExplicitThinking ? null : inferThinkingCapability(model);
|
|
469
|
+
if ((!info || !info.supportsThinking) && !inferred) {
|
|
447
470
|
return {
|
|
448
471
|
supportsThinking: false,
|
|
449
472
|
thinkingProtocol: 'none',
|
|
@@ -452,13 +475,23 @@ export function getThinkingCapability(model) {
|
|
|
452
475
|
};
|
|
453
476
|
}
|
|
454
477
|
return {
|
|
455
|
-
supportsThinking:
|
|
456
|
-
thinkingProtocol: info
|
|
457
|
-
defaultEffort: info
|
|
458
|
-
maxBudgetTokens: info
|
|
478
|
+
supportsThinking: Boolean(info?.supportsThinking ?? inferred?.supportsThinking),
|
|
479
|
+
thinkingProtocol: info?.thinkingProtocol || inferred?.thinkingProtocol || 'none',
|
|
480
|
+
defaultEffort: info?.defaultEffort ?? inferred?.defaultEffort ?? null,
|
|
481
|
+
maxBudgetTokens: info?.maxBudgetTokens ?? inferred?.maxBudgetTokens ?? null,
|
|
459
482
|
};
|
|
460
483
|
}
|
|
461
484
|
|
|
485
|
+
export function getModelEffortOptions(model) {
|
|
486
|
+
const cap = getThinkingCapability(model);
|
|
487
|
+
if (!cap.supportsThinking || cap.thinkingProtocol === 'none') return [];
|
|
488
|
+
return MODEL_EFFORT_OPTIONS.slice();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function modelSupportsEffort(model) {
|
|
492
|
+
return getModelEffortOptions(model).length > 0;
|
|
493
|
+
}
|
|
494
|
+
|
|
462
495
|
/**
|
|
463
496
|
* Valid-effort guard. Unknown values → null (caller should treat as "no effort").
|
|
464
497
|
*
|
package/yeaft/prompts.js
CHANGED
|
@@ -191,7 +191,7 @@ export function getDefaultPlanInstruction(language = 'en') {
|
|
|
191
191
|
|
|
192
192
|
const PROMPTS = {
|
|
193
193
|
en: {
|
|
194
|
-
identity: '
|
|
194
|
+
identity: 'No VP soul is active for this turn. Participate in the current session with grounded, evidence-based answers and preserve the user\'s context.',
|
|
195
195
|
date: (d) => `Date: ${d}`,
|
|
196
196
|
dream: 'You are in dream mode. Reflect on past conversations and consolidate memories.',
|
|
197
197
|
tools: (names) => `Available tools: ${names}`,
|
|
@@ -204,11 +204,9 @@ const PROMPTS = {
|
|
|
204
204
|
projectDocHeader: '[Project Doc]',
|
|
205
205
|
projectDocIntro:
|
|
206
206
|
'The user keeps project-level instructions and context in `CLAUDE.md` or `AGENTS.md` at the session working directory. Treat the content below as authoritative project context — coding conventions, task guidance, workflow rules, etc.',
|
|
207
|
-
vpPersonaIntro: (name, role) =>
|
|
208
|
-
`You are ${name}${role ? `, ${role}` : ''}. Think, decide, and respond from ${name}'s perspective. Speak in the first person as ${name}; do not refer to yourself as "Yeaft" or as a generic AI assistant.`,
|
|
209
207
|
},
|
|
210
208
|
zh: {
|
|
211
|
-
identity: '
|
|
209
|
+
identity: '当前回合没有激活 VP soul。你在当前 session 中参与协作,回答要基于证据,并保持用户上下文。',
|
|
212
210
|
date: (d) => `日期:${d}`,
|
|
213
211
|
dream: '你处于梦境模式。回顾过去的对话,整理和巩固记忆。',
|
|
214
212
|
tools: (names) => `可用工具:${names}`,
|
|
@@ -219,8 +217,6 @@ const PROMPTS = {
|
|
|
219
217
|
projectDocHeader: '[项目文档]',
|
|
220
218
|
projectDocIntro:
|
|
221
219
|
'用户把项目级的说明和上下文记录在 session 工作目录下的 `CLAUDE.md` 或 `AGENTS.md` 中。下面的内容是权威的项目上下文 —— 编码规范、任务指导、工作流约定等,请遵循它来工作。',
|
|
222
|
-
vpPersonaIntro: (name, role) =>
|
|
223
|
-
`你是 ${name}${role ? `,${role}` : ''}。请以 ${name} 的思考方式理解问题、判断优先级并回答,并以 ${name} 的第一人称发言;不要自称 "Yeaft" 或泛指的 AI 助手。`,
|
|
224
220
|
},
|
|
225
221
|
};
|
|
226
222
|
|
|
@@ -427,7 +423,8 @@ function renderVpPersona(vpPersona, lang, effectiveLang = 'en') {
|
|
|
427
423
|
// generic assistant identity here: the VP soul body is the source of truth.
|
|
428
424
|
// `role` is intentionally not rendered as a second identity line; stock VPs
|
|
429
425
|
// carry bilingual, role-aware soul text in role.md.
|
|
430
|
-
const
|
|
426
|
+
const soulHeading = effectiveLang === 'zh' ? '## 灵魂' : '## Soul';
|
|
427
|
+
const lines = [`# ${name}`, '', soulHeading];
|
|
431
428
|
if (body) lines.push('', body);
|
|
432
429
|
return lines.join('\n');
|
|
433
430
|
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* v1 schema (intentionally tiny — extend via additive keys only):
|
|
8
8
|
* {
|
|
9
|
-
* "model": "my-proxy/claude-sonnet-4-20250514"
|
|
9
|
+
* "model": "my-proxy/claude-sonnet-4-20250514", // optional
|
|
10
|
+
* "modelEffort": "high" // optional
|
|
10
11
|
* }
|
|
11
12
|
*
|
|
12
13
|
* Missing file or `{}` → no session-level override. Missing field → fall back to user-level
|
|
@@ -26,7 +27,8 @@ import { sessionsRoot, resolveSessionYeaftDir } from './session-crud.js';
|
|
|
26
27
|
const CONFIG_FILE = 'config.json';
|
|
27
28
|
|
|
28
29
|
/** Whitelist of persisted session model-override fields. Reject everything else. */
|
|
29
|
-
const ALLOWED_KEYS = new Set(['model']);
|
|
30
|
+
const ALLOWED_KEYS = new Set(['model', 'modelEffort']);
|
|
31
|
+
const ALLOWED_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
30
32
|
|
|
31
33
|
export class SessionConfigError extends Error {
|
|
32
34
|
constructor(code, message) {
|
|
@@ -97,6 +99,11 @@ export function validateSessionConfig(cfg) {
|
|
|
97
99
|
throw new SessionConfigError('invalid_model', 'model must be a non-empty string');
|
|
98
100
|
}
|
|
99
101
|
}
|
|
102
|
+
if ('modelEffort' in cfg && cfg.modelEffort !== null && cfg.modelEffort !== undefined && cfg.modelEffort !== '') {
|
|
103
|
+
if (typeof cfg.modelEffort !== 'string' || !ALLOWED_EFFORTS.has(cfg.modelEffort.trim())) {
|
|
104
|
+
throw new SessionConfigError('invalid_model_effort', 'modelEffort must be low, medium, or high');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
/**
|
|
@@ -163,5 +170,10 @@ export function resolveSessionConfig(userConfig, sessionConfig) {
|
|
|
163
170
|
base.model = model;
|
|
164
171
|
base.primaryModel = model;
|
|
165
172
|
}
|
|
173
|
+
if (overrides.modelEffort && typeof overrides.modelEffort === 'string' && ALLOWED_EFFORTS.has(overrides.modelEffort)) {
|
|
174
|
+
base.modelEffort = overrides.modelEffort;
|
|
175
|
+
} else {
|
|
176
|
+
delete base.modelEffort;
|
|
177
|
+
}
|
|
166
178
|
return base;
|
|
167
179
|
}
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -2796,6 +2796,7 @@ async function ensureSessionLoaded() {
|
|
|
2796
2796
|
type: 'session_ready',
|
|
2797
2797
|
conversationId: yeaftConversationId,
|
|
2798
2798
|
model: session.config.model,
|
|
2799
|
+
modelEffort: session.config.modelEffort || null,
|
|
2799
2800
|
availableModels: session.config.availableModels || [],
|
|
2800
2801
|
skills: session.status.skills,
|
|
2801
2802
|
mcpServers: session.status.mcpServers,
|
|
@@ -3920,23 +3921,34 @@ export function handleYeaftModeSwitch(_msg) {
|
|
|
3920
3921
|
}
|
|
3921
3922
|
|
|
3922
3923
|
|
|
3924
|
+
|
|
3925
|
+
export function modelRefMatchesAvailable(model, requested) {
|
|
3926
|
+
if (!model || !requested) return false;
|
|
3927
|
+
return model.id === requested
|
|
3928
|
+
|| model.ref === requested
|
|
3929
|
+
|| (model.provider && model.id && `${model.provider}/${model.id}` === requested);
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3923
3932
|
/** Handle model switch from the web UI. */
|
|
3924
3933
|
export function handleYeaftModelSwitch(msg) {
|
|
3925
3934
|
if (!session || !msg.model) return;
|
|
3926
3935
|
refreshLiveSessionConfig();
|
|
3927
3936
|
|
|
3928
3937
|
const available = session.config.availableModels || [];
|
|
3929
|
-
const found = available.some(m => m
|
|
3938
|
+
const found = available.some(m => modelRefMatchesAvailable(m, msg.model));
|
|
3930
3939
|
if (!found) {
|
|
3931
3940
|
console.warn(`[Yeaft] model switch rejected — "${msg.model}" not in availableModels`);
|
|
3932
3941
|
return;
|
|
3933
3942
|
}
|
|
3934
3943
|
|
|
3935
3944
|
session.config.model = msg.model;
|
|
3945
|
+
session.config.primaryModel = msg.model;
|
|
3946
|
+
session.config.modelEffort = msg.modelEffort || null;
|
|
3936
3947
|
|
|
3937
3948
|
sendSessionEvent({
|
|
3938
3949
|
type: 'model_switched',
|
|
3939
3950
|
model: msg.model,
|
|
3951
|
+
modelEffort: session.config.modelEffort || null,
|
|
3940
3952
|
});
|
|
3941
3953
|
}
|
|
3942
3954
|
|
|
@@ -3996,6 +4008,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3996
4008
|
type: 'session_ready',
|
|
3997
4009
|
conversationId: yeaftConversationId,
|
|
3998
4010
|
model: session.config.model,
|
|
4011
|
+
modelEffort: session.config.modelEffort || null,
|
|
3999
4012
|
availableModels: session.config.availableModels || [],
|
|
4000
4013
|
skills: session.status.skills,
|
|
4001
4014
|
mcpServers: session.status.mcpServers,
|