fix(auth-broker): resolve nested auth.broker.url/token yaml keys
readConfigYaml only read the literal flat dotted key from config.yml, so standard nested YAML (auth: broker: url:) was silently ignored while the rest of the CLI normalizes nested keys. Resolve the dotted key through nested segments, keeping the legacy flat literal-dot form working (nested wins). Fixes #4734
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `auth-broker` config discovery ignoring nested `auth.broker.url` / `auth.broker.token` YAML keys. `readConfigYaml` only read the literal flat dotted key, so standard nested YAML was silently dropped; it now resolves both nested and flat forms (nested wins). ([#4734](https://github.com/can1357/oh-my-pi/issues/4734))
|
||||
|
||||
## [16.3.11] - 2026-07-06
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -71,6 +71,26 @@ interface ConfigSnapshot {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a dotted config key (e.g. `auth.broker.url`) against a parsed YAML
|
||||
* record, accepting both nested form (`auth: { broker: { url } }`) and the
|
||||
* legacy flat literal-dot key (`"auth.broker.url": ...`). Nested wins when both
|
||||
* are present. Returns the value only when it is a string.
|
||||
*/
|
||||
function readDottedString(record: Record<string, unknown>, dottedKey: string): string | undefined {
|
||||
let current: unknown = record;
|
||||
for (const segment of dottedKey.split(".")) {
|
||||
if (current === null || typeof current !== "object" || Array.isArray(current)) {
|
||||
current = undefined;
|
||||
break;
|
||||
}
|
||||
current = (current as Record<string, unknown>)[segment];
|
||||
}
|
||||
if (typeof current === "string") return current;
|
||||
const flat = record[dottedKey];
|
||||
return typeof flat === "string" ? flat : undefined;
|
||||
}
|
||||
|
||||
async function readConfigYaml(agentDir: string): Promise<ConfigSnapshot> {
|
||||
const configPath = path.join(agentDir, "config.yml");
|
||||
try {
|
||||
@@ -78,9 +98,8 @@ async function readConfigYaml(agentDir: string): Promise<ConfigSnapshot> {
|
||||
const parsed = YAML.parse(raw);
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
||||
const record = parsed as Record<string, unknown>;
|
||||
const url = typeof record["auth.broker.url"] === "string" ? (record["auth.broker.url"] as string) : undefined;
|
||||
const token =
|
||||
typeof record["auth.broker.token"] === "string" ? (record["auth.broker.token"] as string) : undefined;
|
||||
const url = readDottedString(record, "auth.broker.url");
|
||||
const token = readDottedString(record, "auth.broker.token");
|
||||
return { url, token };
|
||||
} catch (err) {
|
||||
if (isEnoent(err)) return {};
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { resolveAuthBrokerConfig } from "@oh-my-pi/pi-ai/auth-broker";
|
||||
import { removeWithRetries } from "../../utils/src/temp";
|
||||
import { withEnv } from "./helpers";
|
||||
|
||||
const CLEAR_BROKER_ENV = {
|
||||
OMP_AUTH_BROKER_URL: undefined,
|
||||
OMP_AUTH_BROKER_TOKEN: undefined,
|
||||
} as const;
|
||||
|
||||
describe("auth-broker config.yml key resolution", () => {
|
||||
let agentDir = "";
|
||||
|
||||
beforeEach(async () => {
|
||||
agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-ai-auth-broker-config-"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (agentDir) await removeWithRetries(agentDir);
|
||||
agentDir = "";
|
||||
});
|
||||
|
||||
async function writeConfig(yaml: string): Promise<void> {
|
||||
await Bun.write(path.join(agentDir, "config.yml"), yaml);
|
||||
}
|
||||
|
||||
test("nested YAML keys resolve broker url and token", async () => {
|
||||
await writeConfig(
|
||||
["auth:", " broker:", " url: https://broker.example", " token: nested-token", ""].join("\n"),
|
||||
);
|
||||
await withEnv(CLEAR_BROKER_ENV, async () => {
|
||||
const config = await resolveAuthBrokerConfig({ agentDir });
|
||||
expect(config).toEqual({ url: "https://broker.example", token: "nested-token" });
|
||||
});
|
||||
});
|
||||
|
||||
test("legacy flat dotted keys still resolve", async () => {
|
||||
await writeConfig(['"auth.broker.url": https://flat.example', '"auth.broker.token": flat-token', ""].join("\n"));
|
||||
await withEnv(CLEAR_BROKER_ENV, async () => {
|
||||
const config = await resolveAuthBrokerConfig({ agentDir });
|
||||
expect(config).toEqual({ url: "https://flat.example", token: "flat-token" });
|
||||
});
|
||||
});
|
||||
|
||||
test("nested value wins over the flat dotted key", async () => {
|
||||
await writeConfig(
|
||||
[
|
||||
'"auth.broker.url": https://flat.example',
|
||||
'"auth.broker.token": flat-token',
|
||||
"auth:",
|
||||
" broker:",
|
||||
" url: https://nested.example",
|
||||
" token: nested-token",
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
await withEnv(CLEAR_BROKER_ENV, async () => {
|
||||
const config = await resolveAuthBrokerConfig({ agentDir });
|
||||
expect(config).toEqual({ url: "https://nested.example", token: "nested-token" });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user