agentsw 0.10.1 → 0.11.0
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/README.md +102 -29
- package/README.zh-CN.md +91 -27
- package/dist/commands.js +266 -55
- package/dist/config-transaction.js +22 -17
- package/dist/i18n.js +29 -13
- package/dist/index.js +10 -3
- package/dist/menu.js +39 -8
- package/dist/targets/claudecode.js +2 -0
- package/dist/targets/codex.js +5 -1
- package/dist/targets/dsh.js +3 -2
- package/dist/targets/omp.js +4 -21
- package/dist/targets/pistyle.js +10 -28
- package/dist/targets/wire.js +65 -0
- package/dist/targets/workbuddy.js +7 -3
- package/package.json +1 -1
package/dist/menu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
2
|
import prompts from "prompts";
|
|
3
|
-
import { cmdAdd, cmdQuickAdd, cmdApps, cmdInstall,
|
|
3
|
+
import { cmdAdd, cmdQuickAdd, cmdApps, cmdInstall, cmdImport, cmdRefreshMeta, cmdList, cmdStatus, cmdSync, cmdUpgrade, cmdUse, } from "./commands.js";
|
|
4
4
|
import { getLocale, setLocale, t } from "./i18n.js";
|
|
5
5
|
import { getMetadataMode } from "./metadata.js";
|
|
6
6
|
import { loadStore, saveStore } from "./store.js";
|
|
@@ -66,6 +66,41 @@ async function pickProvider(message) {
|
|
|
66
66
|
const p = store.providers[id];
|
|
67
67
|
return { id, defaultModel: p.defaultModel, models: p.models.map((m) => m.id) };
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Multi-select providers for one sync. The first row takes every configured provider and maps to
|
|
71
|
+
* the CLI's `--all`; toggling everything off means "back", not "all", because a sync writes files.
|
|
72
|
+
*/
|
|
73
|
+
async function pickProviders(message) {
|
|
74
|
+
const store = loadStore();
|
|
75
|
+
const ids = Object.keys(store.providers);
|
|
76
|
+
if (ids.length === 0) {
|
|
77
|
+
out(pc.yellow(t("menu.noProvidersHint")));
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const { providers } = await prompts({
|
|
81
|
+
type: "multiselect",
|
|
82
|
+
name: "providers",
|
|
83
|
+
message,
|
|
84
|
+
hint: t("menu.appsHint"),
|
|
85
|
+
instructions: false,
|
|
86
|
+
choices: [
|
|
87
|
+
{ title: t("menu.providersAll", { count: ids.length }), value: "__all__" },
|
|
88
|
+
...ids.map((pid) => {
|
|
89
|
+
const p = store.providers[pid];
|
|
90
|
+
return {
|
|
91
|
+
title: `${pid} · ${p.protocol} · ${t("menu.defaultModel")} ${p.defaultModel}${store.active === pid ? ` (${t("menu.active")})` : ""}`,
|
|
92
|
+
value: pid,
|
|
93
|
+
selected: store.active === pid,
|
|
94
|
+
};
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
}, cancel);
|
|
98
|
+
const chosen = Array.isArray(providers) ? providers : [];
|
|
99
|
+
if (chosen.includes("__all__"))
|
|
100
|
+
return { all: true, ids };
|
|
101
|
+
const selected = chosen.filter((value) => value !== "__all__");
|
|
102
|
+
return selected.length ? { all: false, ids: selected } : undefined;
|
|
103
|
+
}
|
|
69
104
|
/**
|
|
70
105
|
* Multi-select target apps, remembering the previous choice. The first row selects
|
|
71
106
|
* every detected app; picking it (or toggling everything off) returns undefined,
|
|
@@ -212,7 +247,6 @@ export async function cmdMenu() {
|
|
|
212
247
|
{ title: t("menu.status"), description: t("menu.statusHelp"), value: "status" },
|
|
213
248
|
{ title: t("menu.list"), description: t("menu.listHelp"), value: "list" },
|
|
214
249
|
{ title: t("menu.sync"), description: t("menu.syncHelp"), value: "sync" },
|
|
215
|
-
{ title: t("menu.discover"), description: t("menu.discoverHelp"), value: "discover" },
|
|
216
250
|
{ title: t("menu.metadata"), description: t("menu.metadataHelp"), value: "metadata" },
|
|
217
251
|
{ title: t("menu.rename"), description: t("menu.renameHelp"), value: "rename" },
|
|
218
252
|
{ title: t("menu.remove"), description: t("menu.removeHelp"), value: "remove" },
|
|
@@ -271,14 +305,11 @@ export async function cmdMenu() {
|
|
|
271
305
|
cmdList();
|
|
272
306
|
}
|
|
273
307
|
else if (action === "sync") {
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
else if (action === "discover") {
|
|
277
|
-
const picked = await pickProvider(t("menu.discoverFor"));
|
|
308
|
+
const picked = await pickProviders(t("menu.syncFor"));
|
|
278
309
|
if (!picked)
|
|
279
310
|
continue;
|
|
280
|
-
const
|
|
281
|
-
await
|
|
311
|
+
const apps = await pickApps(t("menu.pickApps"));
|
|
312
|
+
await cmdSync(picked.all ? { all: true, apps } : { provider: picked.ids.join(","), apps });
|
|
282
313
|
}
|
|
283
314
|
else if (action === "metadata") {
|
|
284
315
|
const picked = await pickProvider(t("menu.metadataProvider"));
|
|
@@ -14,6 +14,8 @@ export const claudecode = transactionalTarget({
|
|
|
14
14
|
id: "claude",
|
|
15
15
|
name: "Claude Code",
|
|
16
16
|
protocols: ["anthropic"],
|
|
17
|
+
// The env block names one endpoint: a second provider replaces the first rather than joining it.
|
|
18
|
+
singleProvider: true,
|
|
17
19
|
get configPaths() {
|
|
18
20
|
return [claudeSettingsFile()];
|
|
19
21
|
},
|
package/dist/targets/codex.js
CHANGED
|
@@ -82,7 +82,11 @@ export const codex = transactionalTarget({
|
|
|
82
82
|
wire_api: "responses",
|
|
83
83
|
requires_openai_auth: true,
|
|
84
84
|
};
|
|
85
|
-
|
|
85
|
+
// Only worth saying when the endpoint has not declared itself Responses-compatible:
|
|
86
|
+
// a provider the user already marked `responses` needs no caveat.
|
|
87
|
+
if (provider.openaiApi !== "responses") {
|
|
88
|
+
notes.push("codex requires an OpenAI Responses-compatible endpoint (/v1/responses); chat-completions-only endpoints will not work");
|
|
89
|
+
}
|
|
86
90
|
config.model_providers = providers;
|
|
87
91
|
const configBackup = backupFile(configFile);
|
|
88
92
|
if (configBackup)
|
package/dist/targets/dsh.js
CHANGED
|
@@ -5,7 +5,7 @@ import { dshCredentialsFile, dshDir, dshSettingsFile } from "../app-paths.js";
|
|
|
5
5
|
import { isManagedCredentialRef, legacyManagedCredentialRef, managedCredentialRef } from "../provider-identity.js";
|
|
6
6
|
import { transactionalTarget } from "../target-transaction.js";
|
|
7
7
|
import { parseYamlMapping, serializeYamlMapping } from "../yaml.js";
|
|
8
|
-
import { apiValue, classifyApi, entryApi, mergeModels, sdkBaseUrl } from "./wire.js";
|
|
8
|
+
import { apiValue, classifyApi, entryApi, hasModelMetadata, mergeModels, sdkBaseUrl } from "./wire.js";
|
|
9
9
|
/** Layout version of `.credentials.yaml` this build reads and writes. */
|
|
10
10
|
const CREDENTIALS_VERSION = 1;
|
|
11
11
|
/** Thinking levels a `reasoningEfforts` entry may offer (llm-pi-ai THINKING_LEVEL_GATE). */
|
|
@@ -29,7 +29,8 @@ function modelEntry(m) {
|
|
|
29
29
|
...(m.name ? { name: m.name } : {}),
|
|
30
30
|
...(m.contextWindow ? { contextWindow: m.contextWindow } : {}),
|
|
31
31
|
...(m.maxOutput ? { maxTokens: m.maxOutput } : {}),
|
|
32
|
-
input
|
|
32
|
+
// `input` is a guess for a spec that says nothing; see hasModelMetadata.
|
|
33
|
+
...(hasModelMetadata(m) ? { input: m.imageInput ? ["text", "image"] : ["text"] } : {}),
|
|
33
34
|
...(m.reasoning === false
|
|
34
35
|
? { reasoningEfforts: false }
|
|
35
36
|
: m.reasoning && m.reasoningEfforts?.length
|
package/dist/targets/omp.js
CHANGED
|
@@ -5,9 +5,7 @@ import { ompAgentDir, ompModelsFiles } from "../app-paths.js";
|
|
|
5
5
|
import { looksLikeEnvName } from "../slug.js";
|
|
6
6
|
import { transactionalTarget } from "../target-transaction.js";
|
|
7
7
|
import { parseYamlMapping, serializeYamlMapping } from "../yaml.js";
|
|
8
|
-
import { apiValue, classifyApi, entryApi, mergeModels, sdkBaseUrl, stripConflictingOverrides } from "./wire.js";
|
|
9
|
-
/** Per-model keys this adapter writes; one that stops being emitted is cleared, not inherited. */
|
|
10
|
-
const OWNED_MODEL_KEYS = ["id", "name", "reasoning", "input", "contextWindow", "maxTokens", "cost"];
|
|
8
|
+
import { apiValue, applyDeepseekCompat, classifyApi, entryApi, mergeModels, OWNED_MODEL_METADATA_KEYS, ownedModelMetadata, sdkBaseUrl, stripConflictingOverrides, } from "./wire.js";
|
|
11
9
|
/** omp has no directory override; the file set is shared with remove/rename. */
|
|
12
10
|
function modelFiles() {
|
|
13
11
|
const [yml, yaml] = ompModelsFiles();
|
|
@@ -43,27 +41,12 @@ export const omp = transactionalTarget({
|
|
|
43
41
|
const anthropic = provider.protocol === "anthropic";
|
|
44
42
|
const api = apiValue(provider.protocol, provider.openaiApi, prev?.api ?? entryApi(prev ?? {}));
|
|
45
43
|
const baseUrl = sdkBaseUrl(provider.protocol, provider.baseUrl);
|
|
46
|
-
const models = mergeModels(prev?.models, provider.models.map(
|
|
47
|
-
id: m.id,
|
|
48
|
-
...(m.name ? { name: m.name } : {}),
|
|
49
|
-
...(m.reasoning !== undefined ? { reasoning: m.reasoning } : {}),
|
|
50
|
-
input: m.imageInput ? ["text", "image"] : ["text"],
|
|
51
|
-
...(m.contextWindow ? { contextWindow: m.contextWindow } : {}),
|
|
52
|
-
...(m.maxOutput ? { maxTokens: m.maxOutput } : {}),
|
|
53
|
-
...(m.cost
|
|
54
|
-
? {
|
|
55
|
-
cost: {
|
|
56
|
-
input: m.cost.input ?? 0,
|
|
57
|
-
output: m.cost.output ?? 0,
|
|
58
|
-
cacheRead: m.cost.cacheRead ?? 0,
|
|
59
|
-
cacheWrite: m.cost.cacheWrite ?? 0,
|
|
60
|
-
},
|
|
61
|
-
}
|
|
62
|
-
: {}),
|
|
63
|
-
})), OWNED_MODEL_KEYS);
|
|
44
|
+
const models = mergeModels(prev?.models, provider.models.map(ownedModelMetadata), OWNED_MODEL_METADATA_KEYS);
|
|
64
45
|
const conflicts = stripConflictingOverrides(models, api, baseUrl);
|
|
65
46
|
if (conflicts.length)
|
|
66
47
|
notes.push(`dropped model overrides pointing elsewhere: ${conflicts.join(", ")}`);
|
|
48
|
+
if (!anthropic)
|
|
49
|
+
applyDeepseekCompat(models, { requiresReasoningContentForToolCalls: true });
|
|
67
50
|
const entry = {
|
|
68
51
|
baseUrl,
|
|
69
52
|
apiKey: provider.apiKey, // omp treats value as env-var name first, then literal
|
package/dist/targets/pistyle.js
CHANGED
|
@@ -5,18 +5,9 @@ import { piDir, piModelsFile, piSettingsFile } from "../app-paths.js";
|
|
|
5
5
|
import { editJsoncObject, isJsonObject, readJsoncObject } from "../jsonc.js";
|
|
6
6
|
import { looksLikeEnvName } from "../slug.js";
|
|
7
7
|
import { transactionalTarget } from "../target-transaction.js";
|
|
8
|
-
import { apiValue, classifyApi, entryApi, mergeModels, sdkBaseUrl, stripConflictingOverrides } from "./wire.js";
|
|
9
|
-
/**
|
|
10
|
-
const OWNED_MODEL_KEYS = [
|
|
11
|
-
"id",
|
|
12
|
-
"name",
|
|
13
|
-
"reasoning",
|
|
14
|
-
"thinkingLevelMap",
|
|
15
|
-
"input",
|
|
16
|
-
"contextWindow",
|
|
17
|
-
"maxTokens",
|
|
18
|
-
"cost",
|
|
19
|
-
];
|
|
8
|
+
import { apiValue, applyDeepseekCompat, classifyApi, entryApi, mergeModels, OWNED_MODEL_METADATA_KEYS, ownedModelMetadata, sdkBaseUrl, stripConflictingOverrides, } from "./wire.js";
|
|
9
|
+
/** pi writes the shared metadata plus its own thinking-level map. */
|
|
10
|
+
const OWNED_MODEL_KEYS = [...OWNED_MODEL_METADATA_KEYS, "thinkingLevelMap"];
|
|
20
11
|
function readModels(file) {
|
|
21
12
|
const document = readJsoncObject(file);
|
|
22
13
|
const providers = document.value.providers;
|
|
@@ -84,26 +75,17 @@ export function piStyleTarget(opts) {
|
|
|
84
75
|
const api = apiValue(provider.protocol, provider.openaiApi, prev.api ?? entryApi(prev));
|
|
85
76
|
const baseUrl = sdkBaseUrl(provider.protocol, provider.baseUrl);
|
|
86
77
|
const models = mergeModels(prev.models, provider.models.map((m) => ({
|
|
87
|
-
|
|
88
|
-
...(m.name ? { name: m.name } : {}),
|
|
89
|
-
...(m.reasoning !== undefined ? { reasoning: m.reasoning } : {}),
|
|
78
|
+
...ownedModelMetadata(m),
|
|
90
79
|
...(m.reasoning && m.reasoningEfforts?.length
|
|
91
80
|
? { thinkingLevelMap: thinkingLevelMap(m.reasoningEfforts) }
|
|
92
81
|
: {}),
|
|
93
|
-
input: m.imageInput ? ["text", "image"] : ["text"],
|
|
94
|
-
...(m.contextWindow ? { contextWindow: m.contextWindow } : {}),
|
|
95
|
-
...(m.maxOutput ? { maxTokens: m.maxOutput } : {}),
|
|
96
|
-
...(m.cost
|
|
97
|
-
? {
|
|
98
|
-
cost: {
|
|
99
|
-
input: m.cost.input ?? 0,
|
|
100
|
-
output: m.cost.output ?? 0,
|
|
101
|
-
cacheRead: m.cost.cacheRead ?? 0,
|
|
102
|
-
cacheWrite: m.cost.cacheWrite ?? 0,
|
|
103
|
-
},
|
|
104
|
-
}
|
|
105
|
-
: {}),
|
|
106
82
|
})), OWNED_MODEL_KEYS);
|
|
83
|
+
// pi/prime key their DeepSeek wire rules by provider name, so a gateway
|
|
84
|
+
// entry never picks them up; the flags below are what their own deepseek
|
|
85
|
+
// catalog entries carry. Anthropic-wire entries replay differently.
|
|
86
|
+
if (provider.protocol === "openai") {
|
|
87
|
+
applyDeepseekCompat(models, { requiresReasoningContentOnAssistantMessages: true, thinkingFormat: "deepseek" });
|
|
88
|
+
}
|
|
107
89
|
const conflicts = stripConflictingOverrides(models, api, baseUrl);
|
|
108
90
|
if (conflicts.length)
|
|
109
91
|
notes.push(`dropped model overrides pointing elsewhere: ${conflicts.join(", ")}`);
|
package/dist/targets/wire.js
CHANGED
|
@@ -99,6 +99,11 @@ derived = ["input"]) {
|
|
|
99
99
|
const old = prev.get(m.id);
|
|
100
100
|
if (!old)
|
|
101
101
|
return m;
|
|
102
|
+
// An entry that carries nothing but its id says nothing about the model: it
|
|
103
|
+
// is no evidence that the config's metadata went stale, so clearing owned
|
|
104
|
+
// keys on its behalf would delete what a richer sync or the user put there.
|
|
105
|
+
if (Object.keys(m).length === 1)
|
|
106
|
+
return { ...m, ...old };
|
|
102
107
|
const kept = { ...old };
|
|
103
108
|
const known = informative.some((key) => key in m);
|
|
104
109
|
if (known)
|
|
@@ -108,6 +113,66 @@ derived = ["input"]) {
|
|
|
108
113
|
return { ...kept, ...m };
|
|
109
114
|
});
|
|
110
115
|
}
|
|
116
|
+
/** Per-model keys every pi-family adapter owns; see `ownedModelMetadata`. */
|
|
117
|
+
export const OWNED_MODEL_METADATA_KEYS = ["id", "name", "reasoning", "input", "contextWindow", "maxTokens", "cost"];
|
|
118
|
+
const DEEPSEEK_MODEL_ID = /deepseek/i;
|
|
119
|
+
/**
|
|
120
|
+
* omp/pi/prime key their DeepSeek wire rules by provider name ("deepseek",
|
|
121
|
+
* "opencode-go", ...), so a gateway or reseller entry matches none of them: its
|
|
122
|
+
* reasoning replay then breaks and DeepSeek answers 400. Match on the model id
|
|
123
|
+
* instead and write the adapter's own flags per model; an explicit user value wins.
|
|
124
|
+
*/
|
|
125
|
+
export function applyDeepseekCompat(models, flags) {
|
|
126
|
+
for (const model of models) {
|
|
127
|
+
if (!DEEPSEEK_MODEL_ID.test(String(model.id)))
|
|
128
|
+
continue;
|
|
129
|
+
const compat = (model.compat ?? {});
|
|
130
|
+
model.compat = { ...flags, ...compat };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Whether a store entry describes the model at all. A bare id is ignorance,
|
|
135
|
+
* not a catalog statement, so adapters must not clear what the app config
|
|
136
|
+
* already holds on its behalf.
|
|
137
|
+
*/
|
|
138
|
+
export function hasModelMetadata(m) {
|
|
139
|
+
return (m.name !== undefined ||
|
|
140
|
+
m.reasoning !== undefined ||
|
|
141
|
+
m.reasoningEfforts !== undefined ||
|
|
142
|
+
m.imageInput !== undefined ||
|
|
143
|
+
m.contextWindow !== undefined ||
|
|
144
|
+
m.maxInput !== undefined ||
|
|
145
|
+
m.maxOutput !== undefined ||
|
|
146
|
+
m.cost !== undefined);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The per-model metadata agentsw writes into a pi-family models config. omp and
|
|
150
|
+
* the pi/prime adapters share the shape; only the extras around it differ, so
|
|
151
|
+
* keeping one builder is what stops the two from drifting apart.
|
|
152
|
+
*/
|
|
153
|
+
export function ownedModelMetadata(m) {
|
|
154
|
+
// `input` is a guess for a spec that says nothing; writing it would make the
|
|
155
|
+
// entry look authoritative to mergeModels and clear the config's own values.
|
|
156
|
+
const known = hasModelMetadata(m);
|
|
157
|
+
return {
|
|
158
|
+
id: m.id,
|
|
159
|
+
...(m.name ? { name: m.name } : {}),
|
|
160
|
+
...(m.reasoning !== undefined ? { reasoning: m.reasoning } : {}),
|
|
161
|
+
...(known ? { input: m.imageInput ? ["text", "image"] : ["text"] } : {}),
|
|
162
|
+
...(m.contextWindow ? { contextWindow: m.contextWindow } : {}),
|
|
163
|
+
...(m.maxOutput ? { maxTokens: m.maxOutput } : {}),
|
|
164
|
+
...(m.cost
|
|
165
|
+
? {
|
|
166
|
+
cost: {
|
|
167
|
+
input: m.cost.input ?? 0,
|
|
168
|
+
output: m.cost.output ?? 0,
|
|
169
|
+
cacheRead: m.cost.cacheRead ?? 0,
|
|
170
|
+
cacheWrite: m.cost.cacheWrite ?? 0,
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
: {}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
111
176
|
/**
|
|
112
177
|
* Drop per-model `api`/`baseUrl` overrides that contradict the route being
|
|
113
178
|
* written. A model-level override wins over the provider entry in omp and pi,
|
|
@@ -6,6 +6,7 @@ import { workbuddyDir } from "../app-paths.js";
|
|
|
6
6
|
import { localProviderId } from "../provider-identity.js";
|
|
7
7
|
import { providerIdFromBaseUrl, providerNameFromBaseUrl } from "../slug.js";
|
|
8
8
|
import { transactionalTarget } from "../target-transaction.js";
|
|
9
|
+
import { hasModelMetadata } from "./wire.js";
|
|
9
10
|
function configDir() {
|
|
10
11
|
return workbuddyDir();
|
|
11
12
|
}
|
|
@@ -101,12 +102,15 @@ export const workbuddy = transactionalTarget({
|
|
|
101
102
|
const previous = new Map(owned.map((row) => [row.id, row]));
|
|
102
103
|
const ours = provider.models.map((model) => {
|
|
103
104
|
const custom = { ...previous.get(model.id) };
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
// A bare id says nothing about the model; the row keeps what it already has.
|
|
106
|
+
const known = hasModelMetadata(model);
|
|
107
|
+
if (known)
|
|
108
|
+
for (const key of OWNED_MODEL_KEYS)
|
|
109
|
+
delete custom[key];
|
|
106
110
|
return {
|
|
107
111
|
...custom,
|
|
108
112
|
id: model.id,
|
|
109
|
-
name: model.name ?? model.id,
|
|
113
|
+
name: model.name ?? (known ? undefined : custom.name) ?? model.id,
|
|
110
114
|
vendor: provider.name,
|
|
111
115
|
url,
|
|
112
116
|
apiKey: provider.apiKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentsw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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": {
|