Skip to content

Commit 26a69a7

Browse files
committed
fix(config-agent): align agent writers with cc-switch and official Model Studio docs
- claude-code: honor CLAUDE_CONFIG_DIR; drop stale ANTHROPIC_API_KEY - qwen-code: write $version:3; security.auth carries selectedType only - opencode: tolerate JSONC (comments/trailing commas) via stripJsonc - openclaw: add --context-window flag (default 256000), full cost fields, agents.defaults.models allowlist - hermes: switch to official flat model.* block; api_mode only for anthropic endpoints - codex: official env_key + auth.json fallback; add --wire-api flag (default chat, responses for supported models)
1 parent 678f60b commit 26a69a7

11 files changed

Lines changed: 587 additions & 160 deletions

File tree

packages/commands/src/commands/config/agent/index.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,36 @@ const FLAGS = {
1111
required: true,
1212
choices: VALID_AGENT_NAMES,
1313
},
14-
baseUrl: { type: "string", valueHint: "<url>", description: "API base URL", required: true },
15-
apiKey: { type: "string", valueHint: "<key>", description: "API key", required: true },
14+
baseUrl: {
15+
type: "string",
16+
valueHint: "<url>",
17+
description: "API base URL",
18+
required: true,
19+
},
20+
apiKey: {
21+
type: "string",
22+
valueHint: "<key>",
23+
description: "API key",
24+
required: true,
25+
},
1626
model: {
1727
type: "string",
1828
valueHint: "<model>",
1929
description: "Default model name",
2030
required: true,
2131
},
32+
contextWindow: {
33+
type: "number",
34+
valueHint: "<tokens>",
35+
description: "OpenClaw only: model context window in tokens (default: 256000)",
36+
},
37+
wireApi: {
38+
type: "string",
39+
valueHint: "<api>",
40+
description:
41+
'Codex only: wire protocol — "chat" works with every model; "responses" for models supporting the Responses API (default: chat)',
42+
choices: ["chat", "responses"],
43+
},
2244
} satisfies FlagsDef;
2345

2446
export default defineCommand({
@@ -34,7 +56,7 @@ export default defineCommand({
3456
async run(ctx) {
3557
const { settings, flags } = ctx;
3658
const agentName = flags.agent;
37-
const { baseUrl, apiKey, model } = flags;
59+
const { baseUrl, apiKey, model, contextWindow, wireApi } = flags;
3860
const agentDef = AGENTS[agentName];
3961
const format = detectOutputFormat(settings.output);
4062

@@ -59,7 +81,13 @@ export default defineCommand({
5981
return;
6082
}
6183

62-
const params: WriteParams = { baseUrl, apiKey, model };
84+
const params: WriteParams = {
85+
baseUrl,
86+
apiKey,
87+
model,
88+
contextWindow,
89+
wireApi,
90+
};
6391
const summary = agentDef.write(params);
6492

6593
if (!settings.quiet) {

packages/commands/src/commands/config/agent/writers/claude-code.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { backup, readJson, writeJsonAtomic, type AgentDef } from "./utils.ts";
55
export default {
66
label: "Claude Code",
77
write({ baseUrl, apiKey, model }) {
8-
const settingsPath = join(homedir(), ".claude", "settings.json");
8+
// Claude Code honors CLAUDE_CONFIG_DIR for its settings location.
9+
const configDir =
10+
process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
11+
const settingsPath = join(configDir, "settings.json");
912
const onboardingPath = join(homedir(), ".claude.json");
1013

1114
// settings.json — merge env. Base URL + auth token connect Claude Code to
@@ -15,6 +18,9 @@ export default {
1518
const env = (settings.env ?? {}) as Record<string, string>;
1619
env.ANTHROPIC_BASE_URL = baseUrl;
1720
env.ANTHROPIC_AUTH_TOKEN = apiKey;
21+
// AUTH_TOKEN and API_KEY are mutually exclusive credential fields — drop a
22+
// stale ANTHROPIC_API_KEY so it cannot shadow the token we just wrote.
23+
delete env.ANTHROPIC_API_KEY;
1824
env.ANTHROPIC_MODEL = model;
1925
env.ANTHROPIC_DEFAULT_HAIKU_MODEL = model;
2026
env.ANTHROPIC_DEFAULT_SONNET_MODEL = model;

packages/commands/src/commands/config/agent/writers/codex.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import { homedir } from "os";
22
import { join } from "path";
33
import { existsSync, readFileSync } from "fs";
44
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
5-
import { backup, readJson, writeJsonAtomic, writeTextAtomic, type AgentDef } from "./utils.ts";
5+
import {
6+
backup,
7+
readJson,
8+
writeJsonAtomic,
9+
writeTextAtomic,
10+
type AgentDef,
11+
} from "./utils.ts";
612

713
const PROVIDER_KEY = "bailian-cli";
814

915
export default {
1016
label: "Codex",
11-
write({ baseUrl, apiKey, model }) {
17+
write({ baseUrl, apiKey, model, wireApi: wireApiParam }) {
1218
const configPath = join(homedir(), ".codex", "config.toml");
1319

1420
// config.toml — merge into existing config so unrelated settings
@@ -17,31 +23,39 @@ export default {
1723
let config: Record<string, unknown> = {};
1824
if (existsSync(configPath)) {
1925
try {
20-
config = parseToml(readFileSync(configPath, "utf-8")) as Record<string, unknown>;
26+
config = parseToml(readFileSync(configPath, "utf-8")) as Record<
27+
string,
28+
unknown
29+
>;
2130
} catch {
2231
config = {};
2332
}
2433
}
2534

2635
config.model_provider = PROVIDER_KEY;
2736
config.model = model;
28-
config.model_reasoning_effort = "high";
29-
config.disable_response_storage = true;
37+
38+
// wire_api: "responses" for models supporting the Responses API (e.g.
39+
// qwen3.7/3.8 series); "chat" works with every model via Chat Completions.
40+
const wireApi = wireApiParam === "responses" ? "responses" : "chat";
3041

3142
const providers = (config.model_providers ?? {}) as Record<string, unknown>;
3243
const existing = (providers[PROVIDER_KEY] ?? {}) as Record<string, unknown>;
3344
providers[PROVIDER_KEY] = {
3445
...existing,
3546
name: PROVIDER_KEY,
3647
base_url: baseUrl,
37-
wire_api: "responses",
48+
// env_key is the official-doc credential mechanism: Codex resolves the
49+
// key from the OPENAI_API_KEY env var, falling back to auth.json below.
50+
env_key: "OPENAI_API_KEY",
51+
wire_api: wireApi,
3852
requires_openai_auth: true,
3953
};
4054
config.model_providers = providers;
4155

4256
writeTextAtomic(configPath, stringifyToml(config) + "\n");
4357

44-
// auth.json — Codex reads OPENAI_API_KEY from here.
58+
// auth.json — Codex reads OPENAI_API_KEY from here when the env var is unset.
4559
const authPath = join(homedir(), ".codex", "auth.json");
4660
backup(authPath);
4761
const auth = readJson(authPath);

packages/commands/src/commands/config/agent/writers/hermes.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import { homedir } from "os";
22
import { join } from "path";
33
import { existsSync, readFileSync } from "fs";
44
import yaml from "yaml";
5-
import { backup, writeTextAtomic, isAnthropicEndpoint, type AgentDef } from "./utils.ts";
6-
7-
const PROVIDER_NAME = "bailian-cli";
5+
import {
6+
backup,
7+
writeTextAtomic,
8+
isAnthropicEndpoint,
9+
type AgentDef,
10+
} from "./utils.ts";
811

912
export default {
1013
label: "Hermes Agent",
@@ -16,32 +19,25 @@ export default {
1619
let config: Record<string, unknown> = {};
1720
if (existsSync(configPath)) {
1821
try {
19-
config = (yaml.parse(readFileSync(configPath, "utf-8")) ?? {}) as Record<string, unknown>;
22+
config = (yaml.parse(readFileSync(configPath, "utf-8")) ??
23+
{}) as Record<string, unknown>;
2024
} catch {
2125
config = {};
2226
}
2327
}
2428

25-
const apiMode = isAnthropicEndpoint(baseUrl) ? "anthropic_messages" : "chat_completions";
26-
const providerEntry = {
27-
name: PROVIDER_NAME,
29+
// Official Model Studio doc shape: a single flat `model` block holding the
30+
// active endpoint + credentials. `api_mode: anthropic_messages` is required
31+
// for /apps/anthropic endpoints; for the OpenAI-compatible endpoint the
32+
// doc says to omit api_mode entirely (chat completions is the default).
33+
const block: Record<string, unknown> = {
34+
default: model,
35+
provider: "custom",
2836
base_url: baseUrl,
2937
api_key: apiKey,
30-
api_mode: apiMode,
31-
models: [{ id: model, name: model }],
3238
};
33-
34-
// custom_providers — upsert the bailian-cli entry by name.
35-
const providers = Array.isArray(config.custom_providers)
36-
? (config.custom_providers as Array<Record<string, unknown>>)
37-
: [];
38-
const index = providers.findIndex((entry) => entry.name === PROVIDER_NAME);
39-
if (index >= 0) providers[index] = providerEntry;
40-
else providers.push(providerEntry);
41-
config.custom_providers = providers;
42-
43-
// model — select the bailian-cli provider and default model.
44-
config.model = { default: model, provider: PROVIDER_NAME };
39+
if (isAnthropicEndpoint(baseUrl)) block.api_mode = "anthropic_messages";
40+
config.model = block;
4541

4642
writeTextAtomic(configPath, yaml.stringify(config));
4743

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import { homedir } from "os";
22
import { join } from "path";
3-
import { backup, readJson, writeJsonAtomic, isAnthropicEndpoint, type AgentDef } from "./utils.ts";
3+
import {
4+
backup,
5+
readJson,
6+
writeJsonAtomic,
7+
isAnthropicEndpoint,
8+
type AgentDef,
9+
} from "./utils.ts";
10+
11+
// Safe default when --context-window is not given: most Model Studio models
12+
// offer ≥256K context; users can raise it per model via the flag.
13+
const DEFAULT_CONTEXT_WINDOW = 256000;
414

515
export default {
616
label: "OpenClaw",
7-
write({ baseUrl, apiKey, model }) {
17+
write({ baseUrl, apiKey, model, contextWindow }) {
818
const configPath = join(homedir(), ".openclaw", "openclaw.json");
919

1020
backup(configPath);
@@ -14,7 +24,9 @@ export default {
1424
const models = (config.models ?? {}) as Record<string, unknown>;
1525
models.mode = "merge";
1626
const providers = (models.providers ?? {}) as Record<string, unknown>;
17-
const api = isAnthropicEndpoint(baseUrl) ? "anthropic-messages" : "openai-completions";
27+
const api = isAnthropicEndpoint(baseUrl)
28+
? "anthropic-messages"
29+
: "openai-completions";
1830
providers["bailian-cli"] = {
1931
baseUrl,
2032
apiKey,
@@ -23,26 +35,31 @@ export default {
2335
{
2436
id: model,
2537
name: model,
26-
contextWindow: 1000000,
27-
cost: { input: 0, output: 0 },
38+
contextWindow: contextWindow ?? DEFAULT_CONTEXT_WINDOW,
39+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2840
},
2941
],
3042
};
3143
models.providers = providers;
3244
config.models = models;
3345

34-
// agents.defaults
46+
// agents.defaults — select the model and register it in the allowlist.
3547
const agents = (config.agents ?? {}) as Record<string, unknown>;
3648
const defaults = (agents.defaults ?? {}) as Record<string, unknown>;
37-
defaults.model = { primary: `bailian-cli/${model}` };
49+
const primary = `bailian-cli/${model}`;
50+
defaults.model = { primary };
51+
const allowlist = (defaults.models ?? {}) as Record<string, unknown>;
52+
allowlist[primary] = allowlist[primary] ?? {};
53+
defaults.models = allowlist;
3854
agents.defaults = defaults;
3955
config.agents = agents;
4056

4157
writeJsonAtomic(configPath, config);
4258

4359
return {
4460
paths: [configPath],
45-
nextStep: "Run `openclaw` to start using OpenClaw with DashScope.",
61+
nextStep:
62+
"Run `openclaw gateway restart`, then `openclaw` to start using OpenClaw with DashScope.",
4663
};
4764
},
4865
} satisfies AgentDef;

packages/commands/src/commands/config/agent/writers/opencode.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import { homedir } from "os";
22
import { join } from "path";
3-
import { backup, readJson, writeJsonAtomic, isAnthropicEndpoint, type AgentDef } from "./utils.ts";
3+
import {
4+
backup,
5+
readJsonc,
6+
writeJsonAtomic,
7+
isAnthropicEndpoint,
8+
type AgentDef,
9+
} from "./utils.ts";
410

511
export default {
612
label: "OpenCode",
713
write({ baseUrl, apiKey, model }) {
814
const configPath = join(homedir(), ".config", "opencode", "opencode.json");
915

16+
// opencode.json is JSONC — tolerate comments and trailing commas on read.
1017
backup(configPath);
11-
const config = readJson(configPath);
18+
const config = readJsonc(configPath);
1219

1320
if (!config.$schema) config.$schema = "https://opencode.ai/config.json";
1421

1522
const provider = (config.provider ?? {}) as Record<string, unknown>;
16-
const npm = isAnthropicEndpoint(baseUrl) ? "@ai-sdk/anthropic" : "@ai-sdk/openai-compatible";
23+
const npm = isAnthropicEndpoint(baseUrl)
24+
? "@ai-sdk/anthropic"
25+
: "@ai-sdk/openai-compatible";
1726
provider["bailian-cli"] = {
1827
npm,
1928
name: "Alibaba Cloud Model Studio",

packages/commands/src/commands/config/agent/writers/qwen-code.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { homedir } from "os";
22
import { join } from "path";
3-
import { backup, readJson, writeJsonAtomic, isAnthropicEndpoint, type AgentDef } from "./utils.ts";
3+
import {
4+
backup,
5+
readJson,
6+
writeJsonAtomic,
7+
isAnthropicEndpoint,
8+
type AgentDef,
9+
} from "./utils.ts";
410

511
const ENV_KEY = "BAILIAN_CLI_API_KEY";
612

@@ -19,6 +25,9 @@ export default {
1925
backup(settingsPath);
2026
const settings = readJson(settingsPath);
2127

28+
// $version — Qwen Code v3 settings schema (official Model Studio doc shape).
29+
settings.$version = 3;
30+
2231
// env — API key read by the provider entry's envKey.
2332
const env = (settings.env ?? {}) as Record<string, string>;
2433
env[ENV_KEY] = apiKey;
@@ -29,7 +38,9 @@ export default {
2938
string,
3039
Array<Record<string, unknown>>
3140
>;
32-
const entries = (providers[protocol] ?? []) as Array<Record<string, unknown>>;
41+
const entries = (providers[protocol] ?? []) as Array<
42+
Record<string, unknown>
43+
>;
3344
const existing = entries.find(
3445
(entry) => entry.id === model && (entry.baseUrl ?? "") === baseUrl,
3546
);
@@ -38,18 +49,25 @@ export default {
3849
existing.baseUrl = baseUrl;
3950
existing.envKey = ENV_KEY;
4051
} else {
41-
entries.push({ id: model, name: "bailian-cli", baseUrl, envKey: ENV_KEY });
52+
entries.push({
53+
id: model,
54+
name: "bailian-cli",
55+
baseUrl,
56+
envKey: ENV_KEY,
57+
});
4258
}
4359
providers[protocol] = entries;
4460
settings.modelProviders = providers;
4561

46-
// security.auth — select the protocol and carry the OpenAI-compatible creds.
62+
// security.auth — select the protocol only. Credentials live in env (via
63+
// each provider entry's envKey); writing apiKey/baseUrl here is not part of
64+
// the v3 schema.
4765
const security = (settings.security ?? {}) as Record<string, unknown>;
48-
security.auth = { selectedType: protocol, apiKey, baseUrl };
66+
security.auth = { selectedType: protocol };
4967
settings.security = security;
5068

51-
// model — active model, disambiguated by baseUrl.
52-
settings.model = { name: model, baseUrl };
69+
// model — active model id, resolved inside modelProviders[protocol].
70+
settings.model = { name: model };
5371

5472
writeJsonAtomic(settingsPath, settings);
5573

0 commit comments

Comments
 (0)