1- import { z } from "zod"
21import type { APIEvent } from "@solidjs/start/server"
3- import path from "node:path"
42import { and , Database , eq , isNull , lt , or , sql } from "@opencode-ai/console-core/drizzle/index.js"
53import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
64import { BillingTable , UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
75import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
86import { Identifier } from "@opencode-ai/console-core/identifier.js"
9- import { Resource } from "@opencode-ai/console-resource"
10- import { Billing } from "../../../../core/src/billing"
7+ import { Billing } from "@opencode-ai/console-core/billing.js"
118import { Actor } from "@opencode-ai/console-core/actor.js"
129import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js"
1310import { ZenData } from "@opencode-ai/console-core/model.js"
1411import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
1512import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
1613import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
14+ import { logger } from "./logger"
15+ import { AuthError , CreditsError , MonthlyLimitError , UserLimitError , ModelError } from "./error"
16+ import { createBodyConverter , createStreamPartConverter , createResponseConverter } from "./provider/provider"
17+ import { Format } from "./format"
18+ import { anthropicHelper } from "./provider/anthropic"
19+ import { openaiHelper } from "./provider/openai"
20+ import { oaCompatHelper } from "./provider/openai-compatible"
21+
22+ type ZenData = Awaited < ReturnType < typeof ZenData . list > >
23+ type Model = ZenData [ "models" ] [ string ]
1724
1825export async function handler (
1926 input : APIEvent ,
2027 opts : {
21- modifyBody ?: ( body : any ) => any
22- setAuthHeader : ( headers : Headers , apiKey : string ) => void
28+ format : Format
2329 parseApiKey : ( headers : Headers ) => string | undefined
24- onStreamPart : ( chunk : string ) => void
25- getStreamUsage : ( ) => any
26- normalizeUsage : ( body : any ) => {
27- inputTokens : number
28- outputTokens : number
29- reasoningTokens ?: number
30- cacheReadTokens ?: number
31- cacheWrite5mTokens ?: number
32- cacheWrite1hTokens ?: number
33- }
3430 } ,
3531) {
36- class AuthError extends Error { }
37- class CreditsError extends Error { }
38- class MonthlyLimitError extends Error { }
39- class UserLimitError extends Error { }
40- class ModelError extends Error { }
41-
42- type ZenData = Awaited < ReturnType < typeof ZenData . list > >
43- type Model = ZenData [ "models" ] [ string ]
44-
4532 const FREE_WORKSPACES = [
4633 "wrk_01K46JDFR0E75SG2Q8K172KF3Y" , // frank
4734 "wrk_01K6W1A3VE0KMNVSCQT43BG2SX" , // opencode bench
4835 ]
4936
50- const logger = {
51- metric : ( values : Record < string , any > ) => {
52- console . log ( `_metric:${ JSON . stringify ( values ) } ` )
53- } ,
54- log : console . log ,
55- debug : ( message : string ) => {
56- if ( Resource . App . stage === "production" ) return
57- console . debug ( message )
58- } ,
59- }
60-
6137 try {
62- const url = new URL ( input . request . url )
6338 const body = await input . request . json ( )
64- logger . debug ( JSON . stringify ( body ) )
6539 logger . metric ( {
6640 is_tream : ! ! body . stream ,
6741 session : input . request . headers . get ( "x-opencode-session" ) ,
@@ -78,22 +52,28 @@ export async function handler(
7852
7953 // Request to model provider
8054 const startTimestamp = Date . now ( )
81- const res = await fetch ( path . posix . join ( providerInfo . api , url . pathname . replace ( / ^ \/ z e n \/ v 1 / , "" ) + url . search ) , {
55+ const reqUrl = providerInfo . modifyUrl ( providerInfo . api )
56+ const reqBody = JSON . stringify (
57+ providerInfo . modifyBody ( {
58+ ...createBodyConverter ( opts . format , providerInfo . format ) ( body ) ,
59+ model : providerInfo . model ,
60+ } ) ,
61+ )
62+ logger . debug ( "REQUEST URL: " + reqUrl )
63+ logger . debug ( "REQUEST: " + reqBody )
64+ const res = await fetch ( reqUrl , {
8265 method : "POST" ,
8366 headers : ( ( ) => {
8467 const headers = input . request . headers
8568 headers . delete ( "host" )
8669 headers . delete ( "content-length" )
87- opts . setAuthHeader ( headers , providerInfo . apiKey )
70+ providerInfo . modifyHeaders ( headers , body , providerInfo . apiKey )
8871 Object . entries ( providerInfo . headerMappings ?? { } ) . forEach ( ( [ k , v ] ) => {
8972 headers . set ( k , headers . get ( v ) ! )
9073 } )
9174 return headers
9275 } ) ( ) ,
93- body : JSON . stringify ( {
94- ...( opts . modifyBody ?.( body ) ?? body ) ,
95- model : providerInfo . model ,
96- } ) ,
76+ body : reqBody ,
9777 } )
9878
9979 // Scrub response headers
@@ -104,14 +84,19 @@ export async function handler(
10484 resHeaders . set ( k , v )
10585 }
10686 }
87+ logger . debug ( "STATUS: " + res . status + " " + res . statusText )
88+ if ( res . status === 400 || res . status === 503 ) {
89+ logger . debug ( "RESPONSE: " + ( await res . text ( ) ) )
90+ }
10791
10892 // Handle non-streaming response
10993 if ( ! body . stream ) {
94+ const responseConverter = createResponseConverter ( providerInfo . format , opts . format )
11095 const json = await res . json ( )
111- const body = JSON . stringify ( json )
96+ const body = JSON . stringify ( responseConverter ( json ) )
11297 logger . metric ( { response_length : body . length } )
113- logger . debug ( body )
114- await trackUsage ( authInfo , modelInfo , providerInfo . id , json . usage )
98+ logger . debug ( "RESPONSE: " + body )
99+ await trackUsage ( authInfo , modelInfo , providerInfo , json . usage )
115100 await reload ( authInfo )
116101 return new Response ( body , {
117102 status : res . status ,
@@ -121,10 +106,13 @@ export async function handler(
121106 }
122107
123108 // Handle streaming response
109+ const streamConverter = createStreamPartConverter ( providerInfo . format , opts . format )
110+ const usageParser = providerInfo . createUsageParser ( )
124111 const stream = new ReadableStream ( {
125112 start ( c ) {
126113 const reader = res . body ?. getReader ( )
127114 const decoder = new TextDecoder ( )
115+ const encoder = new TextEncoder ( )
128116 let buffer = ""
129117 let responseLength = 0
130118
@@ -136,9 +124,9 @@ export async function handler(
136124 response_length : responseLength ,
137125 "timestamp.last_byte" : Date . now ( ) ,
138126 } )
139- const usage = opts . getStreamUsage ( )
127+ const usage = usageParser . retrieve ( )
140128 if ( usage ) {
141- await trackUsage ( authInfo , modelInfo , providerInfo . id , usage )
129+ await trackUsage ( authInfo , modelInfo , providerInfo , usage )
142130 await reload ( authInfo )
143131 }
144132 c . close ( )
@@ -158,12 +146,21 @@ export async function handler(
158146 const parts = buffer . split ( "\n\n" )
159147 buffer = parts . pop ( ) ?? ""
160148
161- for ( const part of parts ) {
162- logger . debug ( part )
163- opts . onStreamPart ( part . trim ( ) )
149+ for ( let part of parts ) {
150+ logger . debug ( "PART: " + part )
151+
152+ part = part . trim ( )
153+ usageParser . parse ( part )
154+
155+ if ( providerInfo . format !== opts . format ) {
156+ part = streamConverter ( part )
157+ c . enqueue ( encoder . encode ( part + "\n\n" ) )
158+ }
164159 }
165160
166- c . enqueue ( value )
161+ if ( providerInfo . format === opts . format ) {
162+ c . enqueue ( value )
163+ }
167164
168165 return pump ( )
169166 } ) || Promise . resolve ( )
@@ -235,7 +232,11 @@ export async function handler(
235232 throw new ModelError ( `Provider ${ provider . id } not supported` )
236233 }
237234
238- return { ...provider , ...zenData . providers [ provider . id ] }
235+ return {
236+ ...provider ,
237+ ...zenData . providers [ provider . id ] ,
238+ ...( provider . id === "anthropic" ? anthropicHelper : provider . id === "openai" ? openaiHelper : oaCompatHelper ) ,
239+ }
239240 }
240241
241242 async function authenticate (
@@ -356,11 +357,11 @@ export async function handler(
356357 async function trackUsage (
357358 authInfo : Awaited < ReturnType < typeof authenticate > > ,
358359 modelInfo : ReturnType < typeof validateModel > ,
359- providerId : string ,
360+ providerInfo : Awaited < ReturnType < typeof selectProvider > > ,
360361 usage : any ,
361362 ) {
362363 const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
363- opts . normalizeUsage ( usage )
364+ providerInfo . normalizeUsage ( usage )
364365
365366 const modelCost =
366367 modelInfo . cost200K &&
@@ -421,7 +422,7 @@ export async function handler(
421422 workspaceID : authInfo . workspaceID ,
422423 id : Identifier . create ( "usage" ) ,
423424 model : modelInfo . id ,
424- provider : providerId ,
425+ provider : providerInfo . id ,
425426 inputTokens,
426427 outputTokens,
427428 reasoningTokens,
0 commit comments