@yeaft/webchat-agent 0.1.1004 → 0.1.1006
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 +3 -1
- package/yeaft/effort.js +5 -5
- package/yeaft/llm/anthropic.js +29 -26
- package/yeaft/llm/openai-responses.js +6 -7
- package/yeaft/llm/router.js +1 -1
- package/yeaft/models.js +44 -15
- package/yeaft/prompts.js +42 -2
- package/yeaft/sessions/session-config.js +2 -2
- package/yeaft/tools/route-forward.js +10 -5
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 { getModelEffortOptions, modelSupportsEffort, resolveModel, parseModelRef, normalizeProviderModels, resolveContextWindow, resolveMaxOutputTokens } from './models.js';
|
|
25
|
+
import { getModelEffortOptions, getThinkingCapability, 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. */
|
|
@@ -402,8 +402,10 @@ export function loadConfig(overrides = {}) {
|
|
|
402
402
|
if (m.maxOutput !== undefined) entry.maxOutput = m.maxOutput;
|
|
403
403
|
const effortOptions = getModelEffortOptions(m.id);
|
|
404
404
|
if (effortOptions.length > 0) {
|
|
405
|
+
const cap = getThinkingCapability(m.id);
|
|
405
406
|
entry.supportsEffort = modelSupportsEffort(m.id);
|
|
406
407
|
entry.effortOptions = effortOptions;
|
|
408
|
+
entry.effortProtocol = cap.thinkingProtocol;
|
|
407
409
|
}
|
|
408
410
|
config.availableModels.push(entry);
|
|
409
411
|
}
|
package/yeaft/effort.js
CHANGED
|
@@ -61,7 +61,7 @@ export const SCENARIO_EFFORT = Object.freeze({
|
|
|
61
61
|
* Pick the effort level for a given query context.
|
|
62
62
|
*
|
|
63
63
|
* Decision order:
|
|
64
|
-
* 1. If userEffort is a valid Effort ('minimal'|'low'|'medium'|'high'|'max'),
|
|
64
|
+
* 1. If userEffort is a valid Effort ('minimal'|'low'|'medium'|'high'|'xhigh'|'max'),
|
|
65
65
|
* return it unchanged. This is the explicit override path —
|
|
66
66
|
* `/max` prefix, Settings slider, or API caller.
|
|
67
67
|
* 2. If toolLoopTurns >= LONG_LOOP_TURN_THRESHOLD, upgrade the
|
|
@@ -74,7 +74,7 @@ export const SCENARIO_EFFORT = Object.freeze({
|
|
|
74
74
|
* already consumed in the current `query()` call.
|
|
75
75
|
* @param {unknown} [ctx.userEffort=null] — User-supplied override.
|
|
76
76
|
* Invalid values are ignored (fall through to scenario path).
|
|
77
|
-
* @returns {'minimal'|'low'|'medium'|'high'|'max'} Resolved effort. Never null —
|
|
77
|
+
* @returns {'minimal'|'low'|'medium'|'high'|'xhigh'|'max'} Resolved effort. Never null —
|
|
78
78
|
* the adapter/router is responsible for dropping it when the
|
|
79
79
|
* feature flag is off or the model doesn't support thinking.
|
|
80
80
|
*/
|
|
@@ -97,7 +97,7 @@ export function pickEffort({ scenario = 'chat', toolLoopTurns = 0, userEffort =
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
* Parse a user prompt for `/max`, `/high`, `/medium`, `/low` prefix
|
|
100
|
+
* Parse a user prompt for `/max`, `/xhigh`, `/high`, `/medium`, `/low` prefix
|
|
101
101
|
* commands. Returns `{ effort, cleanedPrompt }` where cleanedPrompt has
|
|
102
102
|
* the prefix (plus one trailing space) stripped.
|
|
103
103
|
*
|
|
@@ -108,11 +108,11 @@ export function pickEffort({ scenario = 'chat', toolLoopTurns = 0, userEffort =
|
|
|
108
108
|
* via `!` or `/skill:` instead to avoid collision.
|
|
109
109
|
*
|
|
110
110
|
* @param {string} prompt
|
|
111
|
-
* @returns {{ effort: 'low'|'medium'|'high'|'max'|null, cleanedPrompt: string }}
|
|
111
|
+
* @returns {{ effort: 'low'|'medium'|'high'|'xhigh'|'max'|null, cleanedPrompt: string }}
|
|
112
112
|
*/
|
|
113
113
|
export function parseEffortPrefix(prompt) {
|
|
114
114
|
if (typeof prompt !== 'string') return { effort: null, cleanedPrompt: prompt };
|
|
115
|
-
const m = prompt.match(/^\/(max|high|medium|low)(\s+|$)/);
|
|
115
|
+
const m = prompt.match(/^\/(max|xhigh|high|medium|low)(\s+|$)/);
|
|
116
116
|
if (!m) return { effort: null, cleanedPrompt: prompt };
|
|
117
117
|
const effort = m[1];
|
|
118
118
|
const cleanedPrompt = prompt.slice(m[0].length);
|
package/yeaft/llm/anthropic.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
normalizeEffort,
|
|
21
21
|
thinkingBudgetForEffort,
|
|
22
22
|
getThinkingCapability,
|
|
23
|
+
getModelEffortOptions,
|
|
23
24
|
} from '../models.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -30,6 +31,28 @@ function thinkingV1Enabled() {
|
|
|
30
31
|
return process.env.YEAFT_THINKING_V1 === '1';
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
function applyAnthropicThinking(body, model, effort) {
|
|
35
|
+
const cap = getThinkingCapability(model);
|
|
36
|
+
if (!cap.supportsThinking) return;
|
|
37
|
+
if (!getModelEffortOptions(model).includes(effort)) return;
|
|
38
|
+
|
|
39
|
+
if (cap.thinkingProtocol === 'anthropic-adaptive') {
|
|
40
|
+
body.thinking = { type: 'adaptive' };
|
|
41
|
+
body.output_config = { ...(body.output_config || {}), effort };
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (cap.thinkingProtocol === 'anthropic') {
|
|
46
|
+
const budget = thinkingBudgetForEffort(model, effort);
|
|
47
|
+
if (budget && budget > 0) {
|
|
48
|
+
// Anthropic manual thinking requires max_tokens > budget_tokens.
|
|
49
|
+
const minMax = budget + 1024;
|
|
50
|
+
if (body.max_tokens < minMax) body.max_tokens = minMax;
|
|
51
|
+
body.thinking = { type: 'enabled', budget_tokens: budget };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
const DEFAULT_BASE_URL = 'https://api.anthropic.com';
|
|
34
57
|
const API_VERSION = '2023-06-01';
|
|
35
58
|
|
|
@@ -156,7 +179,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
156
179
|
}
|
|
157
180
|
|
|
158
181
|
/**
|
|
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
|
|
182
|
+
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'low'|'medium'|'high'|'xhigh'|'max', effortSource?: 'user'|'auto', signal?: AbortSignal }} params
|
|
160
183
|
* @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
|
|
161
184
|
*/
|
|
162
185
|
async *stream({ model, system, messages, tools, maxTokens = 16384, effort, effortSource, signal, onRawExchange }) {
|
|
@@ -170,24 +193,12 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
170
193
|
stream: true,
|
|
171
194
|
};
|
|
172
195
|
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
// silently drop the parameter — red line: never error on unsupported.
|
|
196
|
+
// Inject Anthropic thinking only for model-supported effort values.
|
|
197
|
+
// Adaptive Claude 4.7/4.8 uses output_config.effort; older manual-thinking
|
|
198
|
+
// models use budget_tokens. Unsupported combinations silently drop effort.
|
|
177
199
|
const normEffort = normalizeEffort(effort);
|
|
178
200
|
if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
|
|
179
|
-
|
|
180
|
-
if (cap.supportsThinking && cap.thinkingProtocol === 'anthropic') {
|
|
181
|
-
const budget = thinkingBudgetForEffort(model, normEffort);
|
|
182
|
-
if (budget && budget > 0) {
|
|
183
|
-
// Anthropic requires max_tokens > budget_tokens. Widen max_tokens
|
|
184
|
-
// if the caller's value is too small to fit the thinking budget
|
|
185
|
-
// plus a sane reply margin (1024 tokens).
|
|
186
|
-
const minMax = budget + 1024;
|
|
187
|
-
if (body.max_tokens < minMax) body.max_tokens = minMax;
|
|
188
|
-
body.thinking = { type: 'enabled', budget_tokens: budget };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
201
|
+
applyAnthropicThinking(body, model, normEffort);
|
|
191
202
|
}
|
|
192
203
|
|
|
193
204
|
const translatedTools = this.#translateTools(tools);
|
|
@@ -440,15 +451,7 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
440
451
|
// task-327c: mirror stream()'s thinking injection for side queries.
|
|
441
452
|
const normEffort = normalizeEffort(effort);
|
|
442
453
|
if ((thinkingV1Enabled() || effortSource === 'user') && normEffort) {
|
|
443
|
-
|
|
444
|
-
if (cap.supportsThinking && cap.thinkingProtocol === 'anthropic') {
|
|
445
|
-
const budget = thinkingBudgetForEffort(model, normEffort);
|
|
446
|
-
if (budget && budget > 0) {
|
|
447
|
-
const minMax = budget + 1024;
|
|
448
|
-
if (body.max_tokens < minMax) body.max_tokens = minMax;
|
|
449
|
-
body.thinking = { type: 'enabled', budget_tokens: budget };
|
|
450
|
-
}
|
|
451
|
-
}
|
|
454
|
+
applyAnthropicThinking(body, model, normEffort);
|
|
452
455
|
}
|
|
453
456
|
|
|
454
457
|
const response = await fetch(`${this.#baseUrl}/v1/messages`, {
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import {
|
|
39
39
|
normalizeEffort,
|
|
40
40
|
getThinkingCapability,
|
|
41
|
+
mapEffortToOpenAIReasoning,
|
|
41
42
|
} from '../models.js';
|
|
42
43
|
|
|
43
44
|
const DEFAULT_BASE_URL = 'https://api.openai.com/v1';
|
|
@@ -52,15 +53,13 @@ function thinkingV1Enabled() {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
|
-
* Translate a normalised effort ('minimal'|'low'|'medium'|'high'|'max') into the value
|
|
56
|
+
* Translate a normalised effort ('minimal'|'low'|'medium'|'high'|'xhigh'|'max') into the value
|
|
56
57
|
* accepted by the OpenAI Responses `reasoning.effort` field. Responses today
|
|
57
|
-
* accepts 'minimal'|'low'|'medium'|'high'
|
|
58
|
-
*
|
|
58
|
+
* accepts 'minimal'|'low'|'medium'|'high'. Unsupported Anthropic-only
|
|
59
|
+
* adaptive efforts must be dropped, not downgraded.
|
|
59
60
|
*/
|
|
60
61
|
function effortForResponses(effort) {
|
|
61
|
-
|
|
62
|
-
if (effort === 'max') return 'high';
|
|
63
|
-
return effort;
|
|
62
|
+
return mapEffortToOpenAIReasoning(effort);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
@@ -230,7 +229,7 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
230
229
|
// ─── Streaming ──────────────────────────────────────────
|
|
231
230
|
|
|
232
231
|
/**
|
|
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
|
|
232
|
+
* @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, effort?: 'minimal'|'low'|'medium'|'high'|'xhigh'|'max', effortSource?: 'user'|'auto', extraBody?: object, signal?: AbortSignal, onRawExchange?: ({rawRequest, rawResponse}) => void }} params
|
|
234
233
|
*
|
|
235
234
|
* NOTE on `extraBody`: any keys you spread here are merged verbatim into
|
|
236
235
|
* the wire body and — because the verbatim debug feature is intentionally
|
package/yeaft/llm/router.js
CHANGED
|
@@ -115,7 +115,7 @@ export function filterEffortForModel(params) {
|
|
|
115
115
|
const { effort: _drop, effortSource: _source, ...rest } = params;
|
|
116
116
|
return rest;
|
|
117
117
|
}
|
|
118
|
-
if (
|
|
118
|
+
if (!getModelEffortOptions(modelId).includes(norm)) {
|
|
119
119
|
const { effort: _drop, effortSource: _source, ...rest } = params;
|
|
120
120
|
return rest;
|
|
121
121
|
}
|
package/yeaft/models.js
CHANGED
|
@@ -31,9 +31,10 @@ import { lookupModelLimitSync } from './llm/models-dev.js';
|
|
|
31
31
|
* @property {string} baseUrl — Official API endpoint base URL
|
|
32
32
|
* @property {string} displayName — Human-readable model name
|
|
33
33
|
* @property {boolean} [supportsThinking] — task-327a: model supports thinking/reasoning effort.
|
|
34
|
-
* @property {'anthropic' | 'openai-reasoning' | 'none'} [thinkingProtocol] — task-327a:
|
|
34
|
+
* @property {'anthropic' | 'anthropic-adaptive' | 'openai-reasoning' | 'none'} [thinkingProtocol] — task-327a:
|
|
35
35
|
* 'anthropic' → thinking:{type:'enabled', budget_tokens:N}
|
|
36
|
-
* '
|
|
36
|
+
* 'anthropic-adaptive' → thinking:{type:'adaptive'} + output_config:{effort}
|
|
37
|
+
* 'openai-reasoning' → reasoning:{effort:'minimal'|'low'|'medium'|'high'}
|
|
37
38
|
* 'none' (default) → parameter silently dropped by router
|
|
38
39
|
* @property {'low' | 'medium' | 'high' | 'max' | null} [defaultEffort] — task-327a: adapter-level default
|
|
39
40
|
* when caller doesn't specify effort (null = no default / decision-tree decides).
|
|
@@ -73,8 +74,9 @@ export const MODEL_REGISTRY = new Map([
|
|
|
73
74
|
baseUrl: 'https://api.anthropic.com',
|
|
74
75
|
displayName: 'Claude Opus 4.8',
|
|
75
76
|
supportsThinking: true,
|
|
76
|
-
thinkingProtocol: 'anthropic',
|
|
77
|
-
defaultEffort:
|
|
77
|
+
thinkingProtocol: 'anthropic-adaptive',
|
|
78
|
+
defaultEffort: 'high',
|
|
79
|
+
effortOptions: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
78
80
|
maxBudgetTokens: 64000,
|
|
79
81
|
}],
|
|
80
82
|
['claude-opus-4.8', {
|
|
@@ -83,8 +85,9 @@ export const MODEL_REGISTRY = new Map([
|
|
|
83
85
|
baseUrl: 'https://api.anthropic.com',
|
|
84
86
|
displayName: 'Claude Opus 4.8',
|
|
85
87
|
supportsThinking: true,
|
|
86
|
-
thinkingProtocol: 'anthropic',
|
|
87
|
-
defaultEffort:
|
|
88
|
+
thinkingProtocol: 'anthropic-adaptive',
|
|
89
|
+
defaultEffort: 'high',
|
|
90
|
+
effortOptions: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
88
91
|
maxBudgetTokens: 64000,
|
|
89
92
|
}],
|
|
90
93
|
['claude-haiku-3-20250414', {
|
|
@@ -383,7 +386,7 @@ export function parseModelRef(ref) {
|
|
|
383
386
|
|
|
384
387
|
/**
|
|
385
388
|
* Valid effort levels accepted by Yeaft adapters.
|
|
386
|
-
* @typedef {'minimal' | 'low' | 'medium' | 'high' | 'max'} Effort
|
|
389
|
+
* @typedef {'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'} Effort
|
|
387
390
|
*/
|
|
388
391
|
|
|
389
392
|
/**
|
|
@@ -417,15 +420,14 @@ export function mapEffortToOpenAIReasoning(effort) {
|
|
|
417
420
|
case 'low': return 'low';
|
|
418
421
|
case 'medium': return 'medium';
|
|
419
422
|
case 'high': return 'high';
|
|
420
|
-
// OpenAI doesn't support 'max'; degrade to 'high'. Engine may emit a
|
|
421
|
-
// debug line noting the downgrade — adapter level stays silent.
|
|
422
|
-
case 'max': return 'high';
|
|
423
423
|
default: return null;
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
export const OPENAI_REASONING_EFFORT_OPTIONS = ['minimal', 'low', 'medium', 'high'];
|
|
428
|
-
export const
|
|
428
|
+
export const ANTHROPIC_MANUAL_EFFORT_OPTIONS = ['low', 'medium', 'high'];
|
|
429
|
+
export const ANTHROPIC_ADAPTIVE_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
430
|
+
export const ANTHROPIC_ADAPTIVE_MAX_EFFORT_OPTIONS = ['low', 'medium', 'high', 'max'];
|
|
429
431
|
|
|
430
432
|
function inferThinkingCapability(model) {
|
|
431
433
|
const id = parseModelRef(model).modelId.toLowerCase();
|
|
@@ -435,9 +437,32 @@ function inferThinkingCapability(model) {
|
|
|
435
437
|
return { supportsThinking: true, thinkingProtocol: 'openai-reasoning', defaultEffort: null, maxBudgetTokens: null };
|
|
436
438
|
}
|
|
437
439
|
|
|
440
|
+
// Claude Opus 4.7/4.8 expose the full adaptive effort set, including xhigh.
|
|
441
|
+
if (/^claude-opus-4[-.]?(7|8)($|-|\.)/.test(id)) {
|
|
442
|
+
return {
|
|
443
|
+
supportsThinking: true,
|
|
444
|
+
thinkingProtocol: 'anthropic-adaptive',
|
|
445
|
+
defaultEffort: 'high',
|
|
446
|
+
maxBudgetTokens: null,
|
|
447
|
+
effortOptions: ANTHROPIC_ADAPTIVE_EFFORT_OPTIONS,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Claude Opus 4.6 and Sonnet 4.6 use adaptive effort too, but official
|
|
452
|
+
// Anthropic docs do not list xhigh for them. Keep max, reject xhigh.
|
|
453
|
+
if (/^claude-(opus|sonnet)-4[-.]?6($|-|\.)/.test(id)) {
|
|
454
|
+
return {
|
|
455
|
+
supportsThinking: true,
|
|
456
|
+
thinkingProtocol: 'anthropic-adaptive',
|
|
457
|
+
defaultEffort: 'high',
|
|
458
|
+
maxBudgetTokens: null,
|
|
459
|
+
effortOptions: ANTHROPIC_ADAPTIVE_MAX_EFFORT_OPTIONS,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
438
463
|
// Anthropic extended thinking is available on Claude 3.7+ and Claude 4.x.
|
|
439
464
|
// Be conservative: older Claude 3/3.5/Haiku entries stay unsupported unless
|
|
440
|
-
// explicitly listed in the registry.
|
|
465
|
+
// explicitly listed in the registry. Older Claude 4.x entries keep manual budgets.
|
|
441
466
|
if (/^claude-/.test(id) && (/(^|-)3-7($|-|\.)/.test(id) || /(^|-)4($|-|\.)/.test(id))) {
|
|
442
467
|
const maxBudgetTokens = id.includes('opus') ? 64000 : 32000;
|
|
443
468
|
return { supportsThinking: true, thinkingProtocol: 'anthropic', defaultEffort: null, maxBudgetTokens };
|
|
@@ -474,7 +499,7 @@ export function thinkingBudgetForEffort(model, effort) {
|
|
|
474
499
|
* models (red line: never error on unsupported).
|
|
475
500
|
*
|
|
476
501
|
* @param {string} model
|
|
477
|
-
* @returns {{ supportsThinking: boolean, thinkingProtocol: 'anthropic' | 'openai-reasoning' | 'none', defaultEffort: Effort | null, maxBudgetTokens: number | null }}
|
|
502
|
+
* @returns {{ supportsThinking: boolean, thinkingProtocol: 'anthropic' | 'anthropic-adaptive' | 'openai-reasoning' | 'none', defaultEffort: Effort | null, maxBudgetTokens: number | null, effortOptions: Effort[] }}
|
|
478
503
|
*/
|
|
479
504
|
export function getThinkingCapability(model) {
|
|
480
505
|
const info = MODEL_REGISTRY.get(model);
|
|
@@ -486,6 +511,7 @@ export function getThinkingCapability(model) {
|
|
|
486
511
|
thinkingProtocol: 'none',
|
|
487
512
|
defaultEffort: null,
|
|
488
513
|
maxBudgetTokens: null,
|
|
514
|
+
effortOptions: [],
|
|
489
515
|
};
|
|
490
516
|
}
|
|
491
517
|
return {
|
|
@@ -493,14 +519,17 @@ export function getThinkingCapability(model) {
|
|
|
493
519
|
thinkingProtocol: info?.thinkingProtocol || inferred?.thinkingProtocol || 'none',
|
|
494
520
|
defaultEffort: info?.defaultEffort ?? inferred?.defaultEffort ?? null,
|
|
495
521
|
maxBudgetTokens: info?.maxBudgetTokens ?? inferred?.maxBudgetTokens ?? null,
|
|
522
|
+
effortOptions: (info?.effortOptions || inferred?.effortOptions || null),
|
|
496
523
|
};
|
|
497
524
|
}
|
|
498
525
|
|
|
499
526
|
export function getModelEffortOptions(model) {
|
|
500
527
|
const cap = getThinkingCapability(model);
|
|
501
528
|
if (!cap.supportsThinking || cap.thinkingProtocol === 'none') return [];
|
|
529
|
+
if (Array.isArray(cap.effortOptions)) return cap.effortOptions.slice();
|
|
502
530
|
if (cap.thinkingProtocol === 'openai-reasoning') return OPENAI_REASONING_EFFORT_OPTIONS.slice();
|
|
503
|
-
if (cap.thinkingProtocol === 'anthropic') return
|
|
531
|
+
if (cap.thinkingProtocol === 'anthropic-adaptive') return ANTHROPIC_ADAPTIVE_EFFORT_OPTIONS.slice();
|
|
532
|
+
if (cap.thinkingProtocol === 'anthropic') return ANTHROPIC_MANUAL_EFFORT_OPTIONS.slice();
|
|
504
533
|
return [];
|
|
505
534
|
}
|
|
506
535
|
|
|
@@ -515,7 +544,7 @@ export function modelSupportsEffort(model) {
|
|
|
515
544
|
* @returns {Effort | null}
|
|
516
545
|
*/
|
|
517
546
|
export function normalizeEffort(effort) {
|
|
518
|
-
if (effort === 'minimal' || effort === 'low' || effort === 'medium' || effort === 'high' || effort === 'max') {
|
|
547
|
+
if (effort === 'minimal' || effort === 'low' || effort === 'medium' || effort === 'high' || effort === 'xhigh' || effort === 'max') {
|
|
519
548
|
return effort;
|
|
520
549
|
}
|
|
521
550
|
return null;
|
package/yeaft/prompts.js
CHANGED
|
@@ -197,6 +197,7 @@ const PROMPTS = {
|
|
|
197
197
|
tools: (names) => `Available tools: ${names}`,
|
|
198
198
|
// DESIGN-PROMPT §3 ④ — Active Scope header
|
|
199
199
|
activeScopeHeader: '## active_scope',
|
|
200
|
+
multiVpRoutingHeader: '## multi_vp_routing',
|
|
200
201
|
sessionAnnouncementHeader: '[Session Announcement]',
|
|
201
202
|
// Project-doc (CLAUDE.md / AGENTS.md) header + one-liner intro. Both
|
|
202
203
|
// filenames are recognized: CLAUDE.md is this project's convention,
|
|
@@ -212,6 +213,7 @@ const PROMPTS = {
|
|
|
212
213
|
tools: (names) => `可用工具:${names}`,
|
|
213
214
|
// DESIGN-PROMPT §3 ④ — Active Scope header
|
|
214
215
|
activeScopeHeader: '## active_scope',
|
|
216
|
+
multiVpRoutingHeader: '## multi_vp_routing',
|
|
215
217
|
sessionAnnouncementHeader: '[会话公告]',
|
|
216
218
|
// 项目文档块:CLAUDE.md / AGENTS.md(与 Codex 通用命名兼容)。
|
|
217
219
|
projectDocHeader: '[项目文档]',
|
|
@@ -393,6 +395,9 @@ export function buildSystemPrompt({
|
|
|
393
395
|
const activeScopeBlock = renderActiveScope(activeScope, lang);
|
|
394
396
|
if (activeScopeBlock) parts.push(activeScopeBlock);
|
|
395
397
|
|
|
398
|
+
const multiVpRoutingBlock = renderMultiVpRouting(activeScope, lang);
|
|
399
|
+
if (multiVpRoutingBlock) parts.push(multiVpRoutingBlock);
|
|
400
|
+
|
|
396
401
|
return parts.join('\n\n');
|
|
397
402
|
}
|
|
398
403
|
|
|
@@ -490,7 +495,11 @@ function firstNonEmptyString(...values) {
|
|
|
490
495
|
}
|
|
491
496
|
|
|
492
497
|
function renderSessionMembersLine(members) {
|
|
493
|
-
|
|
498
|
+
return normalizeSessionMemberIds(members).join(', ');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function normalizeSessionMemberIds(members) {
|
|
502
|
+
if (!Array.isArray(members)) return [];
|
|
494
503
|
const clean = [];
|
|
495
504
|
const seen = new Set();
|
|
496
505
|
for (const member of members) {
|
|
@@ -500,7 +509,38 @@ function renderSessionMembersLine(members) {
|
|
|
500
509
|
seen.add(id);
|
|
501
510
|
clean.push(id);
|
|
502
511
|
}
|
|
503
|
-
return clean
|
|
512
|
+
return clean;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function renderMultiVpRouting(activeScope, lang) {
|
|
516
|
+
if (!activeScope || typeof activeScope !== 'object') return '';
|
|
517
|
+
const ownId = firstNonEmptyString(activeScope.sessionMember, activeScope.vpId);
|
|
518
|
+
const members = normalizeSessionMemberIds(activeScope.sessionMembers || activeScope.members);
|
|
519
|
+
const peers = ownId ? members.filter((member) => member !== ownId) : members;
|
|
520
|
+
if (peers.length === 0) return '';
|
|
521
|
+
|
|
522
|
+
const header = lang.multiVpRoutingHeader || '## multi_vp_routing';
|
|
523
|
+
if (lang === PROMPTS.zh) {
|
|
524
|
+
return [
|
|
525
|
+
header,
|
|
526
|
+
`当前 VP: ${ownId || 'unknown'}`,
|
|
527
|
+
`可转发 VP: ${peers.join(', ')}`,
|
|
528
|
+
'- 多 VP session 中,先主动感知这些 VP 的职责;不要假装只有你一个人在场。',
|
|
529
|
+
'- 当用户点名其他 VP、任务明显属于其他 VP、需要并行协作,或你需要另一个 VP 继续处理时,必须调用 `route_forward`。',
|
|
530
|
+
'- VP 自己写 @mention 不会触发路由;只有 `route_forward` 工具会真正把任务交给目标 VP。',
|
|
531
|
+
'- 如果要多人一起处理,调用 `route_forward`,`to` 可填目标 vpId 或 `all`;`text` 要包含明确任务和必要上下文。',
|
|
532
|
+
].join('\n');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return [
|
|
536
|
+
header,
|
|
537
|
+
`Current VP: ${ownId || 'unknown'}`,
|
|
538
|
+
`Forwardable VPs: ${peers.join(', ')}`,
|
|
539
|
+
'- In a multi-VP session, actively notice these peers and their likely responsibilities; do not behave as if you are alone.',
|
|
540
|
+
'- When the user names another VP, the task clearly belongs to another VP, parallel collaboration is needed, or another VP should continue the work, you MUST call `route_forward`.',
|
|
541
|
+
'- VP-written @mentions do not route anything; only the `route_forward` tool performs a real hand-off.',
|
|
542
|
+
'- For multi-person work, call `route_forward` with a target vpId or `all`; include the concrete task and required context in `text`.',
|
|
543
|
+
].join('\n');
|
|
504
544
|
}
|
|
505
545
|
|
|
506
546
|
/**
|
|
@@ -28,7 +28,7 @@ const CONFIG_FILE = 'config.json';
|
|
|
28
28
|
|
|
29
29
|
/** Whitelist of persisted session model-override fields. Reject everything else. */
|
|
30
30
|
const ALLOWED_KEYS = new Set(['model', 'modelEffort']);
|
|
31
|
-
const ALLOWED_EFFORTS = new Set(['minimal', 'low', 'medium', 'high']);
|
|
31
|
+
const ALLOWED_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
32
32
|
|
|
33
33
|
export class SessionConfigError extends Error {
|
|
34
34
|
constructor(code, message) {
|
|
@@ -101,7 +101,7 @@ export function validateSessionConfig(cfg) {
|
|
|
101
101
|
}
|
|
102
102
|
if ('modelEffort' in cfg && cfg.modelEffort !== null && cfg.modelEffort !== undefined && cfg.modelEffort !== '') {
|
|
103
103
|
if (typeof cfg.modelEffort !== 'string' || !ALLOWED_EFFORTS.has(cfg.modelEffort.trim())) {
|
|
104
|
-
throw new SessionConfigError('invalid_model_effort', 'modelEffort must be minimal, low, medium, or
|
|
104
|
+
throw new SessionConfigError('invalid_model_effort', 'modelEffort must be minimal, low, medium, high, xhigh, or max');
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -27,16 +27,21 @@ import { defineTool } from './types.js';
|
|
|
27
27
|
|
|
28
28
|
export default defineTool({
|
|
29
29
|
name: 'RouteForward',
|
|
30
|
-
description: `Hand this turn off to another VP in the same
|
|
30
|
+
description: `Hand this turn off to another VP in the same session.
|
|
31
31
|
|
|
32
32
|
Use this tool — NOT free-text @mentions — to route a question or task to
|
|
33
33
|
another VP. VP-authored @mentions in chat text are NOT automatically routed
|
|
34
|
-
(the
|
|
35
|
-
|
|
34
|
+
(the coordinator only text-routes user messages); you must call RouteForward
|
|
35
|
+
for the hand-off to take effect.
|
|
36
|
+
|
|
37
|
+
In a multi-VP session, treat RouteForward as the required hand-off mechanism:
|
|
38
|
+
- If the user names another VP, call RouteForward to that vpId.
|
|
39
|
+
- If another VP clearly owns the domain or should continue the work, call RouteForward instead of only mentioning them.
|
|
40
|
+
- If the task needs parallel collaboration, call RouteForward with a target vpId or "all".
|
|
36
41
|
|
|
37
42
|
Arguments:
|
|
38
43
|
- to (string): target vpId, or the literal "all" to broadcast to every
|
|
39
|
-
other member of the
|
|
44
|
+
other member of the session (subject to the session fan-out cap).
|
|
40
45
|
- text (string): the message body to send on your behalf.
|
|
41
46
|
- reason (string, optional): short rationale for the forward, recorded on
|
|
42
47
|
the message meta for audit / UI display.
|
|
@@ -47,7 +52,7 @@ Rules:
|
|
|
47
52
|
- Forwards carry a causedBy chain; chains deeper than 10 hops are blocked
|
|
48
53
|
(chain_depth_exceeded).
|
|
49
54
|
- A single target may be forwarded to at most 8 times per 5-second window
|
|
50
|
-
per
|
|
55
|
+
per session (throttled).
|
|
51
56
|
|
|
52
57
|
Returns JSON: { ok, dispatched?, error?, detail? }.`,
|
|
53
58
|
parameters: {
|