Skip to content

Commit d31b7f8

Browse files
committed
refactor(core): split god Config into Identity/Settings/Credential resolved at the dispatch boundary
- commands consume a narrowed context (identity/settings/own flags/client); config/auth commands additionally use configStore()/authStore() accessors - resolution happens once at dispatch: buildSources/buildSettings plus per-domain credential resolvers; dry-run tolerates missing credentials - transport takes structured deps; credentials are injected only by Client; console gateway takes a resolved target with optional token (anonymous catalog calls); pipeline steps and advisor run against client/settings - telemetry receives authMethod as a value; global/command flags are split at dispatch with a same-type shadowing guard at registry build - behavior change: base URL resolution now prefers DASHSCOPE_BASE_URL env over config file base_url (unified flag > env > file > default chain) - priority chains, store semantics and command capability boundaries are locked by unit tests
1 parent ae88f7a commit d31b7f8

85 files changed

Lines changed: 1614 additions & 838 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/config-flags-refactor.md

Lines changed: 371 additions & 0 deletions
Large diffs are not rendered by default.

packages/commands/src/commands/advisor/recommend.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -231,19 +231,18 @@ export default defineCommand({
231231
'--message "Long document summarization" --dry-run',
232232
],
233233
async run(ctx) {
234-
const { config, flags } = ctx;
234+
const { settings, flags } = ctx;
235235
const userInput = flags.message;
236-
237236
const top = 3;
238-
const format = detectOutputFormat(config.output);
237+
const format = detectOutputFormat(settings.output);
239238

240239
const modelsOptions: GetModelsOptions = {
241240
onPrepareStart: () => process.stderr.write("Initializing model data...\n"),
242241
};
243242
process.stderr.write("Analyzing your request...\n");
244243
const [allModels, intent] = await Promise.all([
245-
getModels(config, modelsOptions),
246-
analyzeIntent(config, userInput),
244+
getModels(settings, modelsOptions),
245+
analyzeIntent(ctx.client, userInput),
247246
]);
248247

249248
if (intent.confidence === 0) {
@@ -253,9 +252,9 @@ export default defineCommand({
253252
}
254253

255254
// Stage 2: Candidate Recall (semantic recall, auto-builds embeddings on first run)
256-
const candidates = await recallSemantic(config, allModels, userInput, 50, intent);
255+
const candidates = await recallSemantic(ctx.client, allModels, userInput, 50, intent);
257256

258-
if (config.dryRun) {
257+
if (settings.dryRun) {
259258
emitResult(
260259
{
261260
userInput,
@@ -276,7 +275,7 @@ export default defineCommand({
276275
const spinner = createSpinner("Recommending best models...");
277276
spinner.start();
278277

279-
const result = await rankModels(config, candidates, intent, userInput, top);
278+
const result = await rankModels(ctx.client, candidates, intent, userInput, top);
280279

281280
spinner.stop();
282281

@@ -309,8 +308,8 @@ export default defineCommand({
309308
return;
310309
}
311310

312-
emitBare(formatIntentSummary(intent, config.noColor));
311+
emitBare(formatIntentSummary(intent, settings.noColor));
313312
emitBare("");
314-
emitBare(formatResult(result, config.noColor));
313+
emitBare(formatResult(result, settings.noColor));
315314
},
316315
});

packages/commands/src/commands/app/call.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ export default defineCommand({
6565
'--app-id abc123 --prompt "Start" --biz-params \'{"key":"value"}\'',
6666
],
6767
async run(ctx) {
68-
const { config, flags } = ctx;
68+
const { settings, flags } = ctx;
6969
const appId = flags.appId;
7070
const prompt = flags.prompt;
7171

7272
const shouldStream = flags.stream || process.stdout.isTTY;
73-
const format = detectOutputFormat(config.output);
73+
const format = detectOutputFormat(settings.output);
7474

7575
const body: AppCompletionRequest = {
7676
input: { prompt },
@@ -119,7 +119,7 @@ export default defineCommand({
119119
}
120120
}
121121

122-
if (config.dryRun) {
122+
if (settings.dryRun) {
123123
emitResult({ endpoint: ctx.client.url(appCompletionPath(appId)), request: body }, format);
124124
return;
125125
}
@@ -137,8 +137,8 @@ export default defineCommand({
137137
let fullText = "";
138138
let sessionId = "";
139139
const writesStreamingStdout = format === "text";
140-
const dim = config.noColor ? "" : "\x1b[2m";
141-
const reset = config.noColor ? "" : "\x1b[0m";
140+
const dim = settings.noColor ? "" : "\x1b[2m";
141+
const reset = settings.noColor ? "" : "\x1b[0m";
142142

143143
for await (const event of parseSSE(res)) {
144144
if (event.data === "[DONE]") break;
@@ -175,7 +175,7 @@ export default defineCommand({
175175
}
176176

177177
// Show session_id for multi-turn conversation
178-
if (sessionId && !config.quiet) {
178+
if (sessionId && !settings.quiet) {
179179
process.stderr.write(`${dim}Session ID: ${sessionId}${reset}\n`);
180180
}
181181

@@ -193,7 +193,7 @@ export default defineCommand({
193193

194194
const text = response.output?.text ?? "";
195195

196-
if (config.quiet || format === "text") {
196+
if (settings.quiet || format === "text") {
197197
emitBare(text);
198198
} else {
199199
emitResult(response, format);

packages/commands/src/commands/app/list.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ export default defineCommand({
3333
},
3434
exampleArgs: ["", "--name customer service", "--page 2 --page-size 10", "--output json"],
3535
async run(ctx) {
36-
const { config, flags } = ctx;
36+
const { settings, flags } = ctx;
3737
const name = flags.name || "";
3838
const pageNo = flags.page || 1;
3939
const pageSize = flags.pageSize || 30;
40-
const format = detectOutputFormat(config.output);
40+
const format = detectOutputFormat(settings.output);
4141

4242
const data = {
4343
reqDTO: {
@@ -50,7 +50,7 @@ export default defineCommand({
5050
},
5151
};
5252

53-
if (config.dryRun) {
53+
if (settings.dryRun) {
5454
emitResult({ api: APP_LIST_API, data }, format);
5555
return;
5656
}

packages/commands/src/commands/auth/login-console.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@ import {
77
ExitCode,
88
chatPath,
99
getConfigPath,
10-
readConfigFile,
1110
requestJson,
12-
writeConfigFile,
13-
type Config,
11+
type AuthStore,
12+
type ConfigFile,
13+
type Identity,
14+
type Settings,
1415
} from "bailian-cli-core";
1516

17+
/** 登录流程的能力面:身份(UA)、有效配置(timeout 等)、auth 域落盘。 */
18+
export interface LoginDeps {
19+
identity: Identity;
20+
settings: Settings;
21+
authStore: AuthStore;
22+
}
23+
1624
const CONSOLE_LOGIN_TIMEOUT_MS = 15 * 60 * 1000;
1725
const MAX_AUTH_CALLBACK_BODY = 65536;
1826

@@ -399,16 +407,17 @@ function canRetry(err: unknown): boolean {
399407
}
400408

401409
export async function validateAndPersistApiKey(
402-
config: Config,
410+
deps: LoginDeps,
403411
key: string,
404412
baseUrl: string,
405413
): Promise<void> {
406414
process.stderr.write("Testing key... ");
407-
const testConfig = { ...config, apiKey: key, baseUrl };
415+
const httpDeps = { identity: deps.identity, settings: deps.settings };
408416
const requestOpts = {
409-
url: testConfig.baseUrl + chatPath(),
417+
url: baseUrl + chatPath(),
410418
method: "POST",
411-
timeout: Math.min(config.timeout, 30),
419+
headers: { Authorization: `Bearer ${key}` },
420+
timeout: Math.min(deps.settings.timeout, 30),
412421
body: {
413422
model: "qwen3.7-max",
414423
messages: [{ role: "user", content: "hi" }],
@@ -418,7 +427,7 @@ export async function validateAndPersistApiKey(
418427

419428
for (let attempt = 1; attempt <= 3; attempt++) {
420429
try {
421-
await requestJson<unknown>(testConfig, requestOpts);
430+
await requestJson<unknown>(httpDeps, requestOpts);
422431
break;
423432
} catch (err) {
424433
if (attempt >= 3 || !canRetry(err)) {
@@ -433,14 +442,12 @@ export async function validateAndPersistApiKey(
433442
}
434443

435444
process.stderr.write("Valid\n");
436-
const existing = readConfigFile() as Record<string, unknown>;
437-
existing.api_key = key;
438-
await writeConfigFile(existing);
445+
await deps.authStore.login({ api_key: key });
439446
}
440447

441448
export async function runConsoleLogin(
442449
consoleOrigin: string,
443-
config: Config,
450+
deps: LoginDeps,
444451
opts?: { needApiKey?: boolean },
445452
): Promise<void> {
446453
const state = randomBytes(16).toString("hex");
@@ -480,19 +487,19 @@ export async function runConsoleLogin(
480487
if (hasConfig || apiKey) {
481488
try {
482489
if (hasConfig) {
483-
const existing = readConfigFile() as Record<string, unknown>;
484-
if (accessToken) existing.access_token = accessToken;
485-
if (baseUrl) existing.base_url = baseUrl;
486-
if (consoleSite) existing.console_site = consoleSite;
487-
if (consoleRegion) existing.console_region = consoleRegion;
488-
if (consoleSwitchAgent) existing.console_switch_agent = Number(consoleSwitchAgent);
489-
if (workspaceId) existing.workspace_id = workspaceId;
490-
await writeConfigFile(existing);
490+
await deps.authStore.login({
491+
access_token: accessToken || undefined,
492+
base_url: baseUrl || undefined,
493+
console_site: (consoleSite || undefined) as ConfigFile["console_site"],
494+
console_region: consoleRegion || undefined,
495+
console_switch_agent: consoleSwitchAgent ? Number(consoleSwitchAgent) : undefined,
496+
workspace_id: workspaceId || undefined,
497+
});
491498
process.stderr.write(`Config saved to ${getConfigPath()}\n`);
492499
}
493500
if (apiKey) {
494-
const testBaseUrl = baseUrl || config.baseUrl;
495-
await validateAndPersistApiKey(config, apiKey, testBaseUrl);
501+
const testBaseUrl = baseUrl || deps.authStore.resolveBaseUrl();
502+
await validateAndPersistApiKey(deps, apiKey, testBaseUrl);
496503
}
497504
} catch (err: unknown) {
498505
callbackError = err;

packages/commands/src/commands/auth/login.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineCommand, readConfigFile, writeConfigFile } from "bailian-cli-core";
1+
import { defineCommand } from "bailian-cli-core";
22
import { printQuickStart } from "bailian-cli-runtime";
33
import { emitBare } from "bailian-cli-runtime";
44
import {
@@ -27,16 +27,18 @@ export default defineCommand({
2727
exampleArgs: ["--api-key sk-xxxxx", "--console"],
2828
validate: (f) => (!f.console && !f.apiKey ? "Provide --api-key or --console" : undefined),
2929
async run(ctx) {
30-
const { config, flags } = ctx;
30+
const { identity, settings, flags } = ctx;
31+
const store = ctx.authStore();
32+
const deps = { identity, settings, authStore: store };
3133
if (flags.console) {
32-
if (config.dryRun) {
34+
if (settings.dryRun) {
3335
emitBare(
3436
"Would bind a free port on 127.0.0.1 and open the console login URL in your browser.",
3537
);
3638
return;
3739
}
38-
const hasApiKey = !!(config.apiKey || config.fileApiKey);
39-
await runConsoleLogin(resolveConsoleOrigin(config.consoleSite || "domestic"), config, {
40+
const hasApiKey = !!(flags.apiKey || store.stored().apiKey);
41+
await runConsoleLogin(resolveConsoleOrigin(settings.consoleSite || "domestic"), deps, {
4042
needApiKey: !hasApiKey,
4143
});
4244
return;
@@ -46,18 +48,15 @@ export default defineCommand({
4648
if (flags.apiKey) {
4749
const key = flags.apiKey;
4850
const baseUrl = flags.baseUrl || undefined;
49-
const effectiveConfig = baseUrl ? { ...config, baseUrl } : config;
5051

51-
if (config.dryRun) {
52+
if (settings.dryRun) {
5253
emitBare("Would validate and save API key.");
5354
return;
5455
}
5556
if (baseUrl) {
56-
const existing = readConfigFile() as Record<string, unknown>;
57-
existing.base_url = baseUrl;
58-
await writeConfigFile(existing);
57+
await store.login({ base_url: baseUrl });
5958
}
60-
await validateAndPersistApiKey(effectiveConfig, key, effectiveConfig.baseUrl);
59+
await validateAndPersistApiKey(deps, key, baseUrl || store.resolveBaseUrl());
6160
printQuickStart();
6261
}
6362
},

packages/commands/src/commands/auth/logout.ts

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,6 @@
1-
import {
2-
defineCommand,
3-
clearApiKey,
4-
readConfigFile,
5-
writeConfigFile,
6-
getConfigPath,
7-
} from "bailian-cli-core";
1+
import { defineCommand, getConfigPath } from "bailian-cli-core";
82
import { emitBare } from "bailian-cli-runtime";
93

10-
async function clearConsoleToken(): Promise<boolean> {
11-
const file = readConfigFile() as Record<string, unknown>;
12-
if (!file.access_token) return false;
13-
delete file.access_token;
14-
await writeConfigFile(file);
15-
return true;
16-
}
17-
184
export default defineCommand({
195
description: "Clear stored credentials",
206
auth: "none",
@@ -28,21 +14,20 @@ export default defineCommand({
2814
},
2915
exampleArgs: ["", "--console", "--dry-run", "--yes"],
3016
async run(ctx) {
31-
const { config, flags } = ctx;
32-
const file = readConfigFile();
17+
const { settings, flags } = ctx;
18+
const store = ctx.authStore();
19+
const stored = store.stored();
3320

3421
if (flags.console) {
35-
const hasToken = !!file.access_token;
36-
if (config.dryRun) {
37-
if (hasToken) emitBare("Would clear access_token from ~/.bailian/config.json");
22+
if (settings.dryRun) {
23+
if (stored.console) emitBare("Would clear access_token from ~/.bailian/config.json");
3824
else emitBare("No console access_token to clear.");
3925
emitBare("No changes made.");
4026
return;
4127
}
42-
if (hasToken) {
43-
await clearConsoleToken();
28+
if (await store.logout("console")) {
4429
process.stderr.write(`Cleared access_token from ${getConfigPath()}\n`);
45-
if (file.api_key) {
30+
if (stored.apiKey) {
4631
process.stderr.write(
4732
"api_key is still configured and will be used for authentication.\n",
4833
);
@@ -53,17 +38,16 @@ export default defineCommand({
5338
return;
5439
}
5540

56-
const hasKey = !!(file.api_key || file.access_token);
41+
const hasKey = stored.apiKey || stored.console;
5742

58-
if (config.dryRun) {
43+
if (settings.dryRun) {
5944
if (hasKey) emitBare("Would clear api_key / access_token from ~/.bailian/config.json");
6045
else emitBare("No credentials to clear.");
6146
emitBare("No changes made.");
6247
return;
6348
}
6449

65-
if (hasKey) {
66-
await clearApiKey();
50+
if (await store.logout("all")) {
6751
process.stderr.write("Cleared api_key / access_token from ~/.bailian/config.json\n");
6852
} else {
6953
process.stderr.write("No credentials to clear.\n");

packages/commands/src/commands/auth/status.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineCommand, describeAuth, detectOutputFormat, maskToken } from "bailian-cli-core";
1+
import { defineCommand, detectOutputFormat, maskToken } from "bailian-cli-core";
22
import { emitResult, emitBare } from "bailian-cli-runtime";
33
import { API_KEY_PAGE } from "bailian-cli-runtime";
44

@@ -16,9 +16,9 @@ export default defineCommand({
1616
consoleSwitchAgent: { type: "number", valueHint: "<uid>", description: "Switch agent UID" },
1717
},
1818
async run(ctx) {
19-
const { config } = ctx;
20-
const format = detectOutputFormat(config.output);
21-
const auth = await describeAuth(config);
19+
const { identity, settings } = ctx;
20+
const format = detectOutputFormat(settings.output);
21+
const auth = ctx.authStore().describe();
2222

2323
const apiKey = auth.apiKey
2424
? {
@@ -44,8 +44,8 @@ export default defineCommand({
4444
authenticated: false,
4545
message: "Not authenticated.",
4646
hint: [
47-
`API key (model): ${config.binName} auth login --api-key <key> or DASHSCOPE_API_KEY`,
48-
`Console gateway: ${config.binName} auth login --console`,
47+
`API key (model): ${identity.binName} auth login --api-key <key> or DASHSCOPE_API_KEY`,
48+
`Console gateway: ${identity.binName} auth login --console`,
4949
`Get API Key: ${API_KEY_PAGE}`,
5050
].join("\n"),
5151
},
@@ -70,7 +70,7 @@ export default defineCommand({
7070
` Console gateway: ${consoleCred.source} ${consoleCred.masked} (${consoleCred.region}, ${consoleCred.site})`,
7171
);
7272
} else {
73-
emitBare(` Console gateway: not configured (run ${config.binName} auth login --console)`);
73+
emitBare(` Console gateway: not configured (run ${identity.binName} auth login --console)`);
7474
}
7575
},
7676
});

0 commit comments

Comments
 (0)