claudish/tests/gemini-integration.test.ts

356 lines
12 KiB
TypeScript

/**
* Integration tests for Gemini thought signature workflow
*
* Tests the complete flow: extraction from OpenRouter responses → storage → retrieval → inclusion in tool results
* Uses real data captured from OpenRouter API interactions
*/
import { describe, it, expect, beforeEach } from "bun:test";
import { GeminiAdapter } from "../src/adapters/gemini-adapter";
describe("Gemini Integration Workflow", () => {
let adapter: GeminiAdapter;
beforeEach(() => {
adapter = new GeminiAdapter("google/gemini-3-pro-preview");
});
describe("Complete Workflow", () => {
it("should handle complete workflow from extraction to retrieval", () => {
// Step 1: Simulate receiving streaming chunk with reasoning_details
const streamingChunk = {
id: "gen-1763985429-MxzWCknTGYuK9AfiX4QQ",
choices: [{
delta: {
reasoning_details: [
{
id: "tool_Bash_ZOJxtsiJqi9njkBUmCeV",
type: "reasoning.encrypted",
data: "CiQB4/H/XsukhAagMavyI3vfZtzB0lQLRD5TIh1OQyfMar/wzqoKaQHj8f9e7azlSwPXjAxZ3Vy+SA3Lozr6JjvJah7yLoz34Z44orOB9T5IM3acsExG0w2M+LdYDxSm3WfUqbUJTvs4EmG098y5FWCKWhMG1aVaHNGuQ5uytp+21m8BOw0Qw+Q9mEqd7TYK7gpjAePx/16yxZM4eAE4YppB66hLqV6qjWd6vEJ9lGIMbmqi+t5t4Se/HkBPizrcgbdaOd3Fje5GXRfb1vqv+nhuxWwOx+hAFczJWwtd8d6H/YloE38JqTSNt98sb0odCShJcNnVCjgB4/H/XoJS5Xrj4j5jSsnUSG+rvZi6NKV+La8QIur8jKEeBF0DbTnO+ZNiYzz9GokbPHjkIRKePA==",
format: "google-gemini-v1",
index: 0
}
]
}
}]
};
// Step 2: Extract signatures (this is what proxy-server does)
const extracted = adapter.extractThoughtSignaturesFromReasoningDetails(
streamingChunk.choices[0].delta.reasoning_details
);
expect(extracted.size).toBe(1);
expect(adapter.hasThoughtSignature("tool_Bash_ZOJxtsiJqi9njkBUmCeV")).toBe(true);
// Step 3: Simulate tool result building (what proxy-server does later)
// This is simulating the code at line ~388 in proxy-server.ts
const toolResultMsg: any = {
role: "tool",
content: "command output here",
tool_call_id: "tool_Bash_ZOJxtsiJqi9njkBUmCeV",
};
const signature = adapter.getThoughtSignature("tool_Bash_ZOJxtsiJqi9njkBUmCeV");
if (signature) {
toolResultMsg.extra_content = {
google: {
thought_signature: signature
}
};
}
// Step 4: Verify the signature was included correctly
expect(toolResultMsg.extra_content).toBeDefined();
expect(toolResultMsg.extra_content.google).toBeDefined();
expect(toolResultMsg.extra_content.google.thought_signature).toBe(
"CiQB4/H/XsukhAagMavyI3vfZtzB0lQLRD5TIh1OQyfMar/wzqoKaQHj8f9e7azlSwPXjAxZ3Vy+SA3Lozr6JjvJah7yLoz34Z44orOB9T5IM3acsExG0w2M+LdYDxSm3WfUqbUJTvs4EmG098y5FWCKWhMG1aVaHNGuQ5uytp+21m8BOw0Qw+Q9mEqd7TYK7gpjAePx/16yxZM4eAE4YppB66hLqV6qjWd6vEJ9lGIMbmqi+t5t4Se/HkBPizrcgbdaOd3Fje5GXRfb1vqv+nhuxWwOx+hAFczJWwtd8d6H/YloE38JqTSNt98sb0odCShJcNnVCjgB4/H/XoJS5Xrj4j5jSsnUSG+rvZi6NKV+La8QIur8jKEeBF0DbTnO+ZNiYzz9GokbPHjkIRKePA=="
);
});
it("should handle multiple tool calls in sequence", () => {
// First tool call
const firstChunk = {
choices: [{
delta: {
reasoning_details: [{
id: "tool_Bash_first",
type: "reasoning.encrypted",
data: "signature-1",
format: "google-gemini-v1",
index: 0
}]
}
}]
};
adapter.extractThoughtSignaturesFromReasoningDetails(
firstChunk.choices[0].delta.reasoning_details
);
// Second tool call (simulating a multi-turn conversation)
const secondChunk = {
choices: [{
delta: {
reasoning_details: [{
id: "tool_Bash_second",
type: "reasoning.encrypted",
data: "signature-2",
format: "google-gemini-v1",
index: 0
}]
}
}]
};
adapter.extractThoughtSignaturesFromReasoningDetails(
secondChunk.choices[0].delta.reasoning_details
);
// Verify both signatures are stored
expect(adapter.hasThoughtSignature("tool_Bash_first")).toBe(true);
expect(adapter.hasThoughtSignature("tool_Bash_second")).toBe(true);
// Verify we can retrieve both
const sig1 = adapter.getThoughtSignature("tool_Bash_first");
const sig2 = adapter.getThoughtSignature("tool_Bash_second");
expect(sig1).toBe("signature-1");
expect(sig2).toBe("signature-2");
});
it("should persist signatures across multiple extraction calls", () => {
// Simulate progressive streaming with multiple chunks
const chunk1 = {
choices: [{
delta: {
reasoning_details: [{
id: "tool_1",
type: "reasoning.encrypted",
data: "sig-part-1",
format: "google-gemini-v1",
index: 0
}]
}
}]
};
adapter.extractThoughtSignaturesFromReasoningDetails(
chunk1.choices[0].delta.reasoning_details
);
expect(adapter.getAllThoughtSignatures().size).toBe(1);
// Simulate another chunk with same tool (should override)
const chunk2 = {
choices: [{
delta: {
reasoning_details: [{
id: "tool_1",
type: "reasoning.encrypted",
data: "sig-part-2",
format: "google-gemini-v1",
index: 0
}]
}
}]
};
adapter.extractThoughtSignaturesFromReasoningDetails(
chunk2.choices[0].delta.reasoning_details
);
// Should still have 1 signature (overwritten)
expect(adapter.getAllThoughtSignatures().size).toBe(1);
expect(adapter.getThoughtSignature("tool_1")).toBe("sig-part-2");
});
});
describe("OpenRouter Response Patterns", () => {
it("should handle OpenRouter streaming response with mixed content types", () => {
// OpenRouter sends both reasoning.text and reasoning.encrypted in same response
const mixedChunk = {
choices: [{
delta: {
reasoning_details: [
{
format: "google-gemini-v1",
index: 0,
type: "reasoning.text",
text: "**Analyzing Command**\n\nDecided to use Bash tool..."
},
{
id: "tool_Bash_real",
format: "google-gemini-v1",
index: 0,
type: "reasoning.encrypted",
data: "CiQB4/H/XsukhAagMavyI3vfZtzB0lQLRD5TIh1OQyfMar/wzqoKaQHj8f9e7azlSwPXjAxZ3Vy+SA3Lozr6JjvJah7yLoz34Z44orOB9T5IM3acsExG0w2M+LdYDxSm3WfUqbUJTvs4EmG098y5FWCKWhMG1aVaHNGuQ5uytp+21m8BOw0Qw+Q9mEqd7TYK7gpjAePx/16yxZM4eAE4YppB66hLqV6qjWd6vEJ9lGIMbmqi+t5t4Se/HkBPizrcgbdaOd3Fje5GXRfb1vqv+nhuxWwOx+hAFczJWwtd8d6H/YloE38JqTSNt98sb0odCShJcNnVCjgB4/H/XoJS5Xrj4j5jSsnUSG+rvZi6NKV+La8QIur8jKEeBF0DbTnO+ZNiYzz9GokbPHjkIRKePA=="
}
]
}
}]
};
const extracted = adapter.extractThoughtSignaturesFromReasoningDetails(
mixedChunk.choices[0].delta.reasoning_details
);
// Should only extract encrypted type, not text type
expect(extracted.size).toBe(1);
expect(extracted.has("tool_Bash_real")).toBe(true);
expect(extracted.has(undefined as any)).toBe(false);
const signature = adapter.getThoughtSignature("tool_Bash_real");
expect(signature).toBeTruthy();
expect(signature).toContain("CiQB4/H/X");
});
it("should handle non-streaming response format", () => {
// Non-streaming responses have same structure but in message.reasoning_details
const nonStreamingResponse = {
choices: [{
message: {
reasoning_details: [
{
id: "tool_Bash_non_stream",
format: "google-gemini-v1",
index: 0,
type: "reasoning.encrypted",
data: "encrypted-signature-data-here"
}
]
}
}]
};
const extracted = adapter.extractThoughtSignaturesFromReasoningDetails(
nonStreamingResponse.choices[0].message.reasoning_details
);
expect(extracted.size).toBe(1);
expect(adapter.getThoughtSignature("tool_Bash_non_stream")).toBe("encrypted-signature-data-here");
});
it("should handle parallel function calls", () => {
// Gemini can make multiple tool calls in parallel
const parallelChunk = {
choices: [{
delta: {
reasoning_details: [
{
id: "tool_Bash_parallel_1",
type: "reasoning.encrypted",
data: "sig-parallel-1",
format: "google-gemini-v1",
index: 0
},
{
id: "tool_Bash_parallel_2",
type: "reasoning.encrypted",
data: "sig-parallel-2",
format: "google-gemini-v1",
index: 1
},
{
id: "tool_Bash_parallel_3",
type: "reasoning.encrypted",
data: "sig-parallel-3",
format: "google-gemini-v1",
index: 2
}
]
}
}]
};
const extracted = adapter.extractThoughtSignaturesFromReasoningDetails(
parallelChunk.choices[0].delta.reasoning_details
);
expect(extracted.size).toBe(3);
expect(adapter.hasThoughtSignature("tool_Bash_parallel_1")).toBe(true);
expect(adapter.hasThoughtSignature("tool_Bash_parallel_2")).toBe(true);
expect(adapter.hasThoughtSignature("tool_Bash_parallel_3")).toBe(true);
// Verify all signatures are distinct
const sigs = adapter.getAllThoughtSignatures();
expect(sigs.get("tool_Bash_parallel_1")).toBe("sig-parallel-1");
expect(sigs.get("tool_Bash_parallel_2")).toBe("sig-parallel-2");
expect(sigs.get("tool_Bash_parallel_3")).toBe("sig-parallel-3");
});
});
describe("Edge Cases", () => {
it("should handle tool call with same ID as previous (override)", () => {
const first = {
choices: [{
delta: {
reasoning_details: [{
id: "tool_same_id",
type: "reasoning.encrypted",
data: "first-signature",
format: "google-gemini-v1",
index: 0
}]
}
}]
};
adapter.extractThoughtSignaturesFromReasoningDetails(
first.choices[0].delta.reasoning_details
);
expect(adapter.getThoughtSignature("tool_same_id")).toBe("first-signature");
// Second call with same ID
const second = {
choices: [{
delta: {
reasoning_details: [{
id: "tool_same_id",
type: "reasoning.encrypted",
data: "second-signature",
format: "google-gemini-v1",
index: 0
}]
}
}]
};
adapter.extractThoughtSignaturesFromReasoningDetails(
second.choices[0].delta.reasoning_details
);
expect(adapter.getThoughtSignature("tool_same_id")).toBe("second-signature");
});
it("should handle reset between requests", () => {
const firstRequest = {
choices: [{
delta: {
reasoning_details: [{
id: "tool_request_1",
type: "reasoning.encrypted",
data: "sig-request-1",
format: "google-gemini-v1",
index: 0
}]
}
}]
};
adapter.extractThoughtSignaturesFromReasoningDetails(
firstRequest.choices[0].delta.reasoning_details
);
expect(adapter.hasThoughtSignature("tool_request_1")).toBe(true);
// Reset (simulates new request)
adapter.reset();
expect(adapter.hasThoughtSignature("tool_request_1")).toBe(false);
expect(adapter.getAllThoughtSignatures().size).toBe(0);
});
});
});