Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/api/index.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,8 @@ import OpenAI from "openai"

import { isRetiredProvider, type ProviderSettings, type ModelInfo } from "@roo-code/types"

import type { RooMessage } from "../core/task-persistence/rooMessage"

import { ApiStream } from "./transform/stream"

import {
Expand DownExpand Up@@ -89,11 +91,7 @@ export interface ApiHandlerCreateMessageMetadata {
}

export interface ApiHandler {
createMessage(
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream
createMessage(systemPrompt: string, messages: RooMessage[], metadata?: ApiHandlerCreateMessageMetadata): ApiStream

getModel(): { id: string; info: ModelInfo }

Expand Down
108 changes: 10 additions & 98 deletions src/api/providers/__tests__/anthropic-vertex.spec.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import type { RooMessage } from "../../../core/task-persistence/rooMessage"
// npx vitest run src/api/providers/__tests__/anthropic-vertex.spec.ts

import { AnthropicVertexHandler } from "../anthropic-vertex"
Expand DownExpand Up@@ -54,6 +55,7 @@ vitest.mock("../../transform/ai-sdk", () => ({
}),
mapToolChoice: vitest.fn().mockReturnValue(undefined),
handleAiSdkError: vitest.fn().mockImplementation((error: any) => error),
yieldResponseMessage: vitest.fn().mockImplementation(function* () {}),
}))

// Import mocked modules
Expand DownExpand Up@@ -184,7 +186,7 @@ describe("AnthropicVertexHandler", () => {
})

describe("createMessage", () => {
const mockMessages: Anthropic.Messages.MessageParam[] = [
const mockMessages: RooMessage[] = [
{
role: "user",
content: "Hello",
Expand DownExpand Up@@ -244,15 +246,20 @@ describe("AnthropicVertexHandler", () => {
)
})

it("should call convertToAiSdkMessages with the messages", async () => {
it("should pass messages directly to streamText as ModelMessage[]", async () => {
mockStreamText.mockReturnValue(createMockStreamResult([]))

const stream = handler.createMessage(systemPrompt, mockMessages)
for await (const _chunk of stream) {
// consume
}

expect(convertToAiSdkMessages).toHaveBeenCalledWith(mockMessages)
// Messages are now already in ModelMessage format, passed directly to streamText
expect(mockStreamText).toHaveBeenCalledWith(
expect.objectContaining({
messages: mockMessages,
}),
)
})

it("should pass tools through AI SDK conversion pipeline", async () => {
Expand DownExpand Up@@ -363,55 +370,6 @@ describe("AnthropicVertexHandler", () => {
expect(textChunks[0].text).toBe("Here's my answer:")
})

it("should capture thought signature from stream events", async () => {
const streamParts = [
{
type: "reasoning-delta",
text: "thinking...",
providerMetadata: {
anthropic: { signature: "test-signature-abc123" },
},
},
{ type: "text-delta", text: "answer" },
]

mockStreamText.mockReturnValue(createMockStreamResult(streamParts))

const stream = handler.createMessage(systemPrompt, mockMessages)
for await (const _chunk of stream) {
// consume
}

expect(handler.getThoughtSignature()).toBe("test-signature-abc123")
})

it("should capture redacted thinking blocks from stream events", async () => {
const streamParts = [
{
type: "reasoning-delta",
text: "",
providerMetadata: {
anthropic: { redactedData: "encrypted-redacted-data" },
},
},
{ type: "text-delta", text: "answer" },
]

mockStreamText.mockReturnValue(createMockStreamResult(streamParts))

const stream = handler.createMessage(systemPrompt, mockMessages)
for await (const _chunk of stream) {
// consume
}

const redactedBlocks = handler.getRedactedThinkingBlocks()
expect(redactedBlocks).toHaveLength(1)
expect(redactedBlocks![0]).toEqual({
type: "redacted_thinking",
data: "encrypted-redacted-data",
})
})

it("should configure thinking providerOptions for thinking models", async () => {
const thinkingHandler = new AnthropicVertexHandler({
apiModelId: "claude-3-7-sonnet@20250219:thinking",
Expand DownExpand Up@@ -674,50 +632,4 @@ describe("AnthropicVertexHandler", () => {
expect(handler.isAiSdkProvider()).toBe(true)
})
})

describe("thought signature and redacted thinking", () => {
beforeEach(() => {
handler = new AnthropicVertexHandler({
apiModelId: "claude-3-5-sonnet-v2@20241022",
vertexProjectId: "test-project",
vertexRegion: "us-central1",
})
})

it("should return undefined for thought signature before any request", () => {
expect(handler.getThoughtSignature()).toBeUndefined()
})

it("should return undefined for redacted thinking blocks before any request", () => {
expect(handler.getRedactedThinkingBlocks()).toBeUndefined()
})

it("should reset thought signature on each createMessage call", async () => {
// First call with signature
mockStreamText.mockReturnValue(
createMockStreamResult([
{
type: "reasoning-delta",
text: "thinking",
providerMetadata: { anthropic: { signature: "sig-1" } },
},
]),
)

const stream1 = handler.createMessage("test", [{ role: "user", content: "Hello" }])
for await (const _chunk of stream1) {
// consume
}
expect(handler.getThoughtSignature()).toBe("sig-1")

// Second call without signature
mockStreamText.mockReturnValue(createMockStreamResult([{ type: "text-delta", text: "just text" }]))

const stream2 = handler.createMessage("test", [{ role: "user", content: "Hello again" }])
for await (const _chunk of stream2) {
// consume
}
expect(handler.getThoughtSignature()).toBeUndefined()
})
})
})
90 changes: 1 addition & 89 deletions src/api/providers/__tests__/anthropic.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,7 @@ vitest.mock("../../transform/ai-sdk", () => ({
}),
mapToolChoice: vitest.fn().mockReturnValue(undefined),
handleAiSdkError: vitest.fn().mockImplementation((error: any) => error),
yieldResponseMessage: vitest.fn().mockImplementation(function* () {}),
}))

// Import mocked modules
Expand DownExpand Up@@ -398,85 +399,6 @@ describe("AnthropicHandler", () => {
expect(endChunk).toBeDefined()
})

it("should capture thinking signature from stream events", async () => {
const testSignature = "test-thinking-signature"
setupStreamTextMock([
{
type: "reasoning-delta",
text: "thinking...",
providerMetadata: { anthropic: { signature: testSignature } },
},
{ type: "text-delta", text: "Answer" },
])

const stream = handler.createMessage(systemPrompt, [
{ role: "user", content: [{ type: "text" as const, text: "test" }] },
])

for await (const _chunk of stream) {
// Consume stream
}

expect(handler.getThoughtSignature()).toBe(testSignature)
})

it("should capture redacted thinking blocks from stream events", async () => {
setupStreamTextMock([
{
type: "reasoning-delta",
text: "",
providerMetadata: { anthropic: { redactedData: "redacted-data-base64" } },
},
{ type: "text-delta", text: "Answer" },
])

const stream = handler.createMessage(systemPrompt, [
{ role: "user", content: [{ type: "text" as const, text: "test" }] },
])

for await (const _chunk of stream) {
// Consume stream
}

const redactedBlocks = handler.getRedactedThinkingBlocks()
expect(redactedBlocks).toBeDefined()
expect(redactedBlocks).toHaveLength(1)
expect(redactedBlocks![0]).toEqual({
type: "redacted_thinking",
data: "redacted-data-base64",
})
})

it("should reset thinking state between requests", async () => {
// First request with signature
setupStreamTextMock([
{
type: "reasoning-delta",
text: "thinking...",
providerMetadata: { anthropic: { signature: "sig-1" } },
},
])

const stream1 = handler.createMessage(systemPrompt, [
{ role: "user", content: [{ type: "text" as const, text: "test 1" }] },
])
for await (const _chunk of stream1) {
// Consume
}
expect(handler.getThoughtSignature()).toBe("sig-1")

// Second request without signature
setupStreamTextMock([{ type: "text-delta", text: "plain answer" }])

const stream2 = handler.createMessage(systemPrompt, [
{ role: "user", content: [{ type: "text" as const, text: "test 2" }] },
])
for await (const _chunk of stream2) {
// Consume
}
expect(handler.getThoughtSignature()).toBeUndefined()
})

it("should pass system prompt via system param with systemProviderOptions for cache control", async () => {
setupStreamTextMock([{ type: "text-delta", text: "test" }])

Expand DownExpand Up@@ -610,14 +532,4 @@ describe("AnthropicHandler", () => {
expect(handler.isAiSdkProvider()).toBe(true)
})
})

describe("thinking signature", () => {
it("should return undefined when no signature captured", () => {
expect(handler.getThoughtSignature()).toBeUndefined()
})

it("should return undefined for redacted blocks when none captured", () => {
expect(handler.getRedactedThinkingBlocks()).toBeUndefined()
})
})
})
5 changes: 3 additions & 2 deletions src/api/providers/__tests__/azure.spec.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import type { RooMessage } from "../../../core/task-persistence/rooMessage"
// Use vi.hoisted to define mock functions that can be referenced in hoisted vi.mock() calls
const { mockStreamText, mockGenerateText, mockCreateAzure } = vi.hoisted(() => ({
mockStreamText: vi.fn(),
Expand DownExpand Up@@ -132,7 +133,7 @@ describe("AzureHandler", () => {

describe("createMessage", () => {
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [
const messages: RooMessage[] = [
{
role: "user",
content: [
Expand DownExpand Up@@ -376,7 +377,7 @@ describe("AzureHandler", () => {

describe("tools", () => {
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [
const messages: RooMessage[] = [
{
role: "user",
content: [{ type: "text" as const, text: "Use a tool" }],
Expand Down
3 changes: 2 additions & 1 deletion src/api/providers/__tests__/base-provider.spec.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import type { RooMessage } from "../../../core/task-persistence/rooMessage"
import { Anthropic } from "@anthropic-ai/sdk"

import type { ModelInfo } from "@roo-code/types"
Expand All@@ -7,7 +8,7 @@ import type { ApiStream } from "../../transform/stream"

// Create a concrete implementation for testing
class TestProvider extends BaseProvider {
createMessage(_systemPrompt: string, _messages: Anthropic.Messages.MessageParam[]): ApiStream {
createMessage(_systemPrompt: string, _messages: RooMessage[]): ApiStream {
throw new Error("Not implemented")
}

Expand Down
7 changes: 4 additions & 3 deletions src/api/providers/__tests__/baseten.spec.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import type { RooMessage } from "../../../core/task-persistence/rooMessage"
// npx vitest run src/api/providers/__tests__/baseten.spec.ts

// Use vi.hoisted to define mock functions that can be referenced in hoisted vi.mock() calls
Expand DownExpand Up@@ -101,7 +102,7 @@ describe("BasetenHandler", () => {

describe("createMessage", () => {
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [
const messages: RooMessage[] = [
{
role: "user",
content: [
Expand DownExpand Up@@ -281,7 +282,7 @@ describe("BasetenHandler", () => {

describe("tool handling", () => {
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [
const messages: RooMessage[] = [
{
role: "user",
content: [{ type: "text" as const, text: "Hello!" }],
Expand DownExpand Up@@ -389,7 +390,7 @@ describe("BasetenHandler", () => {

describe("error handling", () => {
const systemPrompt = "You are a helpful assistant."
const messages: Anthropic.Messages.MessageParam[] = [
const messages: RooMessage[] = [
{
role: "user",
content: [{ type: "text" as const, text: "Hello!" }],
Expand Down
Loading
Loading