agentsw 0.9.1 → 0.9.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/dist/commands.js +37 -15
- package/dist/i18n.js +13 -0
- package/dist/metadata.js +69 -20
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -8,7 +8,7 @@ import prompts from "prompts";
|
|
|
8
8
|
import { loadStore, saveStore, getProvider, configFile } from "./store.js";
|
|
9
9
|
import { scanCandidates, normalizeUrl, findMatchingProvider } from "./import.js";
|
|
10
10
|
import { loadCatalog, searchCatalog } from "./modelsdev.js";
|
|
11
|
-
import { enrichProviderModels, getMetadataMode, resolveMetadataOptions } from "./metadata.js";
|
|
11
|
+
import { classifyUnresolved, enrichProviderModels, getMetadataMode, resolveMetadataOptions } from "./metadata.js";
|
|
12
12
|
import { loadGatewayCatalog } from "./gateway.js";
|
|
13
13
|
import { resolveTargets, supportsProtocol, targets } from "./targets/index.js";
|
|
14
14
|
import { discoverProviderModels, probeProtocols } from "./discover.js";
|
|
@@ -70,11 +70,19 @@ function modelRows(models) {
|
|
|
70
70
|
]);
|
|
71
71
|
}
|
|
72
72
|
const MODEL_HEADER = ["MODEL", "CTX", "IN", "OUT", "REASONING", "$IN/$OUT per M"];
|
|
73
|
-
/**
|
|
74
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Explain why some models stayed bare: only ambiguous ids are worth the user's
|
|
75
|
+
* attention, so those are named separately from ids nothing can resolve.
|
|
76
|
+
*/
|
|
77
|
+
function printUncatalogedHint(models, catalog, hint) {
|
|
75
78
|
const bare = models.filter((m) => m.contextWindow === undefined && !m.cost);
|
|
76
|
-
if (bare.length
|
|
77
|
-
|
|
79
|
+
if (bare.length === 0)
|
|
80
|
+
return;
|
|
81
|
+
console.log(pc.dim(t("meta.gap", { count: bare.length })));
|
|
82
|
+
const { ambiguous, unknown, noCreatorRow } = classifyUnresolved(catalog, bare.map((m) => m.id), hint);
|
|
83
|
+
for (const [key, ids] of [["meta.ambiguous", ambiguous], ["meta.noCreatorRow", noCreatorRow], ["meta.unknown", unknown]]) {
|
|
84
|
+
if (ids.length)
|
|
85
|
+
console.log(pc.dim(` ${t(key, { count: ids.length, ids: ids.join(", "), id: ids[0] })}`));
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
/** Guess the models.dev provider whose API host matches the configured baseUrl. */
|
|
@@ -197,8 +205,15 @@ export async function cmdAdd(opts) {
|
|
|
197
205
|
fail(t("add.openaiApiInvalid"));
|
|
198
206
|
const baseUrl = normalizeUrl(answers.baseUrl);
|
|
199
207
|
const explicitId = answers.id || undefined;
|
|
200
|
-
const
|
|
201
|
-
|
|
208
|
+
const byId = explicitId ? store.providers[explicitId] : undefined;
|
|
209
|
+
// Endpoint plus credential is the account identity, so an explicit id never
|
|
210
|
+
// licenses a second entry for an account that is already configured.
|
|
211
|
+
const sameAccount = byId ? undefined : findMatchingProvider(Object.values(store.providers), { protocol, baseUrl, apiKey: answers.apiKey });
|
|
212
|
+
const existing = byId ?? sameAccount;
|
|
213
|
+
if (byId === undefined && explicitId && sameAccount) {
|
|
214
|
+
console.log(pc.yellow(t("add.alreadyConfigured", { id: pc.bold(sameAccount.id) })));
|
|
215
|
+
}
|
|
216
|
+
const id = byId?.id ?? sameAccount?.id ?? explicitId ?? availableProviderId(providerIdFromBaseUrl(baseUrl, protocol), store.providers);
|
|
202
217
|
const openaiApi = protocol === "openai" ? wire ?? existing?.openaiApi : undefined;
|
|
203
218
|
const metadataOptions = resolveMetadataOptions(opts, existing);
|
|
204
219
|
const manualIds = (answers.models ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -256,7 +271,7 @@ export async function cmdAdd(opts) {
|
|
|
256
271
|
console.log(pc.green(t("add.saved", { status: savedStatus, id: pc.bold(provider.id), protocol })));
|
|
257
272
|
console.log(t("add.metadata", { matched, total: models.length }) + (hint ? t("add.providerHint", { hint }) : ""));
|
|
258
273
|
console.log(table(modelRows(models), MODEL_HEADER));
|
|
259
|
-
printUncatalogedHint(models);
|
|
274
|
+
printUncatalogedHint(models, catalog, hint);
|
|
260
275
|
console.log(pc.dim(`\n${t("add.next", { id: provider.id })}`));
|
|
261
276
|
}
|
|
262
277
|
/** Shared logic to create and save a single provider from discovered models. */
|
|
@@ -297,7 +312,7 @@ async function createProvider(opts) {
|
|
|
297
312
|
console.log(pc.green(t("add.saved", { status: savedStatus, id: pc.bold(provider.id), protocol })));
|
|
298
313
|
console.log(t("add.metadata", { matched, total: models.length }) + (hint ? t("add.providerHint", { hint }) : ""));
|
|
299
314
|
console.log(table(modelRows(models), MODEL_HEADER));
|
|
300
|
-
printUncatalogedHint(models);
|
|
315
|
+
printUncatalogedHint(models, catalog, hint);
|
|
301
316
|
return provider;
|
|
302
317
|
}
|
|
303
318
|
/**
|
|
@@ -339,8 +354,13 @@ export async function cmdQuickAdd(opts) {
|
|
|
339
354
|
const gatewayLoader = sharedGatewayLoader();
|
|
340
355
|
for (const protocol of protocols) {
|
|
341
356
|
const explicitId = opts.id === undefined ? undefined : `${opts.id}${multi ? `-${protocol}` : ""}`;
|
|
342
|
-
const
|
|
343
|
-
const
|
|
357
|
+
const byId = explicitId ? store.providers[explicitId] : undefined;
|
|
358
|
+
const sameAccount = byId ? undefined : findMatchingProvider(Object.values(store.providers), { protocol, baseUrl, apiKey });
|
|
359
|
+
const existing = byId ?? sameAccount;
|
|
360
|
+
if (byId === undefined && explicitId && sameAccount) {
|
|
361
|
+
console.log(pc.yellow(t("add.alreadyConfigured", { id: pc.bold(sameAccount.id) })));
|
|
362
|
+
}
|
|
363
|
+
const id = byId?.id ?? sameAccount?.id ?? explicitId ?? availableProviderId(providerIdFromBaseUrl(baseUrl, protocol), store.providers);
|
|
344
364
|
const name = opts.name ?? existing?.name ?? (opts.id === undefined ? providerNameFromBaseUrl(baseUrl, protocol) : multi ? `${opts.id} (${protocol})` : opts.id);
|
|
345
365
|
const modelFilter = parseFilterOpts(opts, existing?.modelFilter);
|
|
346
366
|
process.stderr.write(pc.dim(`${t("add.discovering", { url: baseUrl })} [${protocol}]\n`));
|
|
@@ -641,7 +661,7 @@ export async function cmdModels(query, opts) {
|
|
|
641
661
|
}
|
|
642
662
|
console.log(`${pc.bold(provider.id)} (${provider.protocol}) · ${provider.baseUrl}`);
|
|
643
663
|
console.log(table(modelRows(provider.models), MODEL_HEADER));
|
|
644
|
-
printUncatalogedHint(provider.models);
|
|
664
|
+
printUncatalogedHint(provider.models, await loadCatalog(), provider.modelsDevId);
|
|
645
665
|
return;
|
|
646
666
|
}
|
|
647
667
|
const catalog = await loadCatalog({ refresh: opts.refresh });
|
|
@@ -680,9 +700,11 @@ export async function cmdRefreshMeta(opts = {}) {
|
|
|
680
700
|
updated++;
|
|
681
701
|
}
|
|
682
702
|
saveStore(store);
|
|
683
|
-
console.log(pc.green(
|
|
703
|
+
console.log(pc.green(t("refresh.checked", { changed: updated })));
|
|
684
704
|
if (updated > 0)
|
|
685
|
-
console.log(pc.dim("
|
|
705
|
+
console.log(pc.dim(t("refresh.next")));
|
|
706
|
+
for (const provider of providers)
|
|
707
|
+
printUncatalogedHint(provider.models, catalog, provider.modelsDevId);
|
|
686
708
|
}
|
|
687
709
|
export async function cmdDiscover(id, opts) {
|
|
688
710
|
resolveTargets(opts.apps);
|
|
@@ -715,7 +737,7 @@ export async function cmdDiscover(id, opts) {
|
|
|
715
737
|
(added.length ? `\n new: ${added.join(", ")}` : "") +
|
|
716
738
|
(gone.length ? `\n removed upstream: ${gone.join(", ")}` : ""));
|
|
717
739
|
console.log(table(modelRows(provider.models), MODEL_HEADER));
|
|
718
|
-
printUncatalogedHint(provider.models);
|
|
740
|
+
printUncatalogedHint(provider.models, catalog, provider.modelsDevId);
|
|
719
741
|
if (opts.sync) {
|
|
720
742
|
console.log("");
|
|
721
743
|
await cmdSync({ provider: id, apps: opts.apps });
|
package/dist/i18n.js
CHANGED
|
@@ -190,6 +190,7 @@ const messages = {
|
|
|
190
190
|
"add.defaultMissing": { en: "default model {model} is not in the model list", "zh-CN": "默认模型 {model} 不在模型列表中" },
|
|
191
191
|
"add.smallMissing": { en: "small model {model} is not in the model list", "zh-CN": "小型模型 {model} 不在模型列表中" },
|
|
192
192
|
"add.added": { en: "added", "zh-CN": "已添加" },
|
|
193
|
+
"add.alreadyConfigured": { en: "same endpoint and key are already configured as {id}; updating it instead of creating a duplicate", "zh-CN": "相同地址与密钥已配置为 {id};将更新该供应商,不会新建重复项" },
|
|
193
194
|
"add.updated": { en: "updated", "zh-CN": "已更新" },
|
|
194
195
|
"add.saved": { en: "{status} provider {id} ({protocol})", "zh-CN": "{status}供应商 {id} ({protocol})" },
|
|
195
196
|
"add.metadata": { en: "model metadata: {matched}/{total} models have a context limit", "zh-CN": "模型参数:{matched}/{total} 个模型已有上下文上限" },
|
|
@@ -245,6 +246,18 @@ const messages = {
|
|
|
245
246
|
"status.config": { en: "config: {file}", "zh-CN": "配置:{file}" },
|
|
246
247
|
"status.active": { en: "active provider: {id}", "zh-CN": "当前供应商:{id}" },
|
|
247
248
|
"status.none": { en: "(none)", "zh-CN": "(无)" },
|
|
249
|
+
"meta.gap": { en: "{count} model(s) have no catalog metadata (shown as \"-\")", "zh-CN": "{count} 个模型没有目录元数据(显示为 \"-\")" },
|
|
250
|
+
"meta.ambiguous": {
|
|
251
|
+
en: "ambiguous ({count}): {ids} — several creators list these; pin with --gateway-models '{\"{id}\":\"creator/model\"}'",
|
|
252
|
+
"zh-CN": "存在歧义({count}):{ids} —— 多个厂商都提供;用 --gateway-models '{\"{id}\":\"creator/model\"}' 指定",
|
|
253
|
+
},
|
|
254
|
+
"meta.unknown": { en: "not listed by any creator ({count}): {ids}", "zh-CN": "所有厂商均未收录({count}):{ids}" },
|
|
255
|
+
"meta.noCreatorRow": {
|
|
256
|
+
en: "no models.dev row for the creator ({count}): {ids} — filled from AI Gateway when available",
|
|
257
|
+
"zh-CN": "厂商在 models.dev 无对应条目({count}):{ids} —— 有 AI Gateway 数据时会自动补全",
|
|
258
|
+
},
|
|
259
|
+
"refresh.checked": { en: "checked model metadata ({changed} provider(s) changed)", "zh-CN": "已检查模型元数据({changed} 个供应商有变化)" },
|
|
260
|
+
"refresh.next": { en: "run `agentsw sync` to push updated metadata into app configs", "zh-CN": "运行 `agentsw sync` 将更新的元数据写入各智能体配置" },
|
|
248
261
|
};
|
|
249
262
|
let current = detectSystemLocale();
|
|
250
263
|
export function normalizeLocale(value) {
|
package/dist/metadata.js
CHANGED
|
@@ -105,25 +105,42 @@ function equalValue(a, b) {
|
|
|
105
105
|
function indexCatalog(catalog) {
|
|
106
106
|
const exact = new Map();
|
|
107
107
|
const canonical = new Map();
|
|
108
|
+
const identities = new Map();
|
|
109
|
+
const push = (map, key, entry) => {
|
|
110
|
+
const matches = map.get(key);
|
|
111
|
+
if (matches)
|
|
112
|
+
matches.push(entry);
|
|
113
|
+
else
|
|
114
|
+
map.set(key, [entry]);
|
|
115
|
+
};
|
|
108
116
|
for (const provider of Object.values(catalog)) {
|
|
109
117
|
for (const [key, model] of Object.entries(provider.models)) {
|
|
110
118
|
const modelId = model.id.includes("/") ? model.id : `${provider.id}/${model.id}`;
|
|
111
|
-
const entry = { provider: provider.id, modelId, model };
|
|
112
|
-
for (const id of new Set([key, model.id]))
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
else
|
|
117
|
-
exact.set(id, [entry]);
|
|
118
|
-
}
|
|
119
|
-
const matches = canonical.get(modelId);
|
|
120
|
-
if (matches)
|
|
121
|
-
matches.push(entry);
|
|
122
|
-
else
|
|
123
|
-
canonical.set(modelId, [entry]);
|
|
119
|
+
const entry = { provider: provider.id, modelId, identity: model.canonical_model_id ?? modelId, model };
|
|
120
|
+
for (const id of new Set([key, model.id]))
|
|
121
|
+
push(exact, id, entry);
|
|
122
|
+
push(canonical, modelId, entry);
|
|
123
|
+
push(identities, entry.identity, entry);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
return { exact, canonical };
|
|
126
|
+
return { exact, canonical, identities };
|
|
127
|
+
}
|
|
128
|
+
/** Creator identity of a catalog model id, or undefined when its rows disagree. */
|
|
129
|
+
function catalogIdentity(index, modelId) {
|
|
130
|
+
const identities = new Set(index?.canonical.get(modelId)?.map((entry) => entry.identity));
|
|
131
|
+
return identities?.size === 1 ? [...identities][0] : undefined;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The creator's own listing for an identity, when models.dev carries one.
|
|
135
|
+
* Gateways also key their rows by the canonical id, and those rows carry the
|
|
136
|
+
* gateway's own limits and prices, so only a row from the creator's own
|
|
137
|
+
* namespace counts as authoritative.
|
|
138
|
+
*/
|
|
139
|
+
function creatorEntry(index, identity) {
|
|
140
|
+
const slash = identity.indexOf("/");
|
|
141
|
+
if (slash < 1)
|
|
142
|
+
return undefined;
|
|
143
|
+
return index?.identities.get(identity)?.find((entry) => entry.modelId === identity && entry.provider === identity.slice(0, slash));
|
|
127
144
|
}
|
|
128
145
|
function indexGatewayBareIds(gateway) {
|
|
129
146
|
const result = new Map();
|
|
@@ -147,10 +164,15 @@ function selectExact(index, id, hint, aliases, gateway, gatewayBareIds) {
|
|
|
147
164
|
const candidate = gatewayBareIds?.get(id);
|
|
148
165
|
const evidence = index?.exact.get(id);
|
|
149
166
|
const hintedEvidence = hint ? evidence?.filter((entry) => entry.provider === hint) : undefined;
|
|
150
|
-
const identities = new Set((hintedEvidence?.length ? hintedEvidence : evidence)?.map((entry) => entry.
|
|
167
|
+
const identities = new Set((hintedEvidence?.length ? hintedEvidence : evidence)?.map((entry) => entry.identity));
|
|
168
|
+
// Gateway names the creator while models.dev names the listing provider, so
|
|
169
|
+
// the two agree only once both are reduced to a creator identity. A gateway
|
|
170
|
+
// id the catalog has no row for stands for itself.
|
|
171
|
+
const candidateIdentity = candidate ? catalogIdentity(index, candidate) ?? candidate : undefined;
|
|
151
172
|
// A unique exact basename is safe only with absent or agreeing primary evidence.
|
|
152
173
|
// Explicit provider hints can resolve primary ambiguity, but never Gateway ambiguity.
|
|
153
|
-
if (candidate &&
|
|
174
|
+
if (candidate && candidateIdentity !== undefined
|
|
175
|
+
&& (identities.size === 0 || (identities.size === 1 && identities.has(candidateIdentity))))
|
|
154
176
|
gatewayId = candidate;
|
|
155
177
|
}
|
|
156
178
|
}
|
|
@@ -159,10 +181,10 @@ function selectExact(index, id, hint, aliases, gateway, gatewayBareIds) {
|
|
|
159
181
|
: (bare && hint ? index?.canonical.get(`${hint}/${id}`) : undefined)
|
|
160
182
|
?? index?.canonical.get(id) ?? index?.exact.get(id);
|
|
161
183
|
const hinted = candidates?.find((entry) => entry.provider === hint);
|
|
162
|
-
const identities = new Set(candidates?.map((entry) => entry.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
184
|
+
const identities = new Set(candidates?.map((entry) => entry.identity));
|
|
185
|
+
// Reseller rows for one model share an identity but disagree on limits and
|
|
186
|
+
// prices, so only the creator's own row is authoritative.
|
|
187
|
+
const primary = hinted ?? (identities.size === 1 ? creatorEntry(index, [...identities][0]) : undefined);
|
|
166
188
|
return { primary, gatewayId, alias };
|
|
167
189
|
}
|
|
168
190
|
function legacyPrimary(catalog, id, hint) {
|
|
@@ -398,3 +420,30 @@ export async function enrichProviderModels(catalog, ids, settings, options = {})
|
|
|
398
420
|
// bounds must be resolved with the supplement against the original values.
|
|
399
421
|
return gateway ? enrich(gateway) : primaryOnly ?? enrich();
|
|
400
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Why a model id stayed bare after enrichment, so callers can tell the user
|
|
425
|
+
* what is worth acting on: "ambiguous" ids are listed under more than one
|
|
426
|
+
* creator and an explicit mapping resolves them; "unknown" ids no creator
|
|
427
|
+
* lists at all, so no mapping helps; "noCreatorRow" ids have one unambiguous
|
|
428
|
+
* creator but models.dev carries no authoritative row for it, so only Gateway
|
|
429
|
+
* can fill them.
|
|
430
|
+
*/
|
|
431
|
+
export function classifyUnresolved(catalog, ids, hint) {
|
|
432
|
+
const ambiguous = [];
|
|
433
|
+
const unknown = [];
|
|
434
|
+
const noCreatorRow = [];
|
|
435
|
+
if (!catalog)
|
|
436
|
+
return { ambiguous, unknown: [...ids], noCreatorRow };
|
|
437
|
+
const index = indexCatalog(catalog);
|
|
438
|
+
for (const id of ids) {
|
|
439
|
+
const hinted = hint ? index.canonical.get(`${hint}/${id}`) : undefined;
|
|
440
|
+
const candidates = hinted ?? index.canonical.get(id) ?? index.exact.get(id);
|
|
441
|
+
if (!candidates?.length)
|
|
442
|
+
unknown.push(id);
|
|
443
|
+
else if (new Set(candidates.map((entry) => entry.identity)).size > 1)
|
|
444
|
+
ambiguous.push(id);
|
|
445
|
+
else if (!creatorEntry(index, [...new Set(candidates.map((entry) => entry.identity))][0]))
|
|
446
|
+
noCreatorRow.push(id);
|
|
447
|
+
}
|
|
448
|
+
return { ambiguous, unknown, noCreatorRow };
|
|
449
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentsw",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "CLI provider switcher for coding agents: manage OpenAI/Anthropic-protocol providers in one place and sync them, with models.dev metadata, into Claude Code, Codex, omp, pi, prime-agent, opencode, Hermes, WorkBuddy and DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|