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
185 changes: 79 additions & 106 deletions src/api/providers/__tests__/requesty.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { RequestyHandler } from "../requesty"
import { ApiHandlerOptions } from "../../../shared/api"
import { Package } from "../../../shared/package"
import { ApiHandlerCreateMessageMetadata } from "../../index"
import { asyncStreamFrom, collectStream } from "../../../test-utils/stream"

const mockCreate = vitest.fn()

Expand Down Expand Up @@ -182,38 +183,31 @@ describe("RequestyHandler", () => {
it("generates correct stream chunks", async () => {
const handler = new RequestyHandler(mockOptions)

const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
id: mockOptions.requestyModelId,
choices: [{ delta: { content: "test response" } }],
}
yield {
id: "test-id",
choices: [{ delta: {} }],
usage: {
prompt_tokens: 10,
completion_tokens: 20,
prompt_tokens_details: {
caching_tokens: 5,
cached_tokens: 2,
},
const mockStream = asyncStreamFrom([
{
id: mockOptions.requestyModelId,
choices: [{ delta: { content: "test response" } }],
},
{
id: "test-id",
choices: [{ delta: {} }],
usage: {
prompt_tokens: 10,
completion_tokens: 20,
prompt_tokens_details: {
caching_tokens: 5,
cached_tokens: 2,
},
}
},
},
}
])

mockCreate.mockResolvedValue(mockStream)

const systemPrompt = "test system prompt"
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user" as const, content: "test message" }]

const generator = handler.createMessage(systemPrompt, messages)
const chunks = []

for await (const chunk of generator) {
chunks.push(chunk)
}
const chunks = await collectStream(handler.createMessage(systemPrompt, messages))

// Verify stream chunks
expect(chunks).toHaveLength(2) // One text chunk and one usage chunk
Expand Down Expand Up @@ -257,15 +251,13 @@ describe("RequestyHandler", () => {
modelMaxTokens: 32768,
})

const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
id: "test-id",
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20 },
}
const mockStream = asyncStreamFrom([
{
id: "test-id",
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20 },
},
}
])

mockCreate.mockResolvedValue(mockStream)

Expand All @@ -290,15 +282,13 @@ describe("RequestyHandler", () => {
modelMaxTokens: 32768,
})

const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
id: "test-id",
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20 },
}
const mockStream = asyncStreamFrom([
{
id: "test-id",
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20 },
},
}
])

mockCreate.mockResolvedValue(mockStream)

Expand All @@ -323,15 +313,13 @@ describe("RequestyHandler", () => {
modelMaxTokens: 32768,
})

const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
id: "test-id",
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20 },
}
const mockStream = asyncStreamFrom([
{
id: "test-id",
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20 },
},
}
])

mockCreate.mockResolvedValue(mockStream)

Expand Down Expand Up @@ -359,53 +347,47 @@ describe("RequestyHandler", () => {

it("streams reasoning chunks from delta.reasoning_content", async () => {
const handler = new RequestyHandler(mockOptions)
mockCreate.mockResolvedValue({
async *[Symbol.asyncIterator]() {
yield { id: "1", choices: [{ delta: { reasoning_content: "thinking..." } }] }
yield { id: "1", choices: [{ delta: { content: "answer" } }] }
yield {
mockCreate.mockResolvedValue(
asyncStreamFrom([
{ id: "1", choices: [{ delta: { reasoning_content: "thinking..." } }] },
{ id: "1", choices: [{ delta: { content: "answer" } }] },
{
id: "1",
choices: [{ delta: {} }],
usage: { prompt_tokens: 1, completion_tokens: 1 },
}
},
})
},
]),
)

const chunks: any[] = []
for await (const chunk of handler.createMessage("sys", [{ role: "user", content: "hi" }])) {
chunks.push(chunk)
}
const chunks = await collectStream(handler.createMessage("sys", [{ role: "user", content: "hi" }]))

expect(chunks).toContainEqual({ type: "reasoning", text: "thinking..." })
})

it("falls back to delta.reasoning when reasoning_content is absent", async () => {
const handler = new RequestyHandler(mockOptions)
mockCreate.mockResolvedValue({
async *[Symbol.asyncIterator]() {
yield { id: "1", choices: [{ delta: { reasoning: "router-style thought" } }] }
yield {
mockCreate.mockResolvedValue(
asyncStreamFrom([
{ id: "1", choices: [{ delta: { reasoning: "router-style thought" } }] },
{
id: "1",
choices: [{ delta: {} }],
usage: { prompt_tokens: 1, completion_tokens: 1 },
}
},
})
},
]),
)

const chunks: any[] = []
for await (const chunk of handler.createMessage("sys", [{ role: "user", content: "hi" }])) {
chunks.push(chunk)
}
const chunks = await collectStream(handler.createMessage("sys", [{ role: "user", content: "hi" }]))

expect(chunks).toContainEqual({ type: "reasoning", text: "router-style thought" })
})

it("prefers delta.reasoning_content over delta.reasoning when both are present", async () => {
const handler = new RequestyHandler(mockOptions)

mockCreate.mockResolvedValue({
async *[Symbol.asyncIterator]() {
yield {
mockCreate.mockResolvedValue(
asyncStreamFrom([
{
id: "1",
choices: [
{
Expand All @@ -415,20 +397,16 @@ describe("RequestyHandler", () => {
},
},
],
}
yield {
},
{
id: "1",
choices: [{ delta: {} }],
usage: { prompt_tokens: 1, completion_tokens: 1 },
}
},
})

const chunks: any[] = []
},
]),
)

for await (const chunk of handler.createMessage("sys", [{ role: "user", content: "hi" }])) {
chunks.push(chunk)
}
const chunks = await collectStream(handler.createMessage("sys", [{ role: "user", content: "hi" }]))

const reasoningChunks = chunks.filter((chunk) => chunk.type === "reasoning")

Expand Down Expand Up @@ -459,15 +437,14 @@ describe("RequestyHandler", () => {
]

beforeEach(() => {
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
mockCreate.mockResolvedValue(
asyncStreamFrom([
{
id: "test-id",
choices: [{ delta: { content: "test response" } }],
}
},
}
mockCreate.mockResolvedValue(mockStream)
},
]),
)
})

it("should include tools in request when tools are provided", async () => {
Expand Down Expand Up @@ -498,9 +475,9 @@ describe("RequestyHandler", () => {
})

it("should handle tool_call_partial chunks in streaming response", async () => {
const mockStreamWithToolCalls = {
async *[Symbol.asyncIterator]() {
yield {
mockCreate.mockResolvedValue(
asyncStreamFrom([
{
id: "test-id",
choices: [
{
Expand All @@ -518,8 +495,8 @@ describe("RequestyHandler", () => {
},
},
],
}
yield {
},
{
id: "test-id",
choices: [
{
Expand All @@ -535,26 +512,22 @@ describe("RequestyHandler", () => {
},
},
],
}
yield {
},
{
id: "test-id",
choices: [{ delta: {} }],
usage: { prompt_tokens: 10, completion_tokens: 20 },
}
},
}
mockCreate.mockResolvedValue(mockStreamWithToolCalls)
},
]),
)

const metadata: ApiHandlerCreateMessageMetadata = {
taskId: "test-task",
tools: mockTools,
}

const handler = new RequestyHandler(mockOptions)
const chunks = []
for await (const chunk of handler.createMessage(systemPrompt, messages, metadata)) {
chunks.push(chunk)
}
const chunks = await collectStream(handler.createMessage(systemPrompt, messages, metadata))

// Expect two tool_call_partial chunks and one usage chunk
expect(chunks).toHaveLength(3)
Expand Down
5 changes: 0 additions & 5 deletions src/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,6 @@
"count": 5
}
},
"api/providers/__tests__/requesty.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 3
}
},
"api/providers/__tests__/sambanova.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 2
Expand Down
20 changes: 20 additions & 0 deletions src/test-utils/__tests__/stream.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { asyncStreamFrom, collectStream } from "../stream"

describe("stream test utils", () => {
it("collects chunks in order", async () => {
await expect(collectStream(asyncStreamFrom([1, 2, 3]))).resolves.toEqual([1, 2, 3])
})

it("collects empty streams", async () => {
await expect(collectStream(asyncStreamFrom([]))).resolves.toEqual([])
})

it("propagates stream errors", async () => {
async function* failingStream() {
yield 1
throw new Error("boom")
}

await expect(collectStream(failingStream())).rejects.toThrow("boom")
})
})
Loading