@tokenbuddy/tokenbuddy 1.0.5 → 1.0.7
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/src/buyer-store.d.ts +48 -1
- package/dist/src/buyer-store.d.ts.map +1 -1
- package/dist/src/buyer-store.js +144 -17
- package/dist/src/buyer-store.js.map +1 -1
- package/dist/src/cli.d.ts +17 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +560 -63
- package/dist/src/cli.js.map +1 -1
- package/dist/src/daemon.d.ts +11 -5
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +574 -161
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/doctor-clawtip-wallet.d.ts +14 -0
- package/dist/src/doctor-clawtip-wallet.d.ts.map +1 -0
- package/dist/src/doctor-clawtip-wallet.js +54 -0
- package/dist/src/doctor-clawtip-wallet.js.map +1 -0
- package/dist/src/doctor-diagnostics.d.ts +99 -0
- package/dist/src/doctor-diagnostics.d.ts.map +1 -0
- package/dist/src/doctor-diagnostics.js +552 -0
- package/dist/src/doctor-diagnostics.js.map +1 -0
- package/dist/src/init-clawtip-activation.d.ts +48 -0
- package/dist/src/init-clawtip-activation.d.ts.map +1 -0
- package/dist/src/init-clawtip-activation.js +395 -0
- package/dist/src/init-clawtip-activation.js.map +1 -0
- package/dist/src/init-payment-options.d.ts +56 -0
- package/dist/src/init-payment-options.d.ts.map +1 -0
- package/dist/src/init-payment-options.js +165 -0
- package/dist/src/init-payment-options.js.map +1 -0
- package/dist/src/provider-install.d.ts +37 -2
- package/dist/src/provider-install.d.ts.map +1 -1
- package/dist/src/provider-install.js +317 -67
- package/dist/src/provider-install.js.map +1 -1
- package/dist/src/seller-catalog.d.ts +79 -0
- package/dist/src/seller-catalog.d.ts.map +1 -0
- package/dist/src/seller-catalog.js +126 -0
- package/dist/src/seller-catalog.js.map +1 -0
- package/dist/src/tb-proxyd.js +13 -2
- package/dist/src/tb-proxyd.js.map +1 -1
- package/dist/src/terminal-image.d.ts +22 -0
- package/dist/src/terminal-image.d.ts.map +1 -0
- package/dist/src/terminal-image.js +135 -0
- package/dist/src/terminal-image.js.map +1 -0
- package/package.json +1 -1
- package/src/buyer-store.ts +253 -18
- package/src/cli.ts +709 -68
- package/src/daemon.ts +651 -167
- package/src/doctor-clawtip-wallet.ts +70 -0
- package/src/doctor-diagnostics.ts +861 -0
- package/src/init-clawtip-activation.ts +487 -0
- package/src/init-payment-options.ts +249 -0
- package/src/provider-install.ts +426 -76
- package/src/seller-catalog.ts +222 -0
- package/src/tb-proxyd.ts +14 -2
- package/src/terminal-image.ts +187 -0
- package/tests/e2e.test.ts +88 -5
- package/tests/tokenbuddy.test.ts +1362 -27
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import { BuyerStore } from "./buyer-store.js";
|
|
2
|
-
|
|
2
|
+
import { ProtocolPreference, SellerRoutingPreference } from "./seller-catalog.js";
|
|
3
|
+
export declare const PROXY_ACCESS_TOKEN_PLACEHOLDER = "TOKENBUDDY_PROXY";
|
|
4
|
+
export declare const SUPPORTED_PROVIDER_IDS: readonly ["codex", "claude-code", "claude-desktop", "openclaw", "opencode", "hermes"];
|
|
3
5
|
export type ProviderId = typeof SUPPORTED_PROVIDER_IDS[number];
|
|
6
|
+
export type ModelSelectionKind = "single-model" | "claude-role-mapping";
|
|
4
7
|
export interface ProviderDetectOptions {
|
|
5
8
|
home?: string;
|
|
6
9
|
}
|
|
10
|
+
export interface SingleModelProviderRuntimeConfig {
|
|
11
|
+
selectionKind: "single-model";
|
|
12
|
+
protocolPreference?: ProtocolPreference;
|
|
13
|
+
defaultModel: string;
|
|
14
|
+
sellerId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ClaudeRoleBinding {
|
|
17
|
+
upstreamModel: string;
|
|
18
|
+
displayName?: string;
|
|
19
|
+
declareOneM?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ClaudeCodeModelMappingConfig {
|
|
22
|
+
selectionKind: "claude-role-mapping";
|
|
23
|
+
protocolPreference?: ProtocolPreference;
|
|
24
|
+
fallbackModel?: string;
|
|
25
|
+
roles: {
|
|
26
|
+
haiku?: ClaudeRoleBinding;
|
|
27
|
+
sonnet?: ClaudeRoleBinding;
|
|
28
|
+
opus?: ClaudeRoleBinding;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export type ProviderRuntimeConfig = SingleModelProviderRuntimeConfig | ClaudeCodeModelMappingConfig;
|
|
32
|
+
export type ProviderSelections = Partial<Record<ProviderId, ProviderRuntimeConfig>>;
|
|
7
33
|
export interface ProviderInstallOptions extends ProviderDetectOptions {
|
|
8
34
|
providers: string[];
|
|
9
35
|
proxyUrl: string;
|
|
10
|
-
model
|
|
36
|
+
model?: string;
|
|
37
|
+
providerSelections?: ProviderSelections;
|
|
38
|
+
sellerRouting?: SellerRoutingPreference;
|
|
11
39
|
}
|
|
12
40
|
export interface ProviderRollbackOptions extends ProviderDetectOptions {
|
|
13
41
|
providers: string[];
|
|
@@ -16,7 +44,12 @@ export interface ProviderCandidate {
|
|
|
16
44
|
id: ProviderId;
|
|
17
45
|
name: string;
|
|
18
46
|
detected: boolean;
|
|
47
|
+
configured: boolean;
|
|
48
|
+
status: "configured" | "installed" | "missing";
|
|
19
49
|
configPath: string;
|
|
50
|
+
commandName?: string;
|
|
51
|
+
executablePath?: string;
|
|
52
|
+
observedPaths?: string[];
|
|
20
53
|
reason: string;
|
|
21
54
|
}
|
|
22
55
|
export interface ProviderFileChange {
|
|
@@ -38,6 +71,8 @@ export interface ProviderRollbackResult {
|
|
|
38
71
|
action: "restored" | "removed" | "missing_snapshot";
|
|
39
72
|
}
|
|
40
73
|
export declare function detectProviders(options?: ProviderDetectOptions): ProviderCandidate[];
|
|
74
|
+
export declare function getProviderProtocolPreference(providerId: ProviderId): ProtocolPreference | undefined;
|
|
75
|
+
export declare function getProviderModelSelectionKind(providerId: ProviderId): ModelSelectionKind;
|
|
41
76
|
export declare function previewProviderInstall(options: ProviderInstallOptions): ProviderFileChange[];
|
|
42
77
|
export declare function applyProviderInstall(options: ProviderInstallOptions, store: BuyerStore): ProviderApplyResult[];
|
|
43
78
|
export declare function rollbackProviderInstall(options: ProviderRollbackOptions, store: BuyerStore): ProviderRollbackResult[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-install.d.ts","sourceRoot":"","sources":["../../src/provider-install.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"provider-install.d.ts","sourceRoot":"","sources":["../../src/provider-install.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as os from "os";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
const
|
|
4
|
+
export const PROXY_ACCESS_TOKEN_PLACEHOLDER = "TOKENBUDDY_PROXY";
|
|
5
5
|
const DESKTOP_PROFILE_ID = "00000000-0000-4000-8000-000000178210";
|
|
6
|
+
const CLAUDE_ONE_M_MARKER = "[1M]";
|
|
7
|
+
const CLAUDE_CLIENT_HAIKU_MODEL = "claude-haiku-4-5";
|
|
8
|
+
const CLAUDE_CLIENT_SONNET_MODEL = "claude-sonnet-4-6";
|
|
9
|
+
const CLAUDE_CLIENT_OPUS_MODEL = "claude-opus-4-7";
|
|
10
|
+
const ROUTING_CONFIG_KEY = "routing";
|
|
6
11
|
export const SUPPORTED_PROVIDER_IDS = [
|
|
7
12
|
"codex",
|
|
8
13
|
"claude-code",
|
|
9
14
|
"claude-desktop",
|
|
10
15
|
"openclaw",
|
|
11
|
-
"
|
|
16
|
+
"opencode",
|
|
17
|
+
"hermes",
|
|
12
18
|
];
|
|
13
19
|
function resolveHome(home) {
|
|
14
20
|
return home && home.trim() ? home : os.homedir();
|
|
@@ -55,6 +61,31 @@ function readJsonObject(filePath) {
|
|
|
55
61
|
function jsonContent(value) {
|
|
56
62
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
57
63
|
}
|
|
64
|
+
function displayPath(home, filePath) {
|
|
65
|
+
return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath;
|
|
66
|
+
}
|
|
67
|
+
function resolveExecutable(commandName) {
|
|
68
|
+
const pathValue = process.env.PATH || "";
|
|
69
|
+
const pathEntries = pathValue.split(path.delimiter).filter(Boolean);
|
|
70
|
+
const windowsExts = process.platform === "win32"
|
|
71
|
+
? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM")
|
|
72
|
+
.split(";")
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
: [""];
|
|
75
|
+
for (const entry of pathEntries) {
|
|
76
|
+
for (const ext of windowsExts) {
|
|
77
|
+
const candidate = path.join(entry, process.platform === "win32" ? `${commandName}${ext}` : commandName);
|
|
78
|
+
try {
|
|
79
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
80
|
+
return candidate;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
58
89
|
function escapeTomlString(value) {
|
|
59
90
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
60
91
|
}
|
|
@@ -67,32 +98,154 @@ function replaceTomlSection(existing, sectionName, sectionBody) {
|
|
|
67
98
|
}
|
|
68
99
|
return `${normalized}${normalized ? "\n\n" : ""}${nextSection}`;
|
|
69
100
|
}
|
|
70
|
-
function
|
|
101
|
+
function stripClaudeOneMMarker(model) {
|
|
102
|
+
const trimmed = model.trimEnd();
|
|
103
|
+
if (!trimmed.toLowerCase().endsWith(CLAUDE_ONE_M_MARKER.toLowerCase())) {
|
|
104
|
+
return model;
|
|
105
|
+
}
|
|
106
|
+
return trimmed.slice(0, -CLAUDE_ONE_M_MARKER.length).trimEnd();
|
|
107
|
+
}
|
|
108
|
+
function setClaudeOneMMarker(model, enabled) {
|
|
109
|
+
const base = stripClaudeOneMMarker(model).trim();
|
|
110
|
+
if (!base) {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
return enabled ? `${base}${CLAUDE_ONE_M_MARKER}` : base;
|
|
114
|
+
}
|
|
115
|
+
function makeChange(providerId, filePath, summary, content) {
|
|
116
|
+
const existed = fs.existsSync(filePath);
|
|
117
|
+
return {
|
|
118
|
+
providerId,
|
|
119
|
+
path: filePath,
|
|
120
|
+
action: existed ? "update" : "create",
|
|
121
|
+
existed,
|
|
122
|
+
summary,
|
|
123
|
+
content,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function makeSingleModelRuntimeConfig(provider, model, sellerId) {
|
|
127
|
+
return {
|
|
128
|
+
selectionKind: "single-model",
|
|
129
|
+
protocolPreference: provider.protocolPreference,
|
|
130
|
+
defaultModel: model,
|
|
131
|
+
sellerId,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function pickConfiguredModel(config) {
|
|
135
|
+
if (config.selectionKind === "single-model") {
|
|
136
|
+
return config.defaultModel;
|
|
137
|
+
}
|
|
138
|
+
const sonnetModel = config.roles.sonnet?.upstreamModel;
|
|
139
|
+
const opusModel = config.roles.opus?.upstreamModel;
|
|
140
|
+
const haikuModel = config.roles.haiku?.upstreamModel;
|
|
141
|
+
return sonnetModel || opusModel || haikuModel || config.fallbackModel || "";
|
|
142
|
+
}
|
|
143
|
+
function resolveProviderRuntimeConfig(provider, options) {
|
|
144
|
+
const selection = options.providerSelections?.[provider.id];
|
|
145
|
+
if (selection) {
|
|
146
|
+
return selection;
|
|
147
|
+
}
|
|
148
|
+
const model = options.model?.trim();
|
|
149
|
+
if (!model) {
|
|
150
|
+
throw new Error(`model is required for provider ${provider.id}`);
|
|
151
|
+
}
|
|
152
|
+
return makeSingleModelRuntimeConfig(provider, model, options.sellerRouting?.mode === "fixed" ? options.sellerRouting.sellerId : undefined);
|
|
153
|
+
}
|
|
154
|
+
function codexConfig(home, proxyUrl, config) {
|
|
155
|
+
const model = pickConfiguredModel(config);
|
|
71
156
|
const configPath = path.join(home, ".codex", "config.toml");
|
|
72
157
|
const existing = readText(configPath) || "";
|
|
73
158
|
const content = replaceTomlSection(existing, "tokenbuddy", [
|
|
74
159
|
`proxy_url = "${escapeTomlString(proxyUrl)}"`,
|
|
75
|
-
`api_key = "${
|
|
76
|
-
`model = "${escapeTomlString(model)}"
|
|
160
|
+
`api_key = "${PROXY_ACCESS_TOKEN_PLACEHOLDER}"`,
|
|
161
|
+
`model = "${escapeTomlString(model)}"`,
|
|
77
162
|
].join("\n"));
|
|
78
163
|
return [makeChange("codex", configPath, "configure TokenBuddy proxy for Codex", content)];
|
|
79
164
|
}
|
|
80
|
-
function
|
|
165
|
+
function resolveClaudeFallbackAlias(config) {
|
|
166
|
+
if (config.roles.sonnet?.upstreamModel) {
|
|
167
|
+
return setClaudeOneMMarker(CLAUDE_CLIENT_SONNET_MODEL, Boolean(config.roles.sonnet.declareOneM));
|
|
168
|
+
}
|
|
169
|
+
if (config.roles.opus?.upstreamModel) {
|
|
170
|
+
return setClaudeOneMMarker(CLAUDE_CLIENT_OPUS_MODEL, Boolean(config.roles.opus.declareOneM));
|
|
171
|
+
}
|
|
172
|
+
if (config.roles.haiku?.upstreamModel) {
|
|
173
|
+
return CLAUDE_CLIENT_HAIKU_MODEL;
|
|
174
|
+
}
|
|
175
|
+
return CLAUDE_CLIENT_SONNET_MODEL;
|
|
176
|
+
}
|
|
177
|
+
function buildClaudeRoleEnv(config) {
|
|
178
|
+
const env = {};
|
|
179
|
+
const roleConfigs = [
|
|
180
|
+
{
|
|
181
|
+
binding: config.roles.haiku,
|
|
182
|
+
modelKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
183
|
+
displayNameKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME",
|
|
184
|
+
alias: CLAUDE_CLIENT_HAIKU_MODEL,
|
|
185
|
+
allowOneM: false,
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
binding: config.roles.sonnet,
|
|
189
|
+
modelKey: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
190
|
+
displayNameKey: "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME",
|
|
191
|
+
alias: CLAUDE_CLIENT_SONNET_MODEL,
|
|
192
|
+
allowOneM: true,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
binding: config.roles.opus,
|
|
196
|
+
modelKey: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
197
|
+
displayNameKey: "ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
|
|
198
|
+
alias: CLAUDE_CLIENT_OPUS_MODEL,
|
|
199
|
+
allowOneM: true,
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
for (const role of roleConfigs) {
|
|
203
|
+
if (!role.binding?.upstreamModel?.trim()) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
env[role.modelKey] = role.allowOneM
|
|
207
|
+
? setClaudeOneMMarker(role.alias, Boolean(role.binding.declareOneM))
|
|
208
|
+
: role.alias;
|
|
209
|
+
const displayName = role.binding.displayName?.trim() || stripClaudeOneMMarker(role.binding.upstreamModel).trim();
|
|
210
|
+
if (displayName) {
|
|
211
|
+
env[role.displayNameKey] = displayName;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
env.ANTHROPIC_MODEL = resolveClaudeFallbackAlias(config);
|
|
215
|
+
return env;
|
|
216
|
+
}
|
|
217
|
+
function claudeCodeConfig(home, proxyUrl, config) {
|
|
81
218
|
const configPath = path.join(home, ".claude", "settings.json");
|
|
82
|
-
const
|
|
83
|
-
const env =
|
|
84
|
-
?
|
|
219
|
+
const current = readJsonObject(configPath);
|
|
220
|
+
const env = current.env && typeof current.env === "object" && !Array.isArray(current.env)
|
|
221
|
+
? current.env
|
|
85
222
|
: {};
|
|
86
|
-
|
|
223
|
+
const nextEnv = {
|
|
87
224
|
...env,
|
|
88
225
|
ANTHROPIC_BASE_URL: proxyUrl,
|
|
89
|
-
ANTHROPIC_AUTH_TOKEN:
|
|
90
|
-
ANTHROPIC_MODEL: model,
|
|
91
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: model
|
|
226
|
+
ANTHROPIC_AUTH_TOKEN: PROXY_ACCESS_TOKEN_PLACEHOLDER,
|
|
92
227
|
};
|
|
93
|
-
|
|
228
|
+
delete nextEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
229
|
+
delete nextEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME;
|
|
230
|
+
delete nextEnv.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
231
|
+
delete nextEnv.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME;
|
|
232
|
+
delete nextEnv.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
233
|
+
delete nextEnv.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME;
|
|
234
|
+
delete nextEnv.ANTHROPIC_MODEL;
|
|
235
|
+
if (config.selectionKind === "claude-role-mapping") {
|
|
236
|
+
Object.assign(nextEnv, buildClaudeRoleEnv(config));
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
nextEnv.ANTHROPIC_MODEL = config.defaultModel;
|
|
240
|
+
nextEnv.ANTHROPIC_DEFAULT_SONNET_MODEL = config.defaultModel;
|
|
241
|
+
}
|
|
242
|
+
current.env = nextEnv;
|
|
243
|
+
return [
|
|
244
|
+
makeChange("claude-code", configPath, "configure Anthropic proxy env for Claude Code", jsonContent(current)),
|
|
245
|
+
];
|
|
94
246
|
}
|
|
95
|
-
function claudeDesktopConfig(home, proxyUrl,
|
|
247
|
+
function claudeDesktopConfig(home, proxyUrl, config) {
|
|
248
|
+
const model = pickConfiguredModel(config);
|
|
96
249
|
const configDir = path.join(home, "Library", "Application Support", "Claude");
|
|
97
250
|
const configPath = path.join(configDir, "claude_desktop_config.json");
|
|
98
251
|
const threepDir = path.join(home, "Library", "Application Support", "Claude-3p");
|
|
@@ -106,125 +259,212 @@ function claudeDesktopConfig(home, proxyUrl, model) {
|
|
|
106
259
|
threep.deploymentMode = "3p";
|
|
107
260
|
const profile = {
|
|
108
261
|
disableDeploymentModeChooser: true,
|
|
109
|
-
inferenceGatewayApiKey:
|
|
262
|
+
inferenceGatewayApiKey: PROXY_ACCESS_TOKEN_PLACEHOLDER,
|
|
110
263
|
inferenceGatewayAuthScheme: "bearer",
|
|
111
264
|
inferenceGatewayBaseUrl: proxyUrl,
|
|
112
265
|
inferenceProvider: "gateway",
|
|
113
|
-
inferenceModels: [{ name: model }]
|
|
266
|
+
inferenceModels: [{ name: model }],
|
|
114
267
|
};
|
|
115
268
|
const meta = readJsonObject(metaPath);
|
|
116
269
|
const existingEntries = Array.isArray(meta.entries) ? meta.entries : [];
|
|
117
270
|
meta.appliedId = DESKTOP_PROFILE_ID;
|
|
118
271
|
meta.entries = [
|
|
119
|
-
...existingEntries.filter((entry) =>
|
|
120
|
-
|
|
121
|
-
}),
|
|
122
|
-
{ id: DESKTOP_PROFILE_ID, name: "TokenBuddy" }
|
|
272
|
+
...existingEntries.filter((entry) => !(entry && typeof entry === "object" && "id" in entry && entry.id === DESKTOP_PROFILE_ID)),
|
|
273
|
+
{ id: DESKTOP_PROFILE_ID, name: "TokenBuddy" },
|
|
123
274
|
];
|
|
124
275
|
return [
|
|
125
276
|
makeChange("claude-desktop", configPath, "enable Claude Desktop 3p deployment mode", jsonContent(primary)),
|
|
126
277
|
makeChange("claude-desktop", threepConfigPath, "enable Claude Desktop 3p config", jsonContent(threep)),
|
|
127
278
|
makeChange("claude-desktop", profilePath, "write TokenBuddy Claude Desktop profile", jsonContent(profile)),
|
|
128
|
-
makeChange("claude-desktop", metaPath, "select TokenBuddy Claude Desktop profile", jsonContent(meta))
|
|
279
|
+
makeChange("claude-desktop", metaPath, "select TokenBuddy Claude Desktop profile", jsonContent(meta)),
|
|
129
280
|
];
|
|
130
281
|
}
|
|
131
|
-
function openclawConfig(home, proxyUrl,
|
|
282
|
+
function openclawConfig(home, proxyUrl, config) {
|
|
283
|
+
const model = pickConfiguredModel(config);
|
|
132
284
|
const configPath = path.join(home, ".openclaw", "config.json");
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return [makeChange("openclaw", configPath, "configure OpenClaw proxy settings", jsonContent(
|
|
285
|
+
const current = readJsonObject(configPath);
|
|
286
|
+
current.api_url = proxyUrl;
|
|
287
|
+
current.api_key = PROXY_ACCESS_TOKEN_PLACEHOLDER;
|
|
288
|
+
current.model = model;
|
|
289
|
+
return [makeChange("openclaw", configPath, "configure OpenClaw proxy settings", jsonContent(current))];
|
|
290
|
+
}
|
|
291
|
+
function openAiBaseUrl(proxyUrl) {
|
|
292
|
+
const normalized = proxyUrl.replace(/\/+$/, "");
|
|
293
|
+
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
138
294
|
}
|
|
139
|
-
function
|
|
295
|
+
function opencodeConfig(home, proxyUrl, config) {
|
|
296
|
+
const model = pickConfiguredModel(config);
|
|
297
|
+
const configPath = path.join(home, ".config", "opencode", "opencode.json");
|
|
298
|
+
const current = readJsonObject(configPath);
|
|
299
|
+
const providers = current.provider && typeof current.provider === "object" && !Array.isArray(current.provider)
|
|
300
|
+
? current.provider
|
|
301
|
+
: {};
|
|
302
|
+
providers.tokenbuddy = {
|
|
303
|
+
name: "TokenBuddy",
|
|
304
|
+
npm: "@ai-sdk/openai",
|
|
305
|
+
options: {
|
|
306
|
+
apiKey: PROXY_ACCESS_TOKEN_PLACEHOLDER,
|
|
307
|
+
baseURL: openAiBaseUrl(proxyUrl),
|
|
308
|
+
},
|
|
309
|
+
models: {
|
|
310
|
+
[model]: {
|
|
311
|
+
name: model,
|
|
312
|
+
attachment: true,
|
|
313
|
+
tool_call: true,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
current.provider = providers;
|
|
318
|
+
return [makeChange("opencode", configPath, "configure OpenCode provider for TokenBuddy proxy", jsonContent(current))];
|
|
319
|
+
}
|
|
320
|
+
function hermesConfig(home, proxyUrl, config) {
|
|
321
|
+
const model = pickConfiguredModel(config);
|
|
140
322
|
const configPath = path.join(home, ".hermes", "settings.json");
|
|
141
|
-
const
|
|
142
|
-
const openai =
|
|
143
|
-
?
|
|
323
|
+
const current = readJsonObject(configPath);
|
|
324
|
+
const openai = current.openai && typeof current.openai === "object" && !Array.isArray(current.openai)
|
|
325
|
+
? current.openai
|
|
144
326
|
: {};
|
|
145
|
-
|
|
327
|
+
current.openai = {
|
|
146
328
|
...openai,
|
|
147
329
|
base_url: proxyUrl,
|
|
148
|
-
api_key:
|
|
149
|
-
model
|
|
150
|
-
};
|
|
151
|
-
return [makeChange("hermes", configPath, "configure Hermes OpenAI proxy settings", jsonContent(config))];
|
|
152
|
-
}
|
|
153
|
-
function makeChange(providerId, filePath, summary, content) {
|
|
154
|
-
const existed = fs.existsSync(filePath);
|
|
155
|
-
return {
|
|
156
|
-
providerId,
|
|
157
|
-
path: filePath,
|
|
158
|
-
action: existed ? "update" : "create",
|
|
159
|
-
existed,
|
|
160
|
-
summary,
|
|
161
|
-
content
|
|
330
|
+
api_key: PROXY_ACCESS_TOKEN_PLACEHOLDER,
|
|
331
|
+
model,
|
|
162
332
|
};
|
|
333
|
+
return [makeChange("hermes", configPath, "configure Hermes OpenAI proxy settings", jsonContent(current))];
|
|
163
334
|
}
|
|
164
335
|
const PROVIDERS = [
|
|
165
336
|
{
|
|
166
337
|
id: "codex",
|
|
167
338
|
name: "Codex CLI",
|
|
339
|
+
commandName: "codex",
|
|
168
340
|
configPath: (home) => path.join(home, ".codex", "config.toml"),
|
|
169
|
-
changes: codexConfig
|
|
341
|
+
changes: codexConfig,
|
|
342
|
+
modelSelectionKind: "single-model",
|
|
343
|
+
protocolPreference: "responses",
|
|
170
344
|
},
|
|
171
345
|
{
|
|
172
346
|
id: "claude-code",
|
|
173
347
|
name: "Claude Code CLI",
|
|
348
|
+
commandName: "claude",
|
|
174
349
|
configPath: (home) => path.join(home, ".claude", "settings.json"),
|
|
175
|
-
changes: claudeCodeConfig
|
|
350
|
+
changes: claudeCodeConfig,
|
|
351
|
+
modelSelectionKind: "claude-role-mapping",
|
|
352
|
+
protocolPreference: "messages",
|
|
176
353
|
},
|
|
177
354
|
{
|
|
178
355
|
id: "claude-desktop",
|
|
179
356
|
name: "Claude Desktop App",
|
|
180
357
|
configPath: (home) => path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
|
181
|
-
changes: claudeDesktopConfig
|
|
358
|
+
changes: claudeDesktopConfig,
|
|
359
|
+
modelSelectionKind: "single-model",
|
|
360
|
+
protocolPreference: "messages",
|
|
182
361
|
},
|
|
183
362
|
{
|
|
184
363
|
id: "openclaw",
|
|
185
364
|
name: "OpenClaw Agent",
|
|
365
|
+
commandName: "openclaw",
|
|
186
366
|
configPath: (home) => path.join(home, ".openclaw", "config.json"),
|
|
187
|
-
|
|
367
|
+
observedPaths: (home) => [
|
|
368
|
+
path.join(home, ".openclaw", "openclaw.json"),
|
|
369
|
+
path.join(home, ".openclaw", "configs"),
|
|
370
|
+
],
|
|
371
|
+
changes: openclawConfig,
|
|
372
|
+
modelSelectionKind: "single-model",
|
|
373
|
+
protocolPreference: "chat_completions",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "opencode",
|
|
377
|
+
name: "OpenCode",
|
|
378
|
+
commandName: "opencode",
|
|
379
|
+
configPath: (home) => path.join(home, ".config", "opencode", "opencode.json"),
|
|
380
|
+
changes: opencodeConfig,
|
|
381
|
+
modelSelectionKind: "single-model",
|
|
382
|
+
protocolPreference: "responses",
|
|
188
383
|
},
|
|
189
384
|
{
|
|
190
385
|
id: "hermes",
|
|
191
386
|
name: "Hermes Terminal",
|
|
387
|
+
commandName: "hermes",
|
|
192
388
|
configPath: (home) => path.join(home, ".hermes", "settings.json"),
|
|
193
|
-
|
|
194
|
-
|
|
389
|
+
observedPaths: (home) => [
|
|
390
|
+
path.join(home, ".hermes", "config.yaml"),
|
|
391
|
+
path.join(home, ".hermes", "auth.json"),
|
|
392
|
+
],
|
|
393
|
+
changes: hermesConfig,
|
|
394
|
+
modelSelectionKind: "single-model",
|
|
395
|
+
protocolPreference: "chat_completions",
|
|
396
|
+
},
|
|
195
397
|
];
|
|
398
|
+
function getProviderDefinition(providerId) {
|
|
399
|
+
const provider = PROVIDERS.find((entry) => entry.id === providerId);
|
|
400
|
+
if (!provider) {
|
|
401
|
+
throw new Error(`unsupported provider: ${providerId}`);
|
|
402
|
+
}
|
|
403
|
+
return provider;
|
|
404
|
+
}
|
|
196
405
|
export function detectProviders(options = {}) {
|
|
197
406
|
const home = resolveHome(options.home);
|
|
198
407
|
return PROVIDERS.map((provider) => {
|
|
199
408
|
const configPath = provider.configPath(home);
|
|
200
|
-
const
|
|
409
|
+
const configured = fs.existsSync(configPath);
|
|
410
|
+
const executablePath = provider.commandName ? resolveExecutable(provider.commandName) : undefined;
|
|
411
|
+
const observedPaths = provider.observedPaths?.(home).filter((entry) => fs.existsSync(entry)) || [];
|
|
412
|
+
const installed = Boolean(executablePath) || observedPaths.length > 0;
|
|
413
|
+
const status = configured
|
|
414
|
+
? "configured"
|
|
415
|
+
: installed
|
|
416
|
+
? "installed"
|
|
417
|
+
: "missing";
|
|
418
|
+
const reasonParts = [];
|
|
419
|
+
if (configured) {
|
|
420
|
+
reasonParts.push(`Configured at ${displayPath(home, configPath)}`);
|
|
421
|
+
}
|
|
422
|
+
else if (installed) {
|
|
423
|
+
reasonParts.push(`Installed, TokenBuddy config missing at ${displayPath(home, configPath)}`);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
reasonParts.push(`Missing ${displayPath(home, configPath)}`);
|
|
427
|
+
}
|
|
428
|
+
if (executablePath) {
|
|
429
|
+
reasonParts.push(`CLI ${displayPath(home, executablePath)}`);
|
|
430
|
+
}
|
|
431
|
+
if (observedPaths.length > 0) {
|
|
432
|
+
reasonParts.push(`Native files ${observedPaths.map((entry) => displayPath(home, entry)).join(", ")}`);
|
|
433
|
+
}
|
|
201
434
|
return {
|
|
202
435
|
id: provider.id,
|
|
203
436
|
name: provider.name,
|
|
204
|
-
detected,
|
|
437
|
+
detected: status !== "missing",
|
|
438
|
+
configured,
|
|
439
|
+
status,
|
|
205
440
|
configPath,
|
|
206
|
-
|
|
441
|
+
commandName: provider.commandName,
|
|
442
|
+
executablePath,
|
|
443
|
+
observedPaths,
|
|
444
|
+
reason: reasonParts.join(" · "),
|
|
207
445
|
};
|
|
208
446
|
});
|
|
209
447
|
}
|
|
448
|
+
export function getProviderProtocolPreference(providerId) {
|
|
449
|
+
return getProviderDefinition(providerId).protocolPreference;
|
|
450
|
+
}
|
|
451
|
+
export function getProviderModelSelectionKind(providerId) {
|
|
452
|
+
return getProviderDefinition(providerId).modelSelectionKind;
|
|
453
|
+
}
|
|
210
454
|
export function previewProviderInstall(options) {
|
|
211
455
|
const home = resolveHome(options.home);
|
|
212
456
|
const providerIds = assertProviderIds(options.providers);
|
|
213
457
|
if (!options.proxyUrl || !options.proxyUrl.trim()) {
|
|
214
458
|
throw new Error("proxyUrl is required");
|
|
215
459
|
}
|
|
216
|
-
if (!options.model || !options.model.trim()) {
|
|
217
|
-
throw new Error("model is required");
|
|
218
|
-
}
|
|
219
460
|
return providerIds.flatMap((providerId) => {
|
|
220
|
-
const provider =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
return provider.changes(home, options.proxyUrl, options.model);
|
|
461
|
+
const provider = getProviderDefinition(providerId);
|
|
462
|
+
const runtimeConfig = resolveProviderRuntimeConfig(provider, options);
|
|
463
|
+
return provider.changes(home, options.proxyUrl, runtimeConfig);
|
|
225
464
|
});
|
|
226
465
|
}
|
|
227
466
|
export function applyProviderInstall(options, store) {
|
|
467
|
+
const providerIds = assertProviderIds(options.providers);
|
|
228
468
|
const changes = previewProviderInstall(options);
|
|
229
469
|
const byProvider = new Map();
|
|
230
470
|
for (const change of changes) {
|
|
@@ -236,11 +476,19 @@ export function applyProviderInstall(options, store) {
|
|
|
236
476
|
files: providerChanges.map((change) => ({
|
|
237
477
|
path: change.path,
|
|
238
478
|
existed: fs.existsSync(change.path),
|
|
239
|
-
content: readText(change.path)
|
|
240
|
-
}))
|
|
479
|
+
content: readText(change.path),
|
|
480
|
+
})),
|
|
241
481
|
};
|
|
242
482
|
store.saveProviderInstallSnapshot(snapshot);
|
|
243
483
|
}
|
|
484
|
+
for (const providerId of providerIds) {
|
|
485
|
+
const provider = getProviderDefinition(providerId);
|
|
486
|
+
const runtimeConfig = resolveProviderRuntimeConfig(provider, options);
|
|
487
|
+
store.saveProviderRuntimeConfig(providerId, runtimeConfig);
|
|
488
|
+
}
|
|
489
|
+
if (options.sellerRouting) {
|
|
490
|
+
store.saveDaemonRuntimeConfig(ROUTING_CONFIG_KEY, options.sellerRouting);
|
|
491
|
+
}
|
|
244
492
|
const applied = [];
|
|
245
493
|
for (const change of changes) {
|
|
246
494
|
const dir = path.dirname(change.path);
|
|
@@ -251,7 +499,7 @@ export function applyProviderInstall(options, store) {
|
|
|
251
499
|
applied.push({
|
|
252
500
|
providerId: change.providerId,
|
|
253
501
|
path: change.path,
|
|
254
|
-
action: change.existed ? "updated" : "created"
|
|
502
|
+
action: change.existed ? "updated" : "created",
|
|
255
503
|
});
|
|
256
504
|
}
|
|
257
505
|
return applied;
|
|
@@ -280,7 +528,9 @@ export function rollbackProviderInstall(options, store) {
|
|
|
280
528
|
}
|
|
281
529
|
}
|
|
282
530
|
store.removeProviderInstallSnapshot(providerId);
|
|
531
|
+
store.removeProviderRuntimeConfig(providerId);
|
|
283
532
|
}
|
|
533
|
+
store.removeDaemonRuntimeConfig(ROUTING_CONFIG_KEY);
|
|
284
534
|
return results;
|
|
285
535
|
}
|
|
286
536
|
//# sourceMappingURL=provider-install.js.map
|