Skip to content

Commit 25d7026

Browse files
committed
feat(sdk): let bailian provider accept base_url instead of workspace_id
1 parent 1336226 commit 25d7026

5 files changed

Lines changed: 61 additions & 14 deletions

File tree

packages/sdk/src/internal/providers/bailian/adapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export class BailianAdapter implements ProviderAdapter {
8080
private client: BailianClient;
8181
private projectName: string;
8282

83-
constructor(apiKey: string, workspaceId: string, baseUrl?: string, projectName?: string) {
83+
constructor(apiKey: string, workspaceId?: string, baseUrl?: string, projectName?: string) {
8484
this.client = new BailianClient({ apiKey, workspaceId, baseUrl });
8585
this.projectName = projectName ?? "";
8686
}

packages/sdk/src/internal/providers/bailian/client.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { UserError } from "../../errors.ts";
12
import { BaseApiClient } from "../base-client.ts";
23

34
export interface BailianClientConfig {
45
apiKey: string;
5-
workspaceId: string;
6+
workspaceId?: string;
67
baseUrl?: string;
78
}
89

@@ -15,7 +16,13 @@ export class BailianClient extends BaseApiClient {
1516
constructor(config: BailianClientConfig) {
1617
super();
1718
this.apiKey = config.apiKey;
18-
this.baseUrl = config.baseUrl ?? `https://${config.workspaceId}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio`;
19+
if (config.baseUrl) {
20+
this.baseUrl = config.baseUrl;
21+
} else if (config.workspaceId) {
22+
this.baseUrl = `https://${config.workspaceId}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio`;
23+
} else {
24+
throw new UserError("bailian provider requires either base_url or workspace_id");
25+
}
1926
}
2027

2128
protected headers(): Record<string, string> {
Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { z } from "zod";
22

3-
export const bailianConfigSchema = z.object({
4-
api_key: z.string(),
5-
workspace_id: z.string(),
6-
base_url: z.string().optional(),
7-
});
3+
// workspace_id and base_url are both optional but at least one is required: the
4+
// client derives the AgentStudio base URL from workspace_id, or takes base_url
5+
// verbatim. Hosts that only know the full endpoint (e.g. bl's --agentstudio-base-url)
6+
// can supply base_url and skip workspace_id entirely.
7+
export const bailianConfigSchema = z
8+
.object({
9+
api_key: z.string(),
10+
workspace_id: z.string().optional(),
11+
base_url: z.string().optional(),
12+
})
13+
.refine((config) => Boolean(config.workspace_id || config.base_url), {
14+
message: "either workspace_id or base_url is required",
15+
});
816

917
export type BailianConfig = z.infer<typeof bailianConfigSchema>;

packages/sdk/src/internal/providers/registry.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,22 @@ export function allProviders(): ProviderDefinition[] {
5151
return Array.from(registry.values());
5252
}
5353

54+
/**
55+
* Validate a raw provider config against its schema, mapping zod issues to a
56+
* readable UserError. Providers call `.parse()`-style validation through here so
57+
* config mistakes surface as `provider 'x' config invalid: field: message` lines
58+
* instead of a raw ZodError JSON dump leaking to the host's stderr.
59+
*/
60+
function parseProviderConfig(def: ProviderDefinition, rawConfig: unknown): unknown {
61+
const result = def.configSchema.safeParse(rawConfig);
62+
if (result.success) return result.data;
63+
const details = result.error.issues.map((issue) => {
64+
const path = issue.path.join(".");
65+
return path ? `${path}: ${issue.message}` : issue.message;
66+
});
67+
throw new UserError(`Provider '${def.name}' config invalid:\n${details.join("\n")}`);
68+
}
69+
5470
export function buildProviders(
5571
providersConfig: Record<string, unknown>,
5672
projectName?: string,
@@ -62,7 +78,7 @@ export function buildProviders(
6278
if (!def) {
6379
throw new UserError(`Unknown provider '${name}'. Registered: ${Array.from(registry.keys()).join(", ")}`);
6480
}
65-
const parsed = def.configSchema.parse(rawConfig);
81+
const parsed = parseProviderConfig(def, rawConfig);
6682
const adapter = def.createAdapter(parsed, projectName);
6783
validateProviderFacets(def, adapter);
6884
adapters.set(name, adapter);
@@ -78,7 +94,9 @@ export function buildProviders(
7894
const PROVIDER_ENV_VARS: Record<string, Record<string, { env: string[]; required: boolean }>> = {
7995
bailian: {
8096
api_key: { env: ["DASHSCOPE_API_KEY", "BAILIAN_API_KEY"], required: true },
81-
workspace_id: { env: ["BAILIAN_WORKSPACE_ID"], required: true },
97+
// Neither workspace_id nor base_url is required on its own; the config schema
98+
// enforces "at least one" so a host can supply just base_url (BAILIAN_BASE_URL).
99+
workspace_id: { env: ["BAILIAN_WORKSPACE_ID"], required: false },
82100
base_url: { env: ["BAILIAN_BASE_URL"], required: false },
83101
},
84102
qoder: {
@@ -160,7 +178,7 @@ export function buildProviderFromEnv(providerName: string, projectName?: string)
160178
}
161179

162180
const config = resolveProviderConfigFromEnv(providerName);
163-
const parsed = def.configSchema.parse(config);
181+
const parsed = parseProviderConfig(def, config);
164182
const adapter = def.createAdapter(parsed, projectName);
165183
validateProviderFacets(def, adapter);
166184
return adapter;

packages/sdk/tests/unit/bailian.test.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,19 @@ describe("bailianConfigSchema", () => {
3838
expect(() => bailianConfigSchema.parse({ workspace_id: "ws-123" })).toThrow();
3939
});
4040

41-
test("rejects config missing workspace_id", () => {
42-
expect(() => bailianConfigSchema.parse({ api_key: "sk-abc" })).toThrow();
41+
test("accepts base_url without workspace_id", () => {
42+
const result = bailianConfigSchema.parse({
43+
api_key: "sk-abc",
44+
base_url: "https://custom.example.com/api/v1/agentstudio",
45+
});
46+
expect(result.workspace_id).toBeUndefined();
47+
expect(result.base_url).toBe("https://custom.example.com/api/v1/agentstudio");
48+
});
49+
50+
test("rejects config with neither workspace_id nor base_url", () => {
51+
expect(() => bailianConfigSchema.parse({ api_key: "sk-abc" })).toThrow(
52+
/either workspace_id or base_url is required/,
53+
);
4354
});
4455
});
4556

@@ -328,7 +339,10 @@ describe("Bailian mapEnvironment", () => {
328339

329340
test("rejects 'limited' networking instead of silently widening it", () => {
330341
const decl: EnvironmentDecl = {
331-
config: { type: "cloud", networking: { type: "limited", allowed_hosts: ["api.github.com"] } },
342+
config: {
343+
type: "cloud",
344+
networking: { type: "limited", allowed_hosts: ["api.github.com"] },
345+
},
332346
};
333347
// The real API rejects non-unrestricted networking; silently dropping the
334348
// restriction would widen a declared egress boundary, so we must throw.

0 commit comments

Comments
 (0)