mirror of
https://github.com/aljazceru/opencode.git
synced 2026-01-06 09:24:55 +01:00
zen: allow byok requests w/o a balance
This commit is contained in:
@@ -13,7 +13,11 @@ import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
|
|||||||
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
|
||||||
import { logger } from "./logger"
|
import { logger } from "./logger"
|
||||||
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError } from "./error"
|
import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError } from "./error"
|
||||||
import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
|
import {
|
||||||
|
createBodyConverter,
|
||||||
|
createStreamPartConverter,
|
||||||
|
createResponseConverter,
|
||||||
|
} from "./provider/provider"
|
||||||
import { Format } from "./format"
|
import { Format } from "./format"
|
||||||
import { anthropicHelper } from "./provider/anthropic"
|
import { anthropicHelper } from "./provider/anthropic"
|
||||||
import { openaiHelper } from "./provider/openai"
|
import { openaiHelper } from "./provider/openai"
|
||||||
@@ -43,7 +47,11 @@ export async function handler(
|
|||||||
})
|
})
|
||||||
const zenData = ZenData.list()
|
const zenData = ZenData.list()
|
||||||
const modelInfo = validateModel(zenData, body.model)
|
const modelInfo = validateModel(zenData, body.model)
|
||||||
const providerInfo = selectProvider(zenData, modelInfo, input.request.headers.get("x-real-ip") ?? "")
|
const providerInfo = selectProvider(
|
||||||
|
zenData,
|
||||||
|
modelInfo,
|
||||||
|
input.request.headers.get("x-real-ip") ?? "",
|
||||||
|
)
|
||||||
const authInfo = await authenticate(modelInfo, providerInfo)
|
const authInfo = await authenticate(modelInfo, providerInfo)
|
||||||
validateBilling(modelInfo, authInfo)
|
validateBilling(modelInfo, authInfo)
|
||||||
validateModelSettings(authInfo)
|
validateModelSettings(authInfo)
|
||||||
@@ -222,7 +230,11 @@ export async function handler(
|
|||||||
return { id: modelId, ...modelData }
|
return { id: modelId, ...modelData }
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectProvider(zenData: ZenData, model: Awaited<ReturnType<typeof validateModel>>, ip: string) {
|
function selectProvider(
|
||||||
|
zenData: ZenData,
|
||||||
|
model: Awaited<ReturnType<typeof validateModel>>,
|
||||||
|
ip: string,
|
||||||
|
) {
|
||||||
const providers = model.providers
|
const providers = model.providers
|
||||||
.filter((provider) => !provider.disabled)
|
.filter((provider) => !provider.disabled)
|
||||||
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
.flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
|
||||||
@@ -239,7 +251,11 @@ export async function handler(
|
|||||||
return {
|
return {
|
||||||
...provider,
|
...provider,
|
||||||
...zenData.providers[provider.id],
|
...zenData.providers[provider.id],
|
||||||
...(provider.id === "anthropic" ? anthropicHelper : provider.id === "openai" ? openaiHelper : oaCompatHelper),
|
...(provider.id === "anthropic"
|
||||||
|
? anthropicHelper
|
||||||
|
: provider.id === "openai"
|
||||||
|
? openaiHelper
|
||||||
|
: oaCompatHelper),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,11 +295,20 @@ export async function handler(
|
|||||||
.from(KeyTable)
|
.from(KeyTable)
|
||||||
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))
|
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))
|
||||||
.innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID))
|
.innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID))
|
||||||
.innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)))
|
.innerJoin(
|
||||||
.leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, model.id)))
|
UserTable,
|
||||||
|
and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)),
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
ModelTable,
|
||||||
|
and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, model.id)),
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
ProviderTable,
|
ProviderTable,
|
||||||
and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, providerInfo.id)),
|
and(
|
||||||
|
eq(ProviderTable.workspaceID, KeyTable.workspaceID),
|
||||||
|
eq(ProviderTable.provider, providerInfo.id),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
|
.where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
|
||||||
.then((rows) => rows[0]),
|
.then((rows) => rows[0]),
|
||||||
@@ -307,12 +332,20 @@ export async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateBilling(model: Model, authInfo: Awaited<ReturnType<typeof authenticate>>) {
|
function validateBilling(model: Model, authInfo: Awaited<ReturnType<typeof authenticate>>) {
|
||||||
if (!authInfo || authInfo.isFree) return
|
if (!authInfo) return
|
||||||
|
if (authInfo.provider?.credentials) return
|
||||||
|
if (authInfo.isFree) return
|
||||||
if (model.allowAnonymous) return
|
if (model.allowAnonymous) return
|
||||||
|
|
||||||
const billing = authInfo.billing
|
const billing = authInfo.billing
|
||||||
if (!billing.paymentMethodID) throw new CreditsError("No payment method")
|
if (!billing.paymentMethodID)
|
||||||
if (billing.balance <= 0) throw new CreditsError("Insufficient balance")
|
throw new CreditsError(
|
||||||
|
`No payment method. Add a payment method here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
|
||||||
|
)
|
||||||
|
if (billing.balance <= 0)
|
||||||
|
throw new CreditsError(
|
||||||
|
`Insufficient balance. Manage your billing here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
|
||||||
|
)
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const currentYear = now.getUTCFullYear()
|
const currentYear = now.getUTCFullYear()
|
||||||
@@ -327,7 +360,7 @@ export async function handler(
|
|||||||
const dateMonth = billing.timeMonthlyUsageUpdated.getUTCMonth()
|
const dateMonth = billing.timeMonthlyUsageUpdated.getUTCMonth()
|
||||||
if (currentYear === dateYear && currentMonth === dateMonth)
|
if (currentYear === dateYear && currentMonth === dateMonth)
|
||||||
throw new MonthlyLimitError(
|
throw new MonthlyLimitError(
|
||||||
`Your workspace has reached its monthly spending limit of $${billing.monthlyLimit}.`,
|
`Your workspace has reached its monthly spending limit of $${billing.monthlyLimit}. Manage your limits here: https://opencode.ai/workspace/${authInfo.workspaceID}/billing`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +373,9 @@ export async function handler(
|
|||||||
const dateYear = authInfo.user.timeMonthlyUsageUpdated.getUTCFullYear()
|
const dateYear = authInfo.user.timeMonthlyUsageUpdated.getUTCFullYear()
|
||||||
const dateMonth = authInfo.user.timeMonthlyUsageUpdated.getUTCMonth()
|
const dateMonth = authInfo.user.timeMonthlyUsageUpdated.getUTCMonth()
|
||||||
if (currentYear === dateYear && currentMonth === dateMonth)
|
if (currentYear === dateYear && currentMonth === dateMonth)
|
||||||
throw new UserLimitError(`You have reached your monthly spending limit of $${authInfo.user.monthlyLimit}.`)
|
throw new UserLimitError(
|
||||||
|
`You have reached your monthly spending limit of $${authInfo.user.monthlyLimit}. Manage your limits here: https://opencode.ai/workspace/${authInfo.workspaceID}/members`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,12 +399,19 @@ export async function handler(
|
|||||||
providerInfo: Awaited<ReturnType<typeof selectProvider>>,
|
providerInfo: Awaited<ReturnType<typeof selectProvider>>,
|
||||||
usage: any,
|
usage: any,
|
||||||
) {
|
) {
|
||||||
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
|
const {
|
||||||
providerInfo.normalizeUsage(usage)
|
inputTokens,
|
||||||
|
outputTokens,
|
||||||
|
reasoningTokens,
|
||||||
|
cacheReadTokens,
|
||||||
|
cacheWrite5mTokens,
|
||||||
|
cacheWrite1hTokens,
|
||||||
|
} = providerInfo.normalizeUsage(usage)
|
||||||
|
|
||||||
const modelCost =
|
const modelCost =
|
||||||
modelInfo.cost200K &&
|
modelInfo.cost200K &&
|
||||||
inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) > 200_000
|
inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) >
|
||||||
|
200_000
|
||||||
? modelInfo.cost200K
|
? modelInfo.cost200K
|
||||||
: modelInfo.cost
|
: modelInfo.cost
|
||||||
|
|
||||||
@@ -420,7 +462,8 @@ export async function handler(
|
|||||||
|
|
||||||
if (!authInfo) return
|
if (!authInfo) return
|
||||||
|
|
||||||
const cost = authInfo.isFree || authInfo.provider?.credentials ? 0 : centsToMicroCents(totalCostInCent)
|
const cost =
|
||||||
|
authInfo.isFree || authInfo.provider?.credentials ? 0 : centsToMicroCents(totalCostInCent)
|
||||||
await Database.transaction(async (tx) => {
|
await Database.transaction(async (tx) => {
|
||||||
await tx.insert(UsageTable).values({
|
await tx.insert(UsageTable).values({
|
||||||
workspaceID: authInfo.workspaceID,
|
workspaceID: authInfo.workspaceID,
|
||||||
@@ -460,7 +503,9 @@ export async function handler(
|
|||||||
`,
|
`,
|
||||||
timeMonthlyUsageUpdated: sql`now()`,
|
timeMonthlyUsageUpdated: sql`now()`,
|
||||||
})
|
})
|
||||||
.where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id)))
|
.where(
|
||||||
|
and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id)),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await Database.use((tx) =>
|
await Database.use((tx) =>
|
||||||
@@ -487,7 +532,10 @@ export async function handler(
|
|||||||
eq(BillingTable.workspaceID, authInfo.workspaceID),
|
eq(BillingTable.workspaceID, authInfo.workspaceID),
|
||||||
eq(BillingTable.reload, true),
|
eq(BillingTable.reload, true),
|
||||||
lt(BillingTable.balance, centsToMicroCents(Billing.CHARGE_THRESHOLD)),
|
lt(BillingTable.balance, centsToMicroCents(Billing.CHARGE_THRESHOLD)),
|
||||||
or(isNull(BillingTable.timeReloadLockedTill), lt(BillingTable.timeReloadLockedTill, sql`now()`)),
|
or(
|
||||||
|
isNull(BillingTable.timeReloadLockedTill),
|
||||||
|
lt(BillingTable.timeReloadLockedTill, sql`now()`),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user