iosm-cli 0.2.1 → 0.2.2
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/CHANGELOG.md +53 -0
- package/README.md +8 -7
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -3
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/models-dev-provider-catalog.d.ts +30 -0
- package/dist/core/models-dev-provider-catalog.d.ts.map +1 -0
- package/dist/core/models-dev-provider-catalog.js +118 -0
- package/dist/core/models-dev-provider-catalog.js.map +1 -0
- package/dist/core/models-dev-providers.d.ts +12 -0
- package/dist/core/models-dev-providers.d.ts.map +1 -0
- package/dist/core/models-dev-providers.js +736 -0
- package/dist/core/models-dev-providers.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +3 -11
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +13 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +89 -27
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +12 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +232 -20
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/configuration.md +4 -1
- package/docs/getting-started.md +2 -2
- package/docs/interactive-mode.md +1 -1
- package/package.json +2 -2
|
@@ -15,7 +15,9 @@ import { parseSkillBlock } from "../../core/agent-session.js";
|
|
|
15
15
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
16
16
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
17
17
|
import { createCompactionSummaryMessage, INTERNAL_UI_META_CUSTOM_TYPE, isInternalUiMetaDetails, } from "../../core/messages.js";
|
|
18
|
+
import { loadModelsDevProviderCatalog, } from "../../core/models-dev-provider-catalog.js";
|
|
18
19
|
import { ModelRegistry } from "../../core/model-registry.js";
|
|
20
|
+
import { MODELS_DEV_PROVIDERS } from "../../core/models-dev-providers.js";
|
|
19
21
|
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
20
22
|
import { getMcpCommandHelp, parseMcpAddCommand, parseMcpTargetCommand, } from "../../core/mcp/index.js";
|
|
21
23
|
import { addMemoryEntry, getMemoryFilePath, readMemoryEntries, removeMemoryEntry, updateMemoryEntry, } from "../../core/memory.js";
|
|
@@ -272,6 +274,41 @@ function resolveDoctorCliToolStatuses() {
|
|
|
272
274
|
});
|
|
273
275
|
}
|
|
274
276
|
const OPENROUTER_PROVIDER_ID = "openrouter";
|
|
277
|
+
const PROVIDER_DISPLAY_NAME_OVERRIDES = {
|
|
278
|
+
"azure-openai-responses": "Azure OpenAI Responses",
|
|
279
|
+
"google-antigravity": "Google Antigravity",
|
|
280
|
+
"google-gemini-cli": "Google Gemini CLI",
|
|
281
|
+
"kimi-coding": "Kimi Coding",
|
|
282
|
+
"openai-codex": "OpenAI Codex",
|
|
283
|
+
"opencode-go": "OpenCode Go",
|
|
284
|
+
"vercel-ai-gateway": "Vercel AI Gateway",
|
|
285
|
+
};
|
|
286
|
+
function toProviderDisplayName(providerId) {
|
|
287
|
+
const override = PROVIDER_DISPLAY_NAME_OVERRIDES[providerId];
|
|
288
|
+
if (override)
|
|
289
|
+
return override;
|
|
290
|
+
return providerId
|
|
291
|
+
.split(/[-_]/g)
|
|
292
|
+
.map((part) => {
|
|
293
|
+
const lower = part.toLowerCase();
|
|
294
|
+
if (lower === "ai")
|
|
295
|
+
return "AI";
|
|
296
|
+
if (lower === "api")
|
|
297
|
+
return "API";
|
|
298
|
+
if (lower === "gpt")
|
|
299
|
+
return "GPT";
|
|
300
|
+
if (lower === "aws")
|
|
301
|
+
return "AWS";
|
|
302
|
+
if (lower === "ui")
|
|
303
|
+
return "UI";
|
|
304
|
+
if (lower === "llm")
|
|
305
|
+
return "LLM";
|
|
306
|
+
if (lower === "id")
|
|
307
|
+
return "ID";
|
|
308
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
309
|
+
})
|
|
310
|
+
.join(" ");
|
|
311
|
+
}
|
|
275
312
|
function isAbortLikeMessage(message) {
|
|
276
313
|
const normalized = message.trim().toLowerCase();
|
|
277
314
|
return normalized.includes("aborted") || normalized.includes("cancelled");
|
|
@@ -421,6 +458,17 @@ export class InteractiveMode {
|
|
|
421
458
|
this.builtInHeader = undefined;
|
|
422
459
|
// ASCII logo component for startup screen
|
|
423
460
|
this.asciiLogo = undefined;
|
|
461
|
+
// API-key provider labels cached for /login and status messages.
|
|
462
|
+
this.apiKeyProviderDisplayNames = new Map();
|
|
463
|
+
this.modelsDevProviderCatalog = MODELS_DEV_PROVIDERS;
|
|
464
|
+
this.modelsDevProviderCatalogById = new Map(MODELS_DEV_PROVIDERS.map((provider) => [
|
|
465
|
+
provider.id,
|
|
466
|
+
{
|
|
467
|
+
...provider,
|
|
468
|
+
models: [],
|
|
469
|
+
},
|
|
470
|
+
]));
|
|
471
|
+
this.modelsDevProviderCatalogRefreshPromise = undefined;
|
|
424
472
|
// Custom header from extension (undefined = use built-in header)
|
|
425
473
|
this.customHeader = undefined;
|
|
426
474
|
/**
|
|
@@ -1275,6 +1323,8 @@ export class InteractiveMode {
|
|
|
1275
1323
|
this.footerDataProvider.onBranchChange(() => {
|
|
1276
1324
|
this.ui.requestRender();
|
|
1277
1325
|
});
|
|
1326
|
+
// Refresh provider catalog from models.dev in background once per startup.
|
|
1327
|
+
void this.refreshModelsDevProviderCatalog();
|
|
1278
1328
|
// Initialize available provider count for footer display
|
|
1279
1329
|
await this.updateAvailableProviderCount();
|
|
1280
1330
|
}
|
|
@@ -3870,7 +3920,7 @@ export class InteractiveMode {
|
|
|
3870
3920
|
this.updateEditorBorderColor();
|
|
3871
3921
|
this.refreshBuiltInHeader();
|
|
3872
3922
|
const thinkingStr = result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
|
|
3873
|
-
this.showStatus(`Switched to ${result.model.
|
|
3923
|
+
this.showStatus(`Switched to ${result.model.provider}/${result.model.id}${thinkingStr}`);
|
|
3874
3924
|
}
|
|
3875
3925
|
}
|
|
3876
3926
|
catch (error) {
|
|
@@ -4910,7 +4960,7 @@ export class InteractiveMode {
|
|
|
4910
4960
|
this.footer.invalidate();
|
|
4911
4961
|
this.updateEditorBorderColor();
|
|
4912
4962
|
this.refreshBuiltInHeader();
|
|
4913
|
-
this.showStatus(`Model: ${model.id}`);
|
|
4963
|
+
this.showStatus(`Model: ${model.provider}/${model.id}`);
|
|
4914
4964
|
this.checkDaxnutsEasterEgg(model);
|
|
4915
4965
|
}
|
|
4916
4966
|
catch (error) {
|
|
@@ -4921,6 +4971,7 @@ export class InteractiveMode {
|
|
|
4921
4971
|
this.showModelSelector(searchTerm);
|
|
4922
4972
|
}
|
|
4923
4973
|
async showModelProviderSelector(preferredProvider) {
|
|
4974
|
+
await this.hydrateMissingProviderModelsForSavedAuth();
|
|
4924
4975
|
this.session.modelRegistry.refresh();
|
|
4925
4976
|
let models = [];
|
|
4926
4977
|
try {
|
|
@@ -5032,7 +5083,7 @@ export class InteractiveMode {
|
|
|
5032
5083
|
this.updateEditorBorderColor();
|
|
5033
5084
|
this.refreshBuiltInHeader();
|
|
5034
5085
|
done();
|
|
5035
|
-
this.showStatus(`Model: ${model.id}`);
|
|
5086
|
+
this.showStatus(`Model: ${model.provider}/${model.id}`);
|
|
5036
5087
|
this.checkDaxnutsEasterEgg(model);
|
|
5037
5088
|
}
|
|
5038
5089
|
catch (error) {
|
|
@@ -5341,22 +5392,29 @@ export class InteractiveMode {
|
|
|
5341
5392
|
return;
|
|
5342
5393
|
}
|
|
5343
5394
|
}
|
|
5395
|
+
await this.refreshModelsDevProviderCatalog();
|
|
5396
|
+
const apiKeyProviders = this.getApiKeyLoginProviders(this.modelsDevProviderCatalog);
|
|
5344
5397
|
this.showSelector((done) => {
|
|
5345
|
-
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, async (
|
|
5398
|
+
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, async (provider) => {
|
|
5346
5399
|
done();
|
|
5347
5400
|
if (mode === "login") {
|
|
5348
|
-
if (
|
|
5349
|
-
|
|
5401
|
+
if (provider.kind === "api_key") {
|
|
5402
|
+
if (provider.id === OPENROUTER_PROVIDER_ID) {
|
|
5403
|
+
await this.handleOpenRouterApiKeyLogin();
|
|
5404
|
+
}
|
|
5405
|
+
else {
|
|
5406
|
+
await this.handleApiKeyLogin(provider.id, { providerName: provider.name });
|
|
5407
|
+
}
|
|
5350
5408
|
}
|
|
5351
5409
|
else {
|
|
5352
|
-
await this.showLoginDialog(
|
|
5410
|
+
await this.showLoginDialog(provider.id);
|
|
5353
5411
|
}
|
|
5354
5412
|
}
|
|
5355
5413
|
else {
|
|
5356
5414
|
// Logout flow
|
|
5357
|
-
const providerName = this.getProviderDisplayName(
|
|
5415
|
+
const providerName = this.getProviderDisplayName(provider.id);
|
|
5358
5416
|
try {
|
|
5359
|
-
this.session.modelRegistry.authStorage.logout(
|
|
5417
|
+
this.session.modelRegistry.authStorage.logout(provider.id);
|
|
5360
5418
|
this.session.modelRegistry.refresh();
|
|
5361
5419
|
await this.updateAvailableProviderCount();
|
|
5362
5420
|
this.showStatus(`Logged out of ${providerName}`);
|
|
@@ -5368,21 +5426,156 @@ export class InteractiveMode {
|
|
|
5368
5426
|
}, () => {
|
|
5369
5427
|
done();
|
|
5370
5428
|
this.ui.requestRender();
|
|
5371
|
-
});
|
|
5429
|
+
}, apiKeyProviders);
|
|
5372
5430
|
return { component: selector, focus: selector };
|
|
5373
5431
|
});
|
|
5374
5432
|
}
|
|
5433
|
+
async refreshModelsDevProviderCatalog() {
|
|
5434
|
+
if (this.modelsDevProviderCatalogRefreshPromise) {
|
|
5435
|
+
await this.modelsDevProviderCatalogRefreshPromise;
|
|
5436
|
+
return;
|
|
5437
|
+
}
|
|
5438
|
+
this.modelsDevProviderCatalogRefreshPromise = (async () => {
|
|
5439
|
+
const catalog = await loadModelsDevProviderCatalog();
|
|
5440
|
+
this.modelsDevProviderCatalogById = catalog;
|
|
5441
|
+
this.modelsDevProviderCatalog = Array.from(catalog.values())
|
|
5442
|
+
.map((provider) => ({
|
|
5443
|
+
id: provider.id,
|
|
5444
|
+
name: provider.name,
|
|
5445
|
+
env: provider.env,
|
|
5446
|
+
}))
|
|
5447
|
+
.sort((a, b) => a.name.localeCompare(b.name, "en") || a.id.localeCompare(b.id, "en"));
|
|
5448
|
+
})()
|
|
5449
|
+
.catch(() => {
|
|
5450
|
+
this.modelsDevProviderCatalog = MODELS_DEV_PROVIDERS;
|
|
5451
|
+
this.modelsDevProviderCatalogById = new Map(MODELS_DEV_PROVIDERS.map((provider) => [
|
|
5452
|
+
provider.id,
|
|
5453
|
+
{
|
|
5454
|
+
...provider,
|
|
5455
|
+
models: [],
|
|
5456
|
+
},
|
|
5457
|
+
]));
|
|
5458
|
+
})
|
|
5459
|
+
.finally(() => {
|
|
5460
|
+
this.modelsDevProviderCatalogRefreshPromise = undefined;
|
|
5461
|
+
});
|
|
5462
|
+
await this.modelsDevProviderCatalogRefreshPromise;
|
|
5463
|
+
}
|
|
5464
|
+
resolveModelsDevApi(modelNpm) {
|
|
5465
|
+
const npm = modelNpm?.toLowerCase() ?? "";
|
|
5466
|
+
if (npm.includes("anthropic"))
|
|
5467
|
+
return "anthropic-messages";
|
|
5468
|
+
if (npm.includes("google-vertex"))
|
|
5469
|
+
return "google-vertex";
|
|
5470
|
+
if (npm.includes("google"))
|
|
5471
|
+
return "google-generative-ai";
|
|
5472
|
+
if (npm.includes("amazon-bedrock"))
|
|
5473
|
+
return "bedrock-converse-stream";
|
|
5474
|
+
if (npm.includes("mistral"))
|
|
5475
|
+
return "mistral-conversations";
|
|
5476
|
+
if (npm.includes("@ai-sdk/openai") && !npm.includes("compatible"))
|
|
5477
|
+
return "openai-responses";
|
|
5478
|
+
return "openai-completions";
|
|
5479
|
+
}
|
|
5480
|
+
buildModelsDevProviderConfig(providerInfo) {
|
|
5481
|
+
const baseUrl = providerInfo.api ?? providerInfo.models.find((model) => !!model.api)?.api;
|
|
5482
|
+
if (!baseUrl)
|
|
5483
|
+
return undefined;
|
|
5484
|
+
if (providerInfo.models.length === 0)
|
|
5485
|
+
return undefined;
|
|
5486
|
+
const models = providerInfo.models.map((model) => ({
|
|
5487
|
+
id: model.id,
|
|
5488
|
+
name: model.name,
|
|
5489
|
+
api: this.resolveModelsDevApi(model.npm ?? providerInfo.npm),
|
|
5490
|
+
reasoning: model.reasoning,
|
|
5491
|
+
input: [...model.input],
|
|
5492
|
+
cost: model.cost,
|
|
5493
|
+
contextWindow: model.contextWindow,
|
|
5494
|
+
maxTokens: model.maxTokens,
|
|
5495
|
+
headers: Object.keys(model.headers).length > 0 ? model.headers : undefined,
|
|
5496
|
+
}));
|
|
5497
|
+
return {
|
|
5498
|
+
baseUrl,
|
|
5499
|
+
models,
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
hasRegisteredProviderModels(providerId) {
|
|
5503
|
+
const registry = this.session.modelRegistry;
|
|
5504
|
+
if (typeof registry.getAll !== "function")
|
|
5505
|
+
return true;
|
|
5506
|
+
return registry.getAll().some((model) => model.provider === providerId);
|
|
5507
|
+
}
|
|
5508
|
+
async hydrateProviderModelsFromModelsDev(providerId) {
|
|
5509
|
+
if (this.hasRegisteredProviderModels(providerId))
|
|
5510
|
+
return true;
|
|
5511
|
+
await this.refreshModelsDevProviderCatalog();
|
|
5512
|
+
const providerInfo = this.modelsDevProviderCatalogById.get(providerId);
|
|
5513
|
+
if (!providerInfo)
|
|
5514
|
+
return false;
|
|
5515
|
+
const config = this.buildModelsDevProviderConfig(providerInfo);
|
|
5516
|
+
if (!config)
|
|
5517
|
+
return false;
|
|
5518
|
+
try {
|
|
5519
|
+
this.session.modelRegistry.registerProvider(providerId, config);
|
|
5520
|
+
return this.hasRegisteredProviderModels(providerId);
|
|
5521
|
+
}
|
|
5522
|
+
catch {
|
|
5523
|
+
return false;
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
async hydrateMissingProviderModelsForSavedAuth() {
|
|
5527
|
+
const savedProviders = this.session.modelRegistry.authStorage.list();
|
|
5528
|
+
if (savedProviders.length === 0)
|
|
5529
|
+
return;
|
|
5530
|
+
for (const providerId of savedProviders) {
|
|
5531
|
+
if (this.hasRegisteredProviderModels(providerId))
|
|
5532
|
+
continue;
|
|
5533
|
+
await this.hydrateProviderModelsFromModelsDev(providerId);
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
getApiKeyLoginProviders(modelsDevProviders) {
|
|
5537
|
+
const providerNames = new Map();
|
|
5538
|
+
this.apiKeyProviderDisplayNames.clear();
|
|
5539
|
+
for (const model of this.session.modelRegistry.getAll()) {
|
|
5540
|
+
if (!providerNames.has(model.provider)) {
|
|
5541
|
+
providerNames.set(model.provider, toProviderDisplayName(model.provider));
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
for (const provider of modelsDevProviders) {
|
|
5545
|
+
const fallbackName = toProviderDisplayName(provider.id);
|
|
5546
|
+
const current = providerNames.get(provider.id);
|
|
5547
|
+
if (!current || current === fallbackName) {
|
|
5548
|
+
providerNames.set(provider.id, provider.name || fallbackName);
|
|
5549
|
+
}
|
|
5550
|
+
}
|
|
5551
|
+
for (const providerId of this.session.modelRegistry.authStorage.list()) {
|
|
5552
|
+
if (!providerNames.has(providerId)) {
|
|
5553
|
+
providerNames.set(providerId, toProviderDisplayName(providerId));
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
const oauthProviderIds = new Set(this.session.modelRegistry.authStorage.getOAuthProviders().map((provider) => provider.id));
|
|
5557
|
+
const providers = [];
|
|
5558
|
+
for (const [id, name] of providerNames.entries()) {
|
|
5559
|
+
if (oauthProviderIds.has(id))
|
|
5560
|
+
continue;
|
|
5561
|
+
this.apiKeyProviderDisplayNames.set(id, name);
|
|
5562
|
+
providers.push({ id, name, kind: "api_key" });
|
|
5563
|
+
}
|
|
5564
|
+
providers.sort((a, b) => a.name.localeCompare(b.name));
|
|
5565
|
+
return providers;
|
|
5566
|
+
}
|
|
5375
5567
|
getProviderDisplayName(providerId) {
|
|
5376
|
-
|
|
5377
|
-
|
|
5568
|
+
const apiKeyName = this.apiKeyProviderDisplayNames.get(providerId);
|
|
5569
|
+
if (apiKeyName) {
|
|
5570
|
+
return apiKeyName;
|
|
5378
5571
|
}
|
|
5379
5572
|
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId);
|
|
5380
|
-
return providerInfo?.name || providerId;
|
|
5573
|
+
return providerInfo?.name || toProviderDisplayName(providerId);
|
|
5381
5574
|
}
|
|
5382
|
-
async
|
|
5575
|
+
async handleApiKeyLogin(providerId, options) {
|
|
5576
|
+
const providerName = options?.providerName || this.getProviderDisplayName(providerId);
|
|
5383
5577
|
const openModelSelector = options?.openModelSelector ?? true;
|
|
5384
|
-
const
|
|
5385
|
-
const existingCredential = this.session.modelRegistry.authStorage.get(OPENROUTER_PROVIDER_ID);
|
|
5578
|
+
const existingCredential = this.session.modelRegistry.authStorage.get(providerId);
|
|
5386
5579
|
if (existingCredential) {
|
|
5387
5580
|
const overwrite = await this.showExtensionConfirm(`${providerName}: replace existing credentials?`, `Stored at ${getAuthPath()}`);
|
|
5388
5581
|
if (!overwrite) {
|
|
@@ -5390,7 +5583,11 @@ export class InteractiveMode {
|
|
|
5390
5583
|
return;
|
|
5391
5584
|
}
|
|
5392
5585
|
}
|
|
5393
|
-
const
|
|
5586
|
+
const promptLines = [`${providerName} API key`];
|
|
5587
|
+
if (options?.createKeyUrl) {
|
|
5588
|
+
promptLines.push(`Create key: ${options.createKeyUrl}`);
|
|
5589
|
+
}
|
|
5590
|
+
const keyInput = await this.showExtensionInput(promptLines.join("\n"), options?.placeholder ?? "api-key");
|
|
5394
5591
|
if (keyInput === undefined) {
|
|
5395
5592
|
this.showStatus(`${providerName} login cancelled.`);
|
|
5396
5593
|
return;
|
|
@@ -5400,13 +5597,28 @@ export class InteractiveMode {
|
|
|
5400
5597
|
this.showWarning(`${providerName} API key cannot be empty.`);
|
|
5401
5598
|
return;
|
|
5402
5599
|
}
|
|
5403
|
-
this.session.modelRegistry.authStorage.set(
|
|
5600
|
+
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
5601
|
+
let hasProviderModels = this.hasRegisteredProviderModels(providerId);
|
|
5602
|
+
if (!hasProviderModels) {
|
|
5603
|
+
hasProviderModels = await this.hydrateProviderModelsFromModelsDev(providerId);
|
|
5604
|
+
}
|
|
5404
5605
|
await this.updateAvailableProviderCount();
|
|
5405
5606
|
this.showStatus(`${providerName} API key saved to ${getAuthPath()}`);
|
|
5406
|
-
if (openModelSelector) {
|
|
5407
|
-
await this.showModelProviderSelector(
|
|
5607
|
+
if (openModelSelector && hasProviderModels) {
|
|
5608
|
+
await this.showModelProviderSelector(providerId);
|
|
5609
|
+
}
|
|
5610
|
+
else if (openModelSelector) {
|
|
5611
|
+
this.showWarning(`${providerName} configured, but no models are available yet. Run /model after network is available.`);
|
|
5408
5612
|
}
|
|
5409
5613
|
}
|
|
5614
|
+
async handleOpenRouterApiKeyLogin(options) {
|
|
5615
|
+
await this.handleApiKeyLogin(OPENROUTER_PROVIDER_ID, {
|
|
5616
|
+
providerName: "OpenRouter",
|
|
5617
|
+
openModelSelector: options?.openModelSelector,
|
|
5618
|
+
createKeyUrl: "https://openrouter.ai/keys",
|
|
5619
|
+
placeholder: "sk-or-v1-...",
|
|
5620
|
+
});
|
|
5621
|
+
}
|
|
5410
5622
|
async showLoginDialog(providerId) {
|
|
5411
5623
|
const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId);
|
|
5412
5624
|
const providerName = this.getProviderDisplayName(providerId);
|