mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 17:48:26 +00:00
fix: sanitize thinking blocks for GitHub Copilot Claude models (openclaw#19459) thanks @jackheuberger
Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: jackheuberger <12731288+jackheuberger@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -284,4 +284,172 @@ describe("sanitizeSessionHistory", () => {
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("drops assistant thinking blocks for github-copilot models", async () => {
|
||||
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "hello" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "internal",
|
||||
thinkingSignature: "reasoning_text",
|
||||
},
|
||||
{ type: "text", text: "hi" },
|
||||
],
|
||||
},
|
||||
] as unknown as AgentMessage[];
|
||||
|
||||
const result = await sanitizeSessionHistory({
|
||||
messages,
|
||||
modelApi: "openai-completions",
|
||||
provider: "github-copilot",
|
||||
modelId: "claude-opus-4.6",
|
||||
sessionManager: makeMockSessionManager(),
|
||||
sessionId: TEST_SESSION_ID,
|
||||
});
|
||||
|
||||
expect(result[1]?.role).toBe("assistant");
|
||||
const assistant = result[1] as Extract<AgentMessage, { role: "assistant" }>;
|
||||
expect(assistant.content).toEqual([{ type: "text", text: "hi" }]);
|
||||
});
|
||||
|
||||
it("preserves assistant turn when all content is thinking blocks (github-copilot)", async () => {
|
||||
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "hello" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "some reasoning",
|
||||
thinkingSignature: "reasoning_text",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ role: "user", content: "follow up" },
|
||||
] as unknown as AgentMessage[];
|
||||
|
||||
const result = await sanitizeSessionHistory({
|
||||
messages,
|
||||
modelApi: "openai-completions",
|
||||
provider: "github-copilot",
|
||||
modelId: "claude-opus-4.6",
|
||||
sessionManager: makeMockSessionManager(),
|
||||
sessionId: TEST_SESSION_ID,
|
||||
});
|
||||
|
||||
// Assistant turn should be preserved (not dropped) to maintain turn alternation
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[1]?.role).toBe("assistant");
|
||||
const assistant = result[1] as Extract<AgentMessage, { role: "assistant" }>;
|
||||
expect(assistant.content).toEqual([{ type: "text", text: "" }]);
|
||||
});
|
||||
|
||||
it("preserves tool_use blocks when dropping thinking blocks (github-copilot)", async () => {
|
||||
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "read a file" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "I should use the read tool",
|
||||
thinkingSignature: "reasoning_text",
|
||||
},
|
||||
{ type: "toolCall", id: "tool_123", name: "read", arguments: { path: "/tmp/test" } },
|
||||
{ type: "text", text: "Let me read that file." },
|
||||
],
|
||||
},
|
||||
] as unknown as AgentMessage[];
|
||||
|
||||
const result = await sanitizeSessionHistory({
|
||||
messages,
|
||||
modelApi: "openai-completions",
|
||||
provider: "github-copilot",
|
||||
modelId: "claude-opus-4.6",
|
||||
sessionManager: makeMockSessionManager(),
|
||||
sessionId: TEST_SESSION_ID,
|
||||
});
|
||||
|
||||
expect(result[1]?.role).toBe("assistant");
|
||||
const assistant = result[1] as Extract<AgentMessage, { role: "assistant" }>;
|
||||
const types = assistant.content.map((b: { type: string }) => b.type);
|
||||
expect(types).toContain("toolCall");
|
||||
expect(types).toContain("text");
|
||||
expect(types).not.toContain("thinking");
|
||||
});
|
||||
|
||||
it("does not drop thinking blocks for non-copilot providers", async () => {
|
||||
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "hello" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "internal",
|
||||
thinkingSignature: "some_sig",
|
||||
},
|
||||
{ type: "text", text: "hi" },
|
||||
],
|
||||
},
|
||||
] as unknown as AgentMessage[];
|
||||
|
||||
const result = await sanitizeSessionHistory({
|
||||
messages,
|
||||
modelApi: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
modelId: "claude-opus-4-6",
|
||||
sessionManager: makeMockSessionManager(),
|
||||
sessionId: TEST_SESSION_ID,
|
||||
});
|
||||
|
||||
expect(result[1]?.role).toBe("assistant");
|
||||
const assistant = result[1] as Extract<AgentMessage, { role: "assistant" }>;
|
||||
const types = assistant.content.map((b: { type: string }) => b.type);
|
||||
expect(types).toContain("thinking");
|
||||
});
|
||||
|
||||
it("does not drop thinking blocks for non-claude copilot models", async () => {
|
||||
vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false);
|
||||
|
||||
const messages = [
|
||||
{ role: "user", content: "hello" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "internal",
|
||||
thinkingSignature: "some_sig",
|
||||
},
|
||||
{ type: "text", text: "hi" },
|
||||
],
|
||||
},
|
||||
] as unknown as AgentMessage[];
|
||||
|
||||
const result = await sanitizeSessionHistory({
|
||||
messages,
|
||||
modelApi: "openai-completions",
|
||||
provider: "github-copilot",
|
||||
modelId: "gpt-5.2",
|
||||
sessionManager: makeMockSessionManager(),
|
||||
sessionId: TEST_SESSION_ID,
|
||||
});
|
||||
|
||||
expect(result[1]?.role).toBe("assistant");
|
||||
const assistant = result[1] as Extract<AgentMessage, { role: "assistant" }>;
|
||||
const types = assistant.content.map((b: { type: string }) => b.type);
|
||||
expect(types).toContain("thinking");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user