@yeaft/webchat-agent 0.1.1024 → 0.1.1026
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/cli.js +118 -1
- package/package.json +1 -1
- package/yeaft/models.js +21 -0
- package/yeaft/web-bridge.js +51 -16
package/cli.js
CHANGED
|
@@ -24,6 +24,11 @@ import {
|
|
|
24
24
|
useOpenAICompatible,
|
|
25
25
|
writeLocalLlmConfig,
|
|
26
26
|
} from './llm-config-cli.js';
|
|
27
|
+
import {
|
|
28
|
+
discoverGitHubCopilotModels,
|
|
29
|
+
discoverOpenAICompatibleModels,
|
|
30
|
+
GITHUB_COPILOT_PROVIDER,
|
|
31
|
+
} from './llm-model-discovery.js';
|
|
27
32
|
|
|
28
33
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
34
|
const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
|
|
@@ -97,6 +102,7 @@ function printLlmHelp() {
|
|
|
97
102
|
|
|
98
103
|
Usage:
|
|
99
104
|
yeaft-agent llm show [--reveal]
|
|
105
|
+
yeaft-agent llm list-models [<provider-name>]
|
|
100
106
|
yeaft-agent llm setup
|
|
101
107
|
yeaft-agent llm use github-copilot --model <modelId> [--fast <modelId>] [--allow-unknown-model]
|
|
102
108
|
yeaft-agent llm use openai-compatible --name <name> --base-url <url> --api-key-env <ENV> --model <modelId> [--fast <modelId>]
|
|
@@ -112,6 +118,8 @@ function printLlmHelp() {
|
|
|
112
118
|
add-provider updates/replaces an existing provider with the same --name.
|
|
113
119
|
--api-key-env reads the environment variable value and writes it as apiKey.
|
|
114
120
|
set-model requires full provider/model references.
|
|
121
|
+
list-models with no provider lists the local config offline; with 'github-copilot'
|
|
122
|
+
or a configured provider name, it queries the live '/models' catalog.
|
|
115
123
|
--config <path> can target a config file for tests or scripted setup.
|
|
116
124
|
|
|
117
125
|
Examples:
|
|
@@ -127,7 +135,14 @@ async function handleLlmCommand(args) {
|
|
|
127
135
|
const subcommand = args[0];
|
|
128
136
|
|
|
129
137
|
try {
|
|
130
|
-
|
|
138
|
+
// `use <preset>` and `list-models <provider-name>` both put a positional
|
|
139
|
+
// arg right after the subcommand; trim it off before flag parsing so
|
|
140
|
+
// parseLlmArgs sees only flags.
|
|
141
|
+
const positionalAfterSub =
|
|
142
|
+
subcommand === 'use' ? 2
|
|
143
|
+
: (subcommand === 'list-models' && args[1] && !args[1].startsWith('--')) ? 2
|
|
144
|
+
: 1;
|
|
145
|
+
const options = parseLlmArgs(args.slice(positionalAfterSub));
|
|
131
146
|
const configPath = options.config || getDefaultYeaftConfigPath();
|
|
132
147
|
if (!subcommand || subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
133
148
|
printLlmHelp();
|
|
@@ -140,6 +155,19 @@ async function handleLlmCommand(args) {
|
|
|
140
155
|
return;
|
|
141
156
|
}
|
|
142
157
|
|
|
158
|
+
if (subcommand === 'list-models') {
|
|
159
|
+
// `yeaft-agent llm list-models` (no provider) — list models declared in
|
|
160
|
+
// the local config (offline; no network call).
|
|
161
|
+
// `yeaft-agent llm list-models <provider-name>` — live-discover models:
|
|
162
|
+
// - "github-copilot" uses the local Copilot credential
|
|
163
|
+
// - any other name must already exist in config.json (uses its
|
|
164
|
+
// baseUrl + apiKey for OpenAI-compatible /models discovery)
|
|
165
|
+
const providerName = (args[1] && !args[1].startsWith('--')) ? args[1] : null;
|
|
166
|
+
const config = readLocalLlmConfig(configPath);
|
|
167
|
+
await handleListModels(config, { providerName });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
143
171
|
const current = readLocalLlmConfig(configPath);
|
|
144
172
|
let result;
|
|
145
173
|
if (subcommand === 'setup') {
|
|
@@ -204,6 +232,95 @@ async function handleLlmCommand(args) {
|
|
|
204
232
|
}
|
|
205
233
|
}
|
|
206
234
|
|
|
235
|
+
/**
|
|
236
|
+
* `yeaft-agent llm list-models [<provider-name>]` handler.
|
|
237
|
+
*
|
|
238
|
+
* Three modes:
|
|
239
|
+
* - No provider — list all models declared in the local config (offline,
|
|
240
|
+
* no network call). Annotates `← primary` / `← fast` for clarity.
|
|
241
|
+
* - "github-copilot" — live-discover Copilot's model catalog using the
|
|
242
|
+
* local device credential. Missing/invalid credential prints an
|
|
243
|
+
* actionable hint ("Run `gh auth login` ...") and exits non-zero so
|
|
244
|
+
* scripts can detect the failure.
|
|
245
|
+
* - Any other name — must already exist in config.json; uses its
|
|
246
|
+
* baseUrl + apiKey for OpenAI-compatible `/models` discovery.
|
|
247
|
+
*/
|
|
248
|
+
export async function handleListModels(
|
|
249
|
+
config,
|
|
250
|
+
{ providerName = null, deps = {} } = {}
|
|
251
|
+
) {
|
|
252
|
+
const discoverCopilot = deps.discoverCopilot || discoverGitHubCopilotModels;
|
|
253
|
+
const discoverOpenAI = deps.discoverOpenAI || discoverOpenAICompatibleModels;
|
|
254
|
+
|
|
255
|
+
if (providerName === GITHUB_COPILOT_PROVIDER.name) {
|
|
256
|
+
try {
|
|
257
|
+
const result = await discoverCopilot();
|
|
258
|
+
console.log(`Available models from GitHub Copilot (source: ${result.source}):`);
|
|
259
|
+
for (const id of result.models) console.log(` ${id}`);
|
|
260
|
+
if (result.warning) console.log(`\nNote: ${result.warning}`);
|
|
261
|
+
return;
|
|
262
|
+
} catch (err) {
|
|
263
|
+
console.error(`GitHub Copilot model discovery failed: ${err.message}`);
|
|
264
|
+
if (err.code === 'COPILOT_CREDENTIAL_MISSING' || err.code === 'COPILOT_AUTH_INVALID') {
|
|
265
|
+
console.error('Tip: run `gh auth login` (or complete the Copilot device login) and re-run this command.');
|
|
266
|
+
}
|
|
267
|
+
process.exitCode = 1;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (providerName) {
|
|
273
|
+
const providers = Array.isArray(config.providers) ? config.providers : [];
|
|
274
|
+
const target = providers.find(p => p && p.name === providerName);
|
|
275
|
+
if (!target) {
|
|
276
|
+
console.error(`Provider "${providerName}" not found in config.json.`);
|
|
277
|
+
if (providers.length === 0) {
|
|
278
|
+
console.error('No providers are configured. Run `yeaft-agent llm setup` or `yeaft-agent llm use github-copilot ...`.');
|
|
279
|
+
} else {
|
|
280
|
+
console.error('Configured providers:');
|
|
281
|
+
for (const p of providers) console.error(` ${p.name}`);
|
|
282
|
+
}
|
|
283
|
+
process.exitCode = 1;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const result = await discoverOpenAI({
|
|
288
|
+
baseUrl: target.baseUrl,
|
|
289
|
+
apiKey: target.apiKey,
|
|
290
|
+
});
|
|
291
|
+
console.log(`Available models from "${providerName}" (${target.baseUrl}, source: ${result.source}):`);
|
|
292
|
+
for (const id of result.models) console.log(` ${providerName}/${id}`);
|
|
293
|
+
return;
|
|
294
|
+
} catch (err) {
|
|
295
|
+
console.error(`Model discovery for "${providerName}" failed: ${err.message}`);
|
|
296
|
+
process.exitCode = 1;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Default: list configured providers' declared models (no network call).
|
|
302
|
+
const providers = Array.isArray(config.providers) ? config.providers : [];
|
|
303
|
+
if (providers.length === 0) {
|
|
304
|
+
console.log('No providers configured in config.json.');
|
|
305
|
+
console.log('Run `yeaft-agent llm setup`, or `yeaft-agent llm list-models github-copilot` to discover the Copilot catalog.');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
console.log('Configured models:');
|
|
309
|
+
for (const provider of providers) {
|
|
310
|
+
const tag = provider.managed || provider.credentialProvider ? ' (managed)' : '';
|
|
311
|
+
console.log(` [${provider.name}]${tag} ${provider.baseUrl || ''}`.trimEnd());
|
|
312
|
+
if (!Array.isArray(provider.models)) continue;
|
|
313
|
+
for (const m of provider.models) {
|
|
314
|
+
const id = typeof m === 'string' ? m : m?.id;
|
|
315
|
+
if (!id) continue;
|
|
316
|
+
const ref = `${provider.name}/${id}`;
|
|
317
|
+
const annot = ref === config.primaryModel ? ' ← primary'
|
|
318
|
+
: ref === config.fastModel ? ' ← fast'
|
|
319
|
+
: '';
|
|
320
|
+
console.log(` ${ref}${annot}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
207
324
|
|
|
208
325
|
async function runLlmSetup(current, configPath) {
|
|
209
326
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
package/package.json
CHANGED
package/yeaft/models.js
CHANGED
|
@@ -428,6 +428,13 @@ export const OPENAI_REASONING_EFFORT_OPTIONS = ['minimal', 'low', 'medium', 'hig
|
|
|
428
428
|
export const ANTHROPIC_MANUAL_EFFORT_OPTIONS = ['low', 'medium', 'high'];
|
|
429
429
|
export const ANTHROPIC_ADAPTIVE_EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
430
430
|
export const ANTHROPIC_ADAPTIVE_MAX_EFFORT_OPTIONS = ['low', 'medium', 'high', 'max'];
|
|
431
|
+
// DeepSeek reasoning models (deepseek-reasoner / deepseek-r1) expose a simple
|
|
432
|
+
// low/medium/high effort scale. DeepSeek's OpenAI-compatible surface accepts a
|
|
433
|
+
// reasoning effort hint; we send it through the standard openai-reasoning
|
|
434
|
+
// `reasoning.effort` path (relay/proxy adapts to DeepSeek's wire format). No
|
|
435
|
+
// `minimal` tier — DeepSeek documents only a high/max effort distinction, so we
|
|
436
|
+
// keep the user-facing scale to the three levels the user expects.
|
|
437
|
+
export const DEEPSEEK_REASONING_EFFORT_OPTIONS = ['low', 'medium', 'high'];
|
|
431
438
|
|
|
432
439
|
function inferThinkingCapability(model) {
|
|
433
440
|
const id = parseModelRef(model).modelId.toLowerCase();
|
|
@@ -468,6 +475,20 @@ function inferThinkingCapability(model) {
|
|
|
468
475
|
return { supportsThinking: true, thinkingProtocol: 'anthropic', defaultEffort: null, maxBudgetTokens };
|
|
469
476
|
}
|
|
470
477
|
|
|
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)) {
|
|
483
|
+
return {
|
|
484
|
+
supportsThinking: true,
|
|
485
|
+
thinkingProtocol: 'openai-reasoning',
|
|
486
|
+
defaultEffort: null,
|
|
487
|
+
maxBudgetTokens: null,
|
|
488
|
+
effortOptions: DEEPSEEK_REASONING_EFFORT_OPTIONS,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
471
492
|
return null;
|
|
472
493
|
}
|
|
473
494
|
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -3481,6 +3481,17 @@ function removeQueuedVpTurn(turnId) {
|
|
|
3481
3481
|
return removed;
|
|
3482
3482
|
}
|
|
3483
3483
|
|
|
3484
|
+
function findTurnIdsForVp({ sessionId = null, vpId = null } = {}) {
|
|
3485
|
+
if (!vpId) return [];
|
|
3486
|
+
const ids = [];
|
|
3487
|
+
for (const [turnId, meta] of turnAbortMeta.entries()) {
|
|
3488
|
+
if (!meta || meta.vpId !== vpId) continue;
|
|
3489
|
+
if (sessionId && meta.sessionId !== sessionId) continue;
|
|
3490
|
+
ids.push(turnId);
|
|
3491
|
+
}
|
|
3492
|
+
return ids;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3484
3495
|
function emitQueuedTurnAbort(meta, turnId) {
|
|
3485
3496
|
if (!meta?.vpId) return;
|
|
3486
3497
|
const sessionId = meta.sessionId || null;
|
|
@@ -3621,31 +3632,55 @@ export function handleYeaftAbortAll(msg = {}) {
|
|
|
3621
3632
|
}
|
|
3622
3633
|
|
|
3623
3634
|
/**
|
|
3624
|
-
* Per-VP abort: stops a single VP turn
|
|
3625
|
-
*
|
|
3626
|
-
* @param {{ turnId?: string }} msg
|
|
3635
|
+
* Per-VP abort: stops a single VP turn without affecting siblings.
|
|
3636
|
+
* New clients send `{ sessionId, vpId }`; `turnId` is kept for legacy buttons.
|
|
3637
|
+
* @param {{ turnId?: string, sessionId?: string, vpId?: string }} msg
|
|
3627
3638
|
*/
|
|
3628
3639
|
export function handleYeaftAbortTurn(msg = {}) {
|
|
3629
|
-
const
|
|
3630
|
-
|
|
3631
|
-
|
|
3640
|
+
const sessionId = msg.sessionId || null;
|
|
3641
|
+
const vpId = msg.vpId || null;
|
|
3642
|
+
const turnIds = msg.turnId ? [msg.turnId] : findTurnIdsForVp({ sessionId, vpId });
|
|
3643
|
+
|
|
3644
|
+
if (turnIds.length === 0) {
|
|
3645
|
+
sendSessionEvent({ type: 'yeaft_turn_aborted', turnId: null, turnIds: [], success: false, sessionId, vpId }, sessionId ? { sessionId } : undefined);
|
|
3632
3646
|
return;
|
|
3633
3647
|
}
|
|
3634
3648
|
|
|
3635
|
-
const meta = turnAbortMeta.get(turnId);
|
|
3636
|
-
const ctrl = turnAbortCtrls.get(turnId);
|
|
3637
3649
|
let success = false;
|
|
3650
|
+
const abortedTurnIds = [];
|
|
3651
|
+
let ackSessionId = sessionId;
|
|
3652
|
+
let ackVpId = vpId;
|
|
3653
|
+
|
|
3654
|
+
for (const turnId of turnIds) {
|
|
3655
|
+
const meta = turnAbortMeta.get(turnId);
|
|
3656
|
+
const ctrl = turnAbortCtrls.get(turnId);
|
|
3657
|
+
if (!ackSessionId && meta?.sessionId) ackSessionId = meta.sessionId;
|
|
3658
|
+
if (!ackVpId && meta?.vpId) ackVpId = meta.vpId;
|
|
3659
|
+
|
|
3660
|
+
let turnAborted = false;
|
|
3661
|
+
if (ctrl && !ctrl.signal.aborted) {
|
|
3662
|
+
try { ctrl.abort(); turnAborted = true; } catch { /* best-effort */ }
|
|
3663
|
+
} else if (removeQueuedVpTurn(turnId)) {
|
|
3664
|
+
turnAborted = true;
|
|
3665
|
+
emitQueuedTurnAbort(meta, turnId);
|
|
3666
|
+
}
|
|
3638
3667
|
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3668
|
+
if (turnAborted) {
|
|
3669
|
+
success = true;
|
|
3670
|
+
abortedTurnIds.push(turnId);
|
|
3671
|
+
}
|
|
3672
|
+
turnAbortCtrls.delete(turnId);
|
|
3673
|
+
turnAbortMeta.delete(turnId);
|
|
3644
3674
|
}
|
|
3645
3675
|
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3676
|
+
sendSessionEvent({
|
|
3677
|
+
type: 'yeaft_turn_aborted',
|
|
3678
|
+
turnId: abortedTurnIds[0] || turnIds[0] || null,
|
|
3679
|
+
turnIds: abortedTurnIds.length > 0 ? abortedTurnIds : turnIds,
|
|
3680
|
+
success,
|
|
3681
|
+
sessionId: ackSessionId || null,
|
|
3682
|
+
vpId: ackVpId || null,
|
|
3683
|
+
}, ackSessionId ? { sessionId: ackSessionId } : undefined);
|
|
3649
3684
|
}
|
|
3650
3685
|
|
|
3651
3686
|
/**
|