Files
claude-code/src/services/api/gemini/__tests__/streamAdapter.test.ts
T
SaltedFish555 0da5ec09e8 feat: 添加gemini协议适配 (#125)
* feat: 添加gemini协议适配

* Remove unrelated local files from Gemini PR
2026-04-06 09:55:20 +08:00

176 lines
4.5 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { adaptGeminiStreamToAnthropic } from '../streamAdapter.js'
import type { GeminiStreamChunk } from '../types.js'
function mockStream(
chunks: GeminiStreamChunk[],
): AsyncIterable<GeminiStreamChunk> {
return {
[Symbol.asyncIterator]() {
let index = 0
return {
async next() {
if (index >= chunks.length) {
return { done: true, value: undefined }
}
return { done: false, value: chunks[index++] }
},
}
},
}
}
async function collectEvents(chunks: GeminiStreamChunk[]) {
const events: any[] = []
for await (const event of adaptGeminiStreamToAnthropic(
mockStream(chunks),
'gemini-2.5-flash',
)) {
events.push(event)
}
return events
}
describe('adaptGeminiStreamToAnthropic', () => {
test('converts text chunks', async () => {
const events = await collectEvents([
{
candidates: [
{
content: {
parts: [{ text: 'Hello' }],
},
},
],
},
{
candidates: [
{
content: {
parts: [{ text: ' world' }],
},
finishReason: 'STOP',
},
],
},
])
const textDeltas = events.filter(
event =>
event.type === 'content_block_delta' && event.delta.type === 'text_delta',
)
expect(events[0].type).toBe('message_start')
expect(textDeltas).toHaveLength(2)
expect(textDeltas[0].delta.text).toBe('Hello')
expect(textDeltas[1].delta.text).toBe(' world')
const messageDelta = events.find(event => event.type === 'message_delta')
expect(messageDelta.delta.stop_reason).toBe('end_turn')
})
test('converts thinking chunks and signatures', async () => {
const events = await collectEvents([
{
candidates: [
{
content: {
parts: [{ text: 'Think', thought: true }],
},
},
],
},
{
candidates: [
{
content: {
parts: [{ thought: true, thoughtSignature: 'sig-123' }],
},
finishReason: 'STOP',
},
],
},
])
const blockStart = events.find(event => event.type === 'content_block_start')
expect(blockStart.content_block.type).toBe('thinking')
const signatureDelta = events.find(
event =>
event.type === 'content_block_delta' &&
event.delta.type === 'signature_delta',
)
expect(signatureDelta.delta.signature).toBe('sig-123')
})
test('converts function calls to tool_use blocks', async () => {
const events = await collectEvents([
{
candidates: [
{
content: {
parts: [
{
functionCall: {
name: 'bash',
args: { command: 'ls' },
},
thoughtSignature: 'sig-tool',
},
],
},
finishReason: 'STOP',
},
],
},
])
const blockStart = events.find(event => event.type === 'content_block_start')
expect(blockStart.content_block.type).toBe('tool_use')
expect(blockStart.content_block.name).toBe('bash')
const signatureDelta = events.find(
event =>
event.type === 'content_block_delta' &&
event.delta.type === 'signature_delta',
)
expect(signatureDelta.delta.signature).toBe('sig-tool')
const inputDelta = events.find(
event =>
event.type === 'content_block_delta' &&
event.delta.type === 'input_json_delta',
)
expect(inputDelta.delta.partial_json).toBe('{"command":"ls"}')
const messageDelta = events.find(event => event.type === 'message_delta')
expect(messageDelta.delta.stop_reason).toBe('tool_use')
})
test('maps usage metadata into output tokens', async () => {
const events = await collectEvents([
{
candidates: [
{
content: {
parts: [{ text: 'Hello' }],
},
finishReason: 'STOP',
},
],
usageMetadata: {
promptTokenCount: 10,
candidatesTokenCount: 5,
thoughtsTokenCount: 2,
},
},
])
const messageStart = events.find(event => event.type === 'message_start')
expect(messageStart.message.usage.input_tokens).toBe(10)
const messageDelta = events.find(event => event.type === 'message_delta')
expect(messageDelta.usage.output_tokens).toBe(7)
})
})