|
@@ -15,6 +15,7 @@ import { logger } from "./logger"
|
|
|
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
|
|
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
|
|
|
import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
|
|
import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
|
|
|
import { anthropicHelper } from "./provider/anthropic"
|
|
import { anthropicHelper } from "./provider/anthropic"
|
|
|
|
|
+import { googleHelper } from "./provider/google"
|
|
|
import { openaiHelper } from "./provider/openai"
|
|
import { openaiHelper } from "./provider/openai"
|
|
|
import { oaCompatHelper } from "./provider/openai-compatible"
|
|
import { oaCompatHelper } from "./provider/openai-compatible"
|
|
|
import { createRateLimiter } from "./rateLimiter"
|
|
import { createRateLimiter } from "./rateLimiter"
|
|
@@ -30,6 +31,8 @@ export async function handler(
|
|
|
opts: {
|
|
opts: {
|
|
|
format: ZenData.Format
|
|
format: ZenData.Format
|
|
|
parseApiKey: (headers: Headers) => string | undefined
|
|
parseApiKey: (headers: Headers) => string | undefined
|
|
|
|
|
+ parseModel: (url: string, body: any) => string
|
|
|
|
|
+ parseIsStream: (url: string, body: any) => boolean
|
|
|
},
|
|
},
|
|
|
) {
|
|
) {
|
|
|
type AuthInfo = Awaited<ReturnType<typeof authenticate>>
|
|
type AuthInfo = Awaited<ReturnType<typeof authenticate>>
|
|
@@ -43,15 +46,18 @@ export async function handler(
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
|
|
+ const url = input.request.url
|
|
|
const body = await input.request.json()
|
|
const body = await input.request.json()
|
|
|
const ip = input.request.headers.get("x-real-ip") ?? ""
|
|
const ip = input.request.headers.get("x-real-ip") ?? ""
|
|
|
|
|
+ const model = opts.parseModel(url, body)
|
|
|
|
|
+ const isStream = opts.parseIsStream(url, body)
|
|
|
logger.metric({
|
|
logger.metric({
|
|
|
- is_tream: !!body.stream,
|
|
|
|
|
|
|
+ is_tream: isStream,
|
|
|
session: input.request.headers.get("x-opencode-session"),
|
|
session: input.request.headers.get("x-opencode-session"),
|
|
|
request: input.request.headers.get("x-opencode-request"),
|
|
request: input.request.headers.get("x-opencode-request"),
|
|
|
})
|
|
})
|
|
|
const zenData = ZenData.list()
|
|
const zenData = ZenData.list()
|
|
|
- const modelInfo = validateModel(zenData, body.model)
|
|
|
|
|
|
|
+ const modelInfo = validateModel(zenData, model)
|
|
|
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
|
const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
|
|
|
await rateLimiter?.check()
|
|
await rateLimiter?.check()
|
|
|
|
|
|
|
@@ -64,7 +70,7 @@ export async function handler(
|
|
|
logger.metric({ provider: providerInfo.id })
|
|
logger.metric({ provider: providerInfo.id })
|
|
|
|
|
|
|
|
const startTimestamp = Date.now()
|
|
const startTimestamp = Date.now()
|
|
|
- const reqUrl = providerInfo.modifyUrl(providerInfo.api)
|
|
|
|
|
|
|
+ const reqUrl = providerInfo.modifyUrl(providerInfo.api, providerInfo.model, isStream)
|
|
|
const reqBody = JSON.stringify(
|
|
const reqBody = JSON.stringify(
|
|
|
providerInfo.modifyBody({
|
|
providerInfo.modifyBody({
|
|
|
...createBodyConverter(opts.format, providerInfo.format)(body),
|
|
...createBodyConverter(opts.format, providerInfo.format)(body),
|
|
@@ -114,7 +120,7 @@ export async function handler(
|
|
|
logger.debug("STATUS: " + res.status + " " + res.statusText)
|
|
logger.debug("STATUS: " + res.status + " " + res.statusText)
|
|
|
|
|
|
|
|
// Handle non-streaming response
|
|
// Handle non-streaming response
|
|
|
- if (!body.stream) {
|
|
|
|
|
|
|
+ if (!isStream) {
|
|
|
const responseConverter = createResponseConverter(providerInfo.format, opts.format)
|
|
const responseConverter = createResponseConverter(providerInfo.format, opts.format)
|
|
|
const json = await res.json()
|
|
const json = await res.json()
|
|
|
const body = JSON.stringify(responseConverter(json))
|
|
const body = JSON.stringify(responseConverter(json))
|
|
@@ -169,7 +175,7 @@ export async function handler(
|
|
|
responseLength += value.length
|
|
responseLength += value.length
|
|
|
buffer += decoder.decode(value, { stream: true })
|
|
buffer += decoder.decode(value, { stream: true })
|
|
|
|
|
|
|
|
- const parts = buffer.split("\n\n")
|
|
|
|
|
|
|
+ const parts = buffer.split(providerInfo.streamSeparator)
|
|
|
buffer = parts.pop() ?? ""
|
|
buffer = parts.pop() ?? ""
|
|
|
|
|
|
|
|
for (let part of parts) {
|
|
for (let part of parts) {
|
|
@@ -283,6 +289,7 @@ export async function handler(
|
|
|
...(() => {
|
|
...(() => {
|
|
|
const format = zenData.providers[provider.id].format
|
|
const format = zenData.providers[provider.id].format
|
|
|
if (format === "anthropic") return anthropicHelper
|
|
if (format === "anthropic") return anthropicHelper
|
|
|
|
|
+ if (format === "google") return googleHelper
|
|
|
if (format === "openai") return openaiHelper
|
|
if (format === "openai") return openaiHelper
|
|
|
return oaCompatHelper
|
|
return oaCompatHelper
|
|
|
})(),
|
|
})(),
|