mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:30:47 +00:00
fix: send copilot headers during compaction
This commit is contained in:
@@ -99,7 +99,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Models/fallback: resolve bare fallback model provider ids before model switching, so configured fallback chains keep working when a fallback is named without an explicit provider prefix. Thanks @steipete.
|
||||
- Voice-call/Telnyx: preserve inbound/outbound callback metadata and read transcription text from Telnyx's current `transcription_data` payload. Thanks @steipete.
|
||||
- Providers/DeepSeek: wire V4 thinking controls and OpenAI-compatible replay policy so follow-up turns preserve DeepSeek `reasoning_content`, while the None/off thinking path strips replayed reasoning fields. Fixes #70931. Thanks @lsdsjy.
|
||||
- Providers/GitHub Copilot: align Copilot request headers across Anthropic and Responses transports, including tool-result and image follow-up turns, without enabling unverified Responses continuation. Thanks @steipete.
|
||||
- Providers/GitHub Copilot: align Copilot request headers across Anthropic, Responses, and built-in compaction summarization paths, including tool-result and image follow-up turns, without enabling unverified Responses continuation. Thanks @steipete.
|
||||
- Codex harness: send verbose tool progress to chat channels for native app-server runs, matching the Pi harness `/verbose on` and `/verbose full` behavior. (#70966) Thanks @jalehman.
|
||||
- Codex models: fetch paginated Codex app-server model catalogs, mark truncated `/codex models` output, and keep ChatGPT OAuth defaults on the `openai-codex/gpt-5.5` route instead of the OpenAI API-key route. Thanks @steipete.
|
||||
- Codex status: report Codex CLI OAuth as `oauth (codex-cli)` for native `codex/*` sessions instead of showing unknown auth. Fixes #70688. Thanks @jb510.
|
||||
|
||||
@@ -92,9 +92,9 @@ openclaw models auth login --provider github-copilot --method device --set-defau
|
||||
|
||||
<Accordion title="Request compatibility">
|
||||
OpenClaw sends Copilot IDE-style request headers on Copilot transports,
|
||||
including tool-result and image follow-up turns. It does not enable
|
||||
provider-level Responses continuation for Copilot unless that behavior has
|
||||
been verified against Copilot's API.
|
||||
including built-in compaction, tool-result, and image follow-up turns. It
|
||||
does not enable provider-level Responses continuation for Copilot unless
|
||||
that behavior has been verified against Copilot's API.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Environment variable resolution order">
|
||||
|
||||
@@ -1286,6 +1286,52 @@ describe("compaction-safeguard recent-turn preservation", () => {
|
||||
expect(droppedCall?.customInstructions).toContain("Keep security caveats.");
|
||||
});
|
||||
|
||||
it("adds Copilot IDE headers to built-in compaction summarization", async () => {
|
||||
mockSummarizeInStages.mockReset();
|
||||
mockSummarizeInStages.mockResolvedValue("mock summary");
|
||||
|
||||
const sessionManager = stubSessionManager();
|
||||
const model = createAnthropicModelFixture({
|
||||
id: "gpt-5.4",
|
||||
name: "gpt-5.4",
|
||||
provider: "github-copilot",
|
||||
api: "openai-responses" as const,
|
||||
baseUrl: "https://api.githubcopilot.com",
|
||||
});
|
||||
setCompactionSafeguardRuntime(sessionManager, { model, recentTurnsPreserve: 0 });
|
||||
|
||||
const getApiKeyAndHeadersMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
apiKey: "github-token",
|
||||
headers: { "X-Test": "1" },
|
||||
});
|
||||
const mockContext = createCompactionContext({
|
||||
sessionManager,
|
||||
getApiKeyAndHeadersMock,
|
||||
});
|
||||
const compactionHandler = createCompactionHandler();
|
||||
const event = createCompactionEvent({
|
||||
messageText: "summarize me",
|
||||
tokensBefore: 1000,
|
||||
});
|
||||
(event.preparation as { settings?: { reserveTokens: number } }).settings = {
|
||||
reserveTokens: 4000,
|
||||
};
|
||||
|
||||
const result = (await compactionHandler(event, mockContext)) as { cancel?: boolean };
|
||||
|
||||
expect(result.cancel).not.toBe(true);
|
||||
const summaryCall = mockSummarizeInStages.mock.calls.at(-1)?.[0];
|
||||
expect(summaryCall?.headers).toMatchObject({
|
||||
"Copilot-Integration-Id": "vscode-chat",
|
||||
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
||||
"Openai-Organization": "github-copilot",
|
||||
"User-Agent": "GitHubCopilotChat/0.26.7",
|
||||
"X-Test": "1",
|
||||
"x-initiator": "user",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not retry summaries unless quality guard is explicitly enabled", async () => {
|
||||
mockSummarizeInStages.mockReset();
|
||||
mockSummarizeInStages.mockResolvedValue("summary missing headings");
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
summarizeInStages,
|
||||
} from "../compaction.js";
|
||||
import { collectTextContentBlocks } from "../content-blocks.js";
|
||||
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "../copilot-dynamic-headers.js";
|
||||
import { isTimeoutError } from "../failover-error.js";
|
||||
import { repairToolUseResultPairing } from "../session-transcript-repair.js";
|
||||
import { extractToolCallsFromAssistant, extractToolResultId } from "../tool-call-id.js";
|
||||
@@ -236,6 +237,26 @@ async function resolveModelAuth(
|
||||
return { ok: true, apiKey: requestAuth.apiKey, headers: requestAuth.headers };
|
||||
}
|
||||
|
||||
function buildCompactionSummaryHeaders(params: {
|
||||
model: NonNullable<ExtensionContext["model"]>;
|
||||
messages: AgentMessage[];
|
||||
headers?: Record<string, string>;
|
||||
}): Record<string, string> | undefined {
|
||||
if (params.model.provider !== "github-copilot") {
|
||||
return params.headers;
|
||||
}
|
||||
const messages = params.messages as unknown as Parameters<
|
||||
typeof buildCopilotDynamicHeaders
|
||||
>[0]["messages"];
|
||||
return {
|
||||
...buildCopilotDynamicHeaders({
|
||||
messages,
|
||||
hasImages: hasCopilotVisionInput(messages),
|
||||
}),
|
||||
...params.headers,
|
||||
};
|
||||
}
|
||||
|
||||
function clampNonNegativeInt(value: unknown, fallback: number): number {
|
||||
const normalized = typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
||||
return Math.max(0, Math.floor(normalized));
|
||||
@@ -880,12 +901,17 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void {
|
||||
return { cancel: true };
|
||||
}
|
||||
const apiKey = authResult.apiKey ?? "";
|
||||
const headers = authResult.headers;
|
||||
const authHeaders = authResult.headers;
|
||||
|
||||
try {
|
||||
const modelContextWindow = resolveContextWindowTokens(model);
|
||||
const contextWindowTokens = runtime?.contextWindowTokens ?? modelContextWindow;
|
||||
let messagesToSummarize = preparation.messagesToSummarize;
|
||||
const headers = buildCompactionSummaryHeaders({
|
||||
model,
|
||||
messages: messagesToSummarize,
|
||||
headers: authHeaders,
|
||||
});
|
||||
const qualityGuardEnabled = runtime?.qualityGuardEnabled ?? false;
|
||||
const qualityGuardMaxRetries = resolveQualityGuardMaxRetries(runtime?.qualityGuardMaxRetries);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user