fix(coding-agent): preserve extension auth endpoints

closes #7579
This commit is contained in:
Vegard Stikbakke
2026-08-04 12:37:02 +02:00
parent 42a06f9477
commit e741cb05ca
7 changed files with 17 additions and 47 deletions
+1
View File
@@ -109,6 +109,7 @@
- Fixed concurrent in-memory credential mutations losing unrelated provider updates by serializing their read-modify-write sections.
- Updated `undici` to 8.9.0 and the packaged `brace-expansion` to 5.0.9 to address GHSA-8xcm-r25x-g524, GHSA-4cwx-7wf7-3272, GHSA-m8rv-5g2x-5cg5, GHSA-jr45-8vmc-qm54, GHSA-v3r7-h72x-cjcm, and GHSA-rgw5-rvv9-x895.
- Fixed GitHub Copilot compaction and branch summaries using the Individual endpoint instead of the credential-resolved Business or Enterprise endpoint ([#6768](https://github.com/earendil-works/pi/issues/6768)).
- Fixed extension model calls dropping credential-resolved endpoints when forwarding request authentication, including custom compaction with GitHub Copilot Business and Enterprise accounts ([#7579](https://github.com/earendil-works/pi/issues/7579)).
## [0.83.0] - 2026-07-29
@@ -31,17 +31,6 @@ export default function (pi: ExtensionAPI) {
return;
}
// Resolve request auth for the summarization model
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
if (!auth.ok) {
ctx.ui.notify(`Compaction auth failed: ${auth.error}`, "warning");
return;
}
if (!auth.apiKey) {
ctx.ui.notify(`No API key for ${model.provider}, using default compaction`, "warning");
return;
}
// Combine all messages for full summary
const allMessages = [...messagesToSummarize, ...turnPrefixMessages];
@@ -91,9 +80,6 @@ ${conversationText}
model,
{ messages: summaryMessages },
{
apiKey: auth.apiKey,
headers: auth.headers,
env: auth.env,
maxTokens: 8192,
signal,
cacheRetention: "none",
@@ -117,11 +117,6 @@ export default function (pi: ExtensionAPI) {
loader.onAbort = () => done(null);
const doGenerate = async () => {
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
if (!auth.ok || !auth.apiKey) {
throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
}
const userMessage: Message = {
role: "user",
content: [
@@ -137,9 +132,6 @@ export default function (pi: ExtensionAPI) {
ctx.model!,
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
{
apiKey: auth.apiKey,
headers: auth.headers,
env: auth.env,
signal: loader.signal,
cacheRetention: "none",
sessionId: uuidv7(),
@@ -77,10 +77,6 @@ export default function (pi: ExtensionAPI) {
// Do the work
const doExtract = async () => {
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
if (!auth.ok || !auth.apiKey) {
throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
}
const userMessage: UserMessage = {
role: "user",
content: [{ type: "text", text: lastAssistantText! }],
@@ -90,7 +86,7 @@ export default function (pi: ExtensionAPI) {
const response = await ctx.modelRegistry.complete(
ctx.model!,
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
{ apiKey: auth.apiKey, headers: auth.headers, env: auth.env, signal: loader.signal },
{ signal: loader.signal },
);
if (response.stopReason === "aborted") {
@@ -1,5 +1,4 @@
import { uuidv7 } from "@earendil-works/pi-ai";
import { complete, getModel } from "@earendil-works/pi-ai/compat";
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
import { DynamicBorder, getMarkdownTheme } from "@earendil-works/pi-coding-agent";
import { Container, Markdown, matchesKey, Text } from "@earendil-works/pi-tui";
@@ -161,20 +160,13 @@ export default function (pi: ExtensionAPI) {
ctx.ui.notify("Preparing summary...", "info");
}
const model = getModel("openai", "gpt-5.2");
if (!model && ctx.hasUI) {
ctx.ui.notify("Model openai/gpt-5.2 not found", "warning");
const model = ctx.modelRegistry.find("openai", "gpt-5.2");
if (!model) {
if (ctx.hasUI) ctx.ui.notify("Model openai/gpt-5.2 not found", "warning");
return;
}
const auth = model ? await ctx.modelRegistry.getApiKeyAndHeaders(model) : undefined;
if (auth && !auth.ok && ctx.hasUI) {
ctx.ui.notify(auth.error, "warning");
}
if (auth?.ok && !auth.apiKey && ctx.hasUI) {
ctx.ui.notify("No API key for openai/gpt-5.2", "warning");
}
if (!model || !auth?.ok || !auth.apiKey) {
if (!ctx.modelRegistry.hasConfiguredAuth(model)) {
if (ctx.hasUI) ctx.ui.notify("No authentication configured for openai/gpt-5.2", "warning");
return;
}
@@ -186,13 +178,10 @@ export default function (pi: ExtensionAPI) {
},
];
const response = await complete(
const response = await ctx.modelRegistry.complete(
model,
{ messages: summaryMessages },
{
apiKey: auth.apiKey,
headers: auth.headers,
env: auth.env,
reasoningEffort: "high",
cacheRetention: "none",
sessionId: uuidv7(),
@@ -19,6 +19,7 @@ export type ResolvedRequestAuth =
ok: true;
apiKey?: string;
headers?: ProviderHeaders;
baseUrl?: string;
env?: Record<string, string>;
}
| { ok: false; error: string };
@@ -74,6 +75,7 @@ export class ModelRegistry {
ok: true,
apiKey: resolution.auth.apiKey,
headers: resolution.auth.headers,
...(resolution.auth.baseUrl ? { baseUrl: resolution.auth.baseUrl } : {}),
env: resolution.env,
};
} catch (error) {
@@ -109,7 +109,6 @@ describe("Documentation example", () => {
ui: { notify: vi.fn() },
modelRegistry: {
find: vi.fn(() => model),
getApiKeyAndHeaders: vi.fn(async () => ({ ok: true, apiKey: "fake-key" })),
complete,
},
},
@@ -118,7 +117,12 @@ describe("Documentation example", () => {
expect(complete).toHaveBeenCalledWith(
model,
expect.objectContaining({ messages: expect.any(Array) }),
expect.objectContaining({ apiKey: "fake-key", maxTokens: 8192 }),
expect.objectContaining({ maxTokens: 8192 }),
);
expect(complete).not.toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ apiKey: expect.anything() }),
);
expect(result).toMatchObject({
compaction: {