Skip to content
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 number Diff line number Diff 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand Up @@ -184,7 +186,7 @@ describe("AnthropicVertexHandler", () => {
})

describe("createMessage", () => {
const mockMessages: Anthropic.Messages.MessageParam[] = [
const mockMessages: RooMessage[] = [
{
role: "user",
content: "Hello",
Expand Down Expand 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand 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