mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
148 lines
4.4 KiB
TypeScript
148 lines
4.4 KiB
TypeScript
import type { ApiStream, ModelInfo, Message, TextBlock } from "../../types.d.ts";
|
|
|
|
interface OpenRouterOptions {
|
|
model: string;
|
|
apiKey: string;
|
|
}
|
|
|
|
export class OpenRouterHandler {
|
|
private apiKey: string;
|
|
private model: string;
|
|
|
|
constructor(options: OpenRouterOptions) {
|
|
this.apiKey = options.apiKey;
|
|
this.model = options.model;
|
|
}
|
|
|
|
async *createMessage(systemPrompt: string, messages: Message[]): ApiStream {
|
|
try {
|
|
// Convert our messages to OpenRouter format
|
|
const openRouterMessages = [
|
|
{ role: "system", content: systemPrompt },
|
|
...messages.map(msg => ({
|
|
role: msg.role,
|
|
content: Array.isArray(msg.content)
|
|
? msg.content.map(c => c.text).join("\n")
|
|
: msg.content
|
|
}))
|
|
];
|
|
|
|
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${this.apiKey}`,
|
|
"Content-Type": "application/json",
|
|
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline",
|
|
"X-Title": "Roo Cline"
|
|
},
|
|
body: JSON.stringify({
|
|
model: this.model,
|
|
messages: openRouterMessages,
|
|
stream: true,
|
|
temperature: 0.7,
|
|
max_tokens: 4096
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
throw new Error(`OpenRouter API error: ${response.statusText}${errorData ? ` - ${JSON.stringify(errorData)}` : ""}`);
|
|
}
|
|
|
|
if (!response.body) {
|
|
throw new Error("No response body received");
|
|
}
|
|
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = "";
|
|
let content = "";
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
|
|
// Add new chunk to buffer and split into lines
|
|
buffer += decoder.decode(value, { stream: true });
|
|
const lines = buffer.split("\n");
|
|
|
|
// Process all complete lines
|
|
buffer = lines.pop() || ""; // Keep the last incomplete line in buffer
|
|
|
|
for (const line of lines) {
|
|
if (line.trim() === "") continue;
|
|
if (line === "data: [DONE]") continue;
|
|
|
|
if (line.startsWith("data: ")) {
|
|
try {
|
|
const data = JSON.parse(line.slice(6));
|
|
if (data.choices?.[0]?.delta?.content) {
|
|
const text = data.choices[0].delta.content;
|
|
content += text;
|
|
yield { type: "text", text };
|
|
}
|
|
} catch (e) {
|
|
// Ignore parse errors for incomplete chunks
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process any remaining content in buffer
|
|
if (buffer.trim() && buffer.startsWith("data: ")) {
|
|
try {
|
|
const data = JSON.parse(buffer.slice(6));
|
|
if (data.choices?.[0]?.delta?.content) {
|
|
const text = data.choices[0].delta.content;
|
|
content += text;
|
|
yield { type: "text", text };
|
|
}
|
|
} catch (e) {
|
|
// Ignore parse errors for final incomplete chunk
|
|
}
|
|
}
|
|
|
|
// Estimate token usage (4 chars per token is a rough estimate)
|
|
const inputText = systemPrompt + messages.reduce((acc, msg) =>
|
|
acc + (typeof msg.content === "string" ?
|
|
msg.content :
|
|
msg.content.reduce((a, b) => a + b.text, "")), "");
|
|
|
|
const inputTokens = Math.ceil(inputText.length / 4);
|
|
const outputTokens = Math.ceil(content.length / 4);
|
|
|
|
yield {
|
|
type: "usage",
|
|
inputTokens,
|
|
outputTokens,
|
|
totalCost: this.calculateCost(inputTokens, outputTokens)
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error("Error in OpenRouter API call:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
getModel(): { id: string; info: ModelInfo } {
|
|
return {
|
|
id: this.model,
|
|
info: {
|
|
contextWindow: 128000, // This varies by model
|
|
supportsComputerUse: true,
|
|
inputPricePerToken: 0.000002, // Approximate, varies by model
|
|
outputPricePerToken: 0.000002
|
|
}
|
|
};
|
|
}
|
|
|
|
private calculateCost(inputTokens: number, outputTokens: number): number {
|
|
const { inputPricePerToken, outputPricePerToken } = this.getModel().info;
|
|
return (
|
|
(inputTokens * (inputPricePerToken || 0)) +
|
|
(outputTokens * (outputPricePerToken || 0))
|
|
);
|
|
}
|
|
}
|