delete the getApiKey hook — pi-ai resolves keys natively per provider (owner, 2026-09-26)
baseAgentOptions (teacher-agent.ts) and runSubagent (subagents/pi-run.ts) passed getApiKey: () => openRouterApiKey(). pi-agent-core resolves (config.getApiKey ? await config.getApiKey(provider) : undefined) || config.apiKey (agent-loop.js:191) — a provided hook OVERRIDES pi-ai's native per-provider env resolution, and this one ignored its provider arg: every non-OpenRouter provider was sent the OpenRouter key and 401'd (owner repro: openai and anthropic direct). Deletion restores the architecture's stated intent — the keys seam reflects file-saved keys into env and pi-ai reads them per request. openRouterApiKey() export kept (now consumer-less; visual-maker/ pdf-reader read process.env.OPENROUTER_API_KEY directly for their own OpenRouter-specific fetch paths — correct, untouched). Pinned by tests/api-key-hook.test.ts: built teacher + prober expose NO getApiKey hook. Gates: typecheck 0, 987/987, lint clean, build green.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* The getApiKey-hook fix (owner, 2026-09-26): both Agent constructions
|
||||
* (baseAgentOptions in teacher-agent.ts, runSubagent's loop in
|
||||
* subagents/pi-run.ts) used to pass `getApiKey: () => openRouterApiKey()` —
|
||||
* and pi-agent-core resolves
|
||||
* `(config.getApiKey ? await config.getApiKey(provider) : undefined) || config.apiKey`
|
||||
* (agent-loop.js:191), so a provided hook OVERRIDES pi-ai's native
|
||||
* per-provider env resolution. The hook ignored its provider arg, so every
|
||||
* non-OpenRouter provider was sent the OpenRouter key and 401'd (owner repro:
|
||||
* openai:gpt-5.6-luna, anthropic:claude-haiku-4-5 direct).
|
||||
*
|
||||
* The fix is DELETION: with no hook, pi-ai resolves keys natively from the
|
||||
* environment per request — the architecture's stated intent (models.ts:
|
||||
* "the keys seam reflects file-saved keys into env"). This file pins that
|
||||
* the deletion holds: the built agents must expose NO getApiKey hook, so
|
||||
* native resolution runs for every provider.
|
||||
*
|
||||
* runSubagent's Agent is internal to its promise (no exposed seam); its
|
||||
* hook was deleted in the same commit and its behavior is covered by the
|
||||
* sub-agent suite's faux engines.
|
||||
*/
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { createProberAgent, createTeacherAgent, type TeachingAgent } from "../src/agent/teacher-agent.ts";
|
||||
import type { ProberToolContext } from "../src/agent/tools.ts";
|
||||
import { jobStore } from "../src/store/jobs.ts";
|
||||
import { chatStore } from "../src/store/chats.ts";
|
||||
import { createFauxCore } from "@earendil-works/pi-ai";
|
||||
import type { PiEngine } from "../src/agent/models.ts";
|
||||
|
||||
/** A scripted pi engine — the agent builds need an injected engine since
|
||||
* BYOK removed the factory pins (owner, 2026-09-22). */
|
||||
function fauxEngine(): PiEngine {
|
||||
const faux = createFauxCore({});
|
||||
return { model: faux.getModel(), streamFn: faux.streamSimple };
|
||||
}
|
||||
|
||||
describe("the getApiKey-hook fix (native per-provider key resolution)", () => {
|
||||
it("teacher and prober builds expose NO getApiKey hook — pi-ai resolves keys natively", () => {
|
||||
const chatId = chatStore.createChat("getApiKey pin").id;
|
||||
const ctx: ProberToolContext = { chatId, chatStore, deliver: () => {}, jobStore, spawn: () => {} };
|
||||
const builds: TeachingAgent[] = [
|
||||
createTeacherAgent(undefined, { engine: fauxEngine() }),
|
||||
createProberAgent(ctx, { engine: fauxEngine() }),
|
||||
];
|
||||
for (const agent of builds) {
|
||||
assert.equal(
|
||||
(agent as unknown as { getApiKey?: unknown }).getApiKey,
|
||||
undefined,
|
||||
"the OpenRouter-pinning hook must stay deleted — a set hook overrides pi-ai's native per-provider env resolution for EVERY provider",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user