fix(pi-coding-agent): skip localhost dummy key when fallback resolver provides a configured key
Custom OpenAI-compatible providers running on localhost (e.g. a local proxy) with an explicit apiKey in models.json received 'local-no-key-needed' during compaction instead of their configured key, causing 401 errors. The localhost shortcut in AuthStorage.getApiKey() was unconditional. Normal dispatch calls getApiKeyForProvider() which skips the baseUrl check entirely, so the fallback resolver was reached and the real key was used. Compaction calls getApiKey(model) which passes baseUrl, hitting the shortcut first. Closes #4106
This commit is contained in:
parent
eb7256b818
commit
1ef6ba16f9
2 changed files with 39 additions and 1 deletions
|
|
@ -531,3 +531,41 @@ describe("AuthStorage — getEarliestBackoffExpiry", () => {
|
|||
assert.equal(expiry, nearExpiry, "should return the nearest (smallest) expiry");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── localhost baseUrl shortcut ────────────────────────────────────────────────
|
||||
|
||||
describe("AuthStorage — localhost baseUrl shortcut", () => {
|
||||
it("returns 'local-no-key-needed' for localhost provider with no configured key", async () => {
|
||||
const storage = inMemory({});
|
||||
const key = await storage.getApiKey("ollama", undefined, { baseUrl: "http://localhost:11434" });
|
||||
assert.equal(key, "local-no-key-needed");
|
||||
});
|
||||
|
||||
it("returns 'local-no-key-needed' for 127.0.0.1 provider with no configured key", async () => {
|
||||
const storage = inMemory({});
|
||||
const key = await storage.getApiKey("custom", undefined, { baseUrl: "http://127.0.0.1:8080/v1" });
|
||||
assert.equal(key, "local-no-key-needed");
|
||||
});
|
||||
|
||||
it("returns configured key from fallback resolver for localhost custom provider (#4106)", async () => {
|
||||
// Regression test: compaction called getApiKey(model) where model.baseUrl is localhost.
|
||||
// The localhost shortcut must NOT override an explicitly configured apiKey from models.json.
|
||||
const storage = inMemory({});
|
||||
storage.setFallbackResolver((provider) =>
|
||||
provider === "cliproxy" ? "sk-real-proxy-key" : undefined,
|
||||
);
|
||||
|
||||
const key = await storage.getApiKey("cliproxy", undefined, { baseUrl: "http://localhost:8317/v1" });
|
||||
assert.equal(key, "sk-real-proxy-key");
|
||||
});
|
||||
|
||||
it("returns configured key from fallback resolver when baseUrl uses 127.0.0.1 (#4106)", async () => {
|
||||
const storage = inMemory({});
|
||||
storage.setFallbackResolver((provider) =>
|
||||
provider === "myproxy" ? "sk-myproxy-key" : undefined,
|
||||
);
|
||||
|
||||
const key = await storage.getApiKey("myproxy", undefined, { baseUrl: "http://127.0.0.1:9000/v1" });
|
||||
assert.equal(key, "sk-myproxy-key");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -819,7 +819,7 @@ export class AuthStorage {
|
|||
*/
|
||||
async getApiKey(providerId: string, sessionId?: string, options?: { baseUrl?: string }): Promise<string | undefined> {
|
||||
// If the model has a local baseUrl, return a dummy key to avoid auth blocking
|
||||
if (options?.baseUrl) {
|
||||
if (options?.baseUrl && !this.fallbackResolver?.(providerId)) {
|
||||
try {
|
||||
const hostname = new URL(options.baseUrl).hostname;
|
||||
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue