Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/agents/auth-change.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
解析分工:

- `resolveApiKey()` — `auth: "apiKey"` 命令;优先级 `--api-key` > `DASHSCOPE_API_KEY` > config `api_key`
- `resolveModelBaseUrl()` — model base URL;优先级 `--base-url` > `DASHSCOPE_BASE_URL` > config `base_url` > `REGIONS.cn`,返回前统一去除 query、fragment、尾斜杠和已知 SDK/API Base 后缀,同时保留自定义网关前缀
- `resolveModelBaseUrl()` — model base URL;优先级 `--base-url` > `DASHSCOPE_BASE_URL` > config `base_url` > `REGIONS.cn`,返回前统一归一化为 URL origin(仅保留协议、host 和显式端口,去除 path、query、fragment)
- `--config` 只选择 config 文件 block,不提升该 block 的字段优先级;内置套餐 Profile(当前为 `token-plan`)的预设仅在登录时物化写入,运行时继续走统一的 flag > env > selected config file > 默认值
- 显式 `auth login --config <name>` 在凭证验证并落盘成功后自动激活目标 Profile;未传
`--config` 时继续写当前激活项,失败和 dry-run 不切换
Expand Down
4 changes: 2 additions & 2 deletions packages/commands/tests/config-ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ test("POST /api/profile 写命名 profile(timeout 强制为 number),空串
expect(readConfigFile("stage")).toMatchObject({
api_key: "sk-stage",
timeout: 90,
base_url: "https://proxy.example.com/team",
base_url: "https://proxy.example.com",
});
const rawConfig = JSON.parse(readFileSync(getConfigPath(), "utf8"));
expect(rawConfig.stage.base_url).toBe("https://proxy.example.com/team");
expect(rawConfig.stage.base_url).toBe("https://proxy.example.com");

// 空串清除 api_key(整块替换)
const clear = await httpJson(port, "POST", `/api/profile?token=${TOKEN}`, {
Expand Down
4 changes: 2 additions & 2 deletions packages/commands/tests/e2e/config.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,10 @@ describe("e2e: config", () => {
);
expect(setResult.exitCode, setResult.stderr).toBe(0);
expect(parseStdoutJson<{ base_url?: string }>(setResult.stdout).base_url).toBe(
"https://proxy.example.com/bailian",
"https://proxy.example.com",
);
expect(JSON.parse(readFileSync(join(configDir, "config.json"), "utf8")).base_url).toBe(
"https://proxy.example.com/bailian",
"https://proxy.example.com",
);

const invalidResult = await runCommandE2e(
Expand Down
22 changes: 4 additions & 18 deletions packages/core/src/config/model-base-url.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { BailianError } from "../errors/base.ts";
import { ExitCode } from "../errors/codes.ts";

const KNOWN_API_BASE_SUFFIXES = ["/compatible-mode/v1", "/apps/anthropic"] as const;

/**
* Normalize a model-service base URL while preserving custom gateway prefixes.
* CLI endpoints append their own API paths, so known SDK/API base suffixes must
* not remain in the stored or resolved base URL.
* Normalize a model-service base URL to its origin.
* CLI endpoints append their own API paths, so user-provided paths, query
* parameters, and fragments must not remain in the stored or resolved base URL.
*/
export function normalizeModelBaseUrl(input: string): string {
const trimmed = input.trim();
Expand All @@ -21,19 +19,7 @@ export function normalizeModelBaseUrl(input: string): string {
throw invalidModelBaseUrl(input);
}

parsed.search = "";
parsed.hash = "";

let pathname = parsed.pathname.replace(/\/+$/, "");
const knownSuffix = KNOWN_API_BASE_SUFFIXES.find(
(suffix) => pathname === suffix || pathname.endsWith(suffix),
);
if (knownSuffix) {
pathname = pathname.slice(0, -knownSuffix.length).replace(/\/+$/, "");
}
parsed.pathname = pathname || "/";

return parsed.toString().replace(/\/$/, "");
return parsed.origin;
}

function invalidModelBaseUrl(input: string): BailianError {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/config-priority.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ test("baseUrl:flag > env > file > 默认,所有来源统一归一化", () => {
const file: ConfigFile = { base_url: "https://file.example.com/gateway/" };
expect(resolveModelBaseUrl(src({ flags, env, file }))).toBe("https://flag.example.com");
expect(resolveModelBaseUrl(src({ env, file }))).toBe("https://env.example.com");
expect(resolveModelBaseUrl(src({ file }))).toBe("https://file.example.com/gateway");
expect(resolveModelBaseUrl(src({ file }))).toBe("https://file.example.com");
expect(resolveModelBaseUrl(src({}))).toBe("https://dashscope.aliyuncs.com");
});

Expand Down
4 changes: 2 additions & 2 deletions packages/core/tests/config-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ test("ConfigStore/AuthStore 写入前归一化 model Base URL", async () => {
await configStore.write({
base_url: "https://proxy.example.com/bailian/compatible-mode/v1/?query=one#fragment",
});
expect(readConfigFile().base_url).toBe("https://proxy.example.com/bailian");
expect(readConfigFile().base_url).toBe("https://proxy.example.com");
expect(JSON.parse(readFileSync(getConfigPath(), "utf8")).base_url).toBe(
"https://proxy.example.com/bailian",
"https://proxy.example.com",
);

const authStore = makeAuthStore(buildSources({}));
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ test("parseConfigFile accepts only well-formed http(s) base_url", () => {
expect(
parseConfigFile({ base_url: "https://proxy.example.com/team/compatible-mode/v1?x=1#y" })
.base_url,
).toBe("https://proxy.example.com/team");
).toBe("https://proxy.example.com");
// Previously accepted because the value merely "starts with http".
expect(parseConfigFile({ base_url: "httpfoo://evil" }).base_url).toBeUndefined();
expect(parseConfigFile({ base_url: "not a url" }).base_url).toBeUndefined();
Expand Down
27 changes: 13 additions & 14 deletions packages/core/tests/model-base-url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,27 @@ import { expect, test } from "vite-plus/test";
import { BailianError } from "../src/errors/base.ts";
import { normalizeModelBaseUrl } from "../src/config/model-base-url.ts";

test("normalizeModelBaseUrl removes URL noise and known API base suffixes", () => {
test("normalizeModelBaseUrl keeps only the URL origin", () => {
expect(normalizeModelBaseUrl(" https://dashscope.aliyuncs.com/?region=cn#docs ")).toBe(
"https://dashscope.aliyuncs.com",
);
expect(
normalizeModelBaseUrl("https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/"),
normalizeModelBaseUrl(
"https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1/chat/completions",
),
).toBe("https://token-plan.cn-beijing.maas.aliyuncs.com");
expect(normalizeModelBaseUrl("https://example.com/api/v1/agentstudio")).toBe(
"https://example.com",
);
expect(
normalizeModelBaseUrl("https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic"),
).toBe("https://token-plan.cn-beijing.maas.aliyuncs.com");
normalizeModelBaseUrl(
"https://example.com/api/v1/services/aigc/image-generation/generation?model=qwen#docs",
),
).toBe("https://example.com");
});

test("normalizeModelBaseUrl preserves ports and custom gateway prefixes", () => {
expect(normalizeModelBaseUrl("http://localhost:8080/bailian/")).toBe(
"http://localhost:8080/bailian",
);
expect(
normalizeModelBaseUrl("https://proxy.example.com/bailian/compatible-mode/v1?tenant=one"),
).toBe("https://proxy.example.com/bailian");
expect(normalizeModelBaseUrl("https://proxy.example.com/custom/apps/anthropic#section")).toBe(
"https://proxy.example.com/custom",
);
test("normalizeModelBaseUrl preserves explicit ports while removing paths", () => {
expect(normalizeModelBaseUrl("http://localhost:8080/bailian/")).toBe("http://localhost:8080");
});

test("normalizeModelBaseUrl rejects non-http and malformed URLs", () => {
Expand Down