@yeaft/webchat-agent 0.1.1024 → 0.1.1025

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.
Files changed (3) hide show
  1. package/cli.js +118 -1
  2. package/package.json +1 -1
  3. package/yeaft/models.js +21 -0
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
- const options = parseLlmArgs(args.slice(subcommand === 'use' ? 2 : 1));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.1024",
3
+ "version": "0.1.1025",
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/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