feat: configurable tool pricing, Sub2API channel, and alpha search billing
Add admin-configurable tool-call prices with cross-provider surcharge settlement, Sub2API channel support, /v1/alpha/search relay, and usage-log surcharge UI.
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import { after, describe, test } from 'node:test'
|
||||
|
||||
import { Window } from 'happy-dom'
|
||||
import type React from 'react'
|
||||
|
||||
const domWindow = new Window()
|
||||
const domGlobals = [
|
||||
'window',
|
||||
'document',
|
||||
'navigator',
|
||||
'HTMLElement',
|
||||
'SVGElement',
|
||||
'Node',
|
||||
'Element',
|
||||
'Event',
|
||||
'CustomEvent',
|
||||
'MutationObserver',
|
||||
'requestAnimationFrame',
|
||||
'cancelAnimationFrame',
|
||||
'getComputedStyle',
|
||||
] as const
|
||||
|
||||
for (const key of domGlobals) {
|
||||
Object.defineProperty(globalThis, key, {
|
||||
configurable: true,
|
||||
value: domWindow[key],
|
||||
})
|
||||
}
|
||||
|
||||
const { act } = await import('react')
|
||||
const { createRoot } = await import('react-dom/client')
|
||||
const { createInstance } = await import('i18next')
|
||||
const { I18nextProvider, initReactI18next } = await import('react-i18next')
|
||||
|
||||
const i18n = createInstance()
|
||||
await i18n.use(initReactI18next).init({
|
||||
lng: 'en',
|
||||
resources: {
|
||||
en: {
|
||||
translation: {
|
||||
Subscription: 'Subscription',
|
||||
'Deducted by subscription': 'Deducted by subscription',
|
||||
'Includes tool-call surcharge': 'Includes tool-call surcharge',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const { LogCostDisplay } = await import('../log-cost-display')
|
||||
const { formatLogQuota } = await import('@/lib/format')
|
||||
const reactTestGlobals = globalThis as typeof globalThis & {
|
||||
IS_REACT_ACT_ENVIRONMENT?: boolean
|
||||
}
|
||||
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
|
||||
|
||||
type RenderedCost = {
|
||||
container: HTMLDivElement
|
||||
root: ReturnType<typeof createRoot>
|
||||
}
|
||||
|
||||
async function renderCost(
|
||||
props: React.ComponentProps<typeof LogCostDisplay>
|
||||
): Promise<RenderedCost> {
|
||||
const container = document.createElement('div')
|
||||
document.body.append(container)
|
||||
const root = createRoot(container)
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<LogCostDisplay {...props} />
|
||||
</I18nextProvider>
|
||||
)
|
||||
})
|
||||
|
||||
return { container, root }
|
||||
}
|
||||
|
||||
async function unmountCost(rendered: RenderedCost) {
|
||||
await act(async () => rendered.root.unmount())
|
||||
rendered.container.remove()
|
||||
}
|
||||
|
||||
function normalizedText(value: string | null): string {
|
||||
return (value ?? '').replaceAll(/\s/g, '')
|
||||
}
|
||||
|
||||
describe('log cost display', () => {
|
||||
after(() => {
|
||||
domWindow.close()
|
||||
})
|
||||
|
||||
test('keeps the regular cost visible and adds an accessible surcharge marker', async () => {
|
||||
const rendered = await renderCost({
|
||||
quota: 12500,
|
||||
other: {
|
||||
tool_surcharges: [{ name: 'lookup_customer', count: 1, price: 5 }],
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(
|
||||
normalizedText(rendered.container.textContent).includes(
|
||||
normalizedText(formatLogQuota(12500))
|
||||
),
|
||||
true
|
||||
)
|
||||
const marker = rendered.container.querySelector(
|
||||
'[data-tool-surcharge-indicator="true"]'
|
||||
)
|
||||
assert.ok(marker)
|
||||
assert.equal(
|
||||
marker.getAttribute('aria-label'),
|
||||
'Includes tool-call surcharge'
|
||||
)
|
||||
assert.equal(marker.getAttribute('tabindex'), '0')
|
||||
|
||||
await unmountCost(rendered)
|
||||
})
|
||||
|
||||
test('preserves the subscription badge and adds the same legacy surcharge marker', async () => {
|
||||
const rendered = await renderCost({
|
||||
quota: 5000,
|
||||
other: {
|
||||
billing_source: 'subscription',
|
||||
web_search: true,
|
||||
web_search_call_count: 1,
|
||||
web_search_price: 10,
|
||||
},
|
||||
})
|
||||
|
||||
assert.equal(rendered.container.textContent?.includes('Subscription'), true)
|
||||
assert.ok(
|
||||
rendered.container.querySelector('[data-tool-surcharge-indicator="true"]')
|
||||
)
|
||||
|
||||
await unmountCost(rendered)
|
||||
})
|
||||
})
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
} from '../../lib/utils'
|
||||
import type { LogOtherData } from '../../types'
|
||||
import { DetailsDialog } from '../dialogs/details-dialog'
|
||||
import { LogCostDisplay } from '../log-cost-display'
|
||||
import { ModelBadge } from '../model-badge'
|
||||
import { TimingMetricsCell, StreamTpsCell } from '../timing-metrics-cell'
|
||||
import { useUsageLogsContext } from '../usage-logs-provider'
|
||||
@@ -93,12 +94,6 @@ function getGroupRatio(other: LogOtherData | null): number | null {
|
||||
return null
|
||||
}
|
||||
|
||||
function splitQuotaDisplay(value: string): { prefix: string; amount: string } {
|
||||
const match = value.match(/^([^0-9+\-.,\s]+)(.+)$/)
|
||||
if (!match) return { prefix: '', amount: value }
|
||||
return { prefix: match[1], amount: match[2] }
|
||||
}
|
||||
|
||||
function buildDetailSegments(
|
||||
log: UsageLog,
|
||||
other: LogOtherData | null,
|
||||
@@ -703,46 +698,7 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef<UsageLog>[] {
|
||||
|
||||
const quota = row.getValue('quota') as number
|
||||
const other = parseLogOther(log.other)
|
||||
const isSubscription = other?.billing_source === 'subscription'
|
||||
|
||||
if (isSubscription) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<StatusBadge
|
||||
label={t('Subscription')}
|
||||
variant='success'
|
||||
size='sm'
|
||||
copyable={false}
|
||||
className='cursor-help'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>
|
||||
<span>
|
||||
{t('Deducted by subscription')}: {formatLogQuota(quota)}
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const quotaStr = formatLogQuota(quota)
|
||||
const quotaDisplay = splitQuotaDisplay(quotaStr)
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
<span className='border-border/80 bg-muted/60 inline-flex h-6 w-fit items-center rounded-md border px-2 [font-family:var(--font-body)] text-sm leading-none font-semibold tabular-nums'>
|
||||
{quotaDisplay.prefix && (
|
||||
<span className='mr-1'>{quotaDisplay.prefix}</span>
|
||||
)}
|
||||
<span>{quotaDisplay.amount}</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
return <LogCostDisplay quota={quota} other={other} />
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { Wrench01Icon } from '@hugeicons/core-free-icons'
|
||||
import { HugeiconsIcon } from '@hugeicons/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { formatLogQuota } from '@/lib/format'
|
||||
|
||||
import { hasToolSurcharge } from '../lib/format'
|
||||
import type { LogOtherData } from '../types'
|
||||
|
||||
interface LogCostDisplayProps {
|
||||
quota: number
|
||||
other: LogOtherData | null
|
||||
}
|
||||
|
||||
function splitQuotaDisplay(value: string): { prefix: string; amount: string } {
|
||||
const match = value.match(/^([^0-9+\-.,\s]+)(.+)$/)
|
||||
if (!match) return { prefix: '', amount: value }
|
||||
return { prefix: match[1], amount: match[2] }
|
||||
}
|
||||
|
||||
function ToolSurchargeMarker() {
|
||||
const { t } = useTranslation()
|
||||
const label = t('Includes tool-call surcharge')
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Badge
|
||||
variant='warning'
|
||||
className='h-5 min-w-5 cursor-help gap-0 rounded-full px-1'
|
||||
role='img'
|
||||
aria-label={label}
|
||||
tabIndex={0}
|
||||
data-tool-surcharge-indicator='true'
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={Wrench01Icon}
|
||||
strokeWidth={2}
|
||||
aria-hidden='true'
|
||||
/>
|
||||
<span
|
||||
className='text-[9px] leading-none font-bold'
|
||||
aria-hidden='true'
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</Badge>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
function QuotaBadge(props: { quota: number }) {
|
||||
const quotaDisplay = splitQuotaDisplay(formatLogQuota(props.quota))
|
||||
|
||||
return (
|
||||
<span className='border-border/80 bg-muted/60 inline-flex h-6 w-fit items-center rounded-md border px-2 [font-family:var(--font-body)] text-sm leading-none font-semibold tabular-nums'>
|
||||
{quotaDisplay.prefix ? (
|
||||
<span className='mr-1'>{quotaDisplay.prefix}</span>
|
||||
) : null}
|
||||
<span>{quotaDisplay.amount}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function SubscriptionBadge(props: { quota: number }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<StatusBadge
|
||||
label={t('Subscription')}
|
||||
variant='success'
|
||||
size='sm'
|
||||
copyable={false}
|
||||
className='cursor-help'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>
|
||||
<span>
|
||||
{t('Deducted by subscription')}: {formatLogQuota(props.quota)}
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export function LogCostDisplay(props: LogCostDisplayProps) {
|
||||
const isSubscription = props.other?.billing_source === 'subscription'
|
||||
const showToolSurcharge = hasToolSurcharge(props.other)
|
||||
|
||||
if (!isSubscription && !showToolSurcharge) {
|
||||
return (
|
||||
<div className='flex flex-col gap-0.5'>
|
||||
<QuotaBadge quota={props.quota} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className='inline-flex items-center gap-1'>
|
||||
{isSubscription ? (
|
||||
<SubscriptionBadge quota={props.quota} />
|
||||
) : (
|
||||
<QuotaBadge quota={props.quota} />
|
||||
)}
|
||||
{showToolSurcharge ? <ToolSurchargeMarker /> : null}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import { describe, test } from 'node:test'
|
||||
|
||||
import type { LogOtherData } from '../../types'
|
||||
import { hasToolSurcharge } from '../format'
|
||||
|
||||
describe('tool surcharge detection', () => {
|
||||
test('shows the marker for a charged structured tool surcharge', () => {
|
||||
assert.equal(
|
||||
hasToolSurcharge({
|
||||
tool_surcharges: [{ name: 'lookup_customer', count: 2, price: 5 }],
|
||||
}),
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
const legacyCases: Array<{
|
||||
name: string
|
||||
other: LogOtherData
|
||||
}> = [
|
||||
{
|
||||
name: 'Web Search',
|
||||
other: {
|
||||
web_search: true,
|
||||
web_search_call_count: 1,
|
||||
web_search_price: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'File Search',
|
||||
other: {
|
||||
file_search: true,
|
||||
file_search_call_count: 2,
|
||||
file_search_price: 2.5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Image Generation',
|
||||
other: {
|
||||
image_generation_call: true,
|
||||
image_generation_call_price: 0.04,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
for (const scenario of legacyCases) {
|
||||
test(`keeps the marker visible for legacy ${scenario.name} charges`, () => {
|
||||
assert.equal(hasToolSurcharge(scenario.other), true)
|
||||
})
|
||||
}
|
||||
|
||||
test('hides the marker when surcharge entries are empty or not chargeable', () => {
|
||||
const invalidCases: Array<LogOtherData | null> = [
|
||||
null,
|
||||
{},
|
||||
{ tool_surcharges: [] },
|
||||
{
|
||||
tool_surcharges: [{ name: 'lookup_customer', count: 0, price: 5 }],
|
||||
},
|
||||
{
|
||||
tool_surcharges: [{ name: 'lookup_customer', count: 1, price: 0 }],
|
||||
},
|
||||
{
|
||||
tool_surcharges: [{ name: ' ', count: 1, price: 5 }],
|
||||
},
|
||||
{
|
||||
web_search: true,
|
||||
web_search_call_count: 1,
|
||||
web_search_price: 0,
|
||||
},
|
||||
{
|
||||
image_generation_call: false,
|
||||
image_generation_call_price: 0.04,
|
||||
},
|
||||
]
|
||||
|
||||
for (const other of invalidCases) {
|
||||
assert.equal(hasToolSurcharge(other), false)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -92,6 +92,67 @@ export function isViolationFeeLog(other: LogOtherData | null): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
function isPositiveFiniteNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && Number.isFinite(value) && value > 0
|
||||
}
|
||||
|
||||
function hasLegacySearchSurcharge(
|
||||
enabled: boolean | undefined,
|
||||
count: number | undefined,
|
||||
price: number | undefined
|
||||
): boolean {
|
||||
return (
|
||||
enabled === true &&
|
||||
isPositiveFiniteNumber(count) &&
|
||||
isPositiveFiniteNumber(price)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a consume log includes an actual tool-call surcharge.
|
||||
* Structured surcharge items cover current logs, while the legacy fields keep
|
||||
* historical Web Search, File Search, and Image Generation logs visible.
|
||||
*/
|
||||
export function hasToolSurcharge(other: LogOtherData | null): boolean {
|
||||
if (!other) return false
|
||||
|
||||
const hasStructuredSurcharge =
|
||||
Array.isArray(other.tool_surcharges) &&
|
||||
other.tool_surcharges.some(
|
||||
(item) =>
|
||||
typeof item?.name === 'string' &&
|
||||
item.name.trim() !== '' &&
|
||||
isPositiveFiniteNumber(item.count) &&
|
||||
isPositiveFiniteNumber(item.price)
|
||||
)
|
||||
if (hasStructuredSurcharge) return true
|
||||
|
||||
if (
|
||||
hasLegacySearchSurcharge(
|
||||
other.web_search,
|
||||
other.web_search_call_count,
|
||||
other.web_search_price
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
hasLegacySearchSurcharge(
|
||||
other.file_search,
|
||||
other.file_search_call_count,
|
||||
other.file_search_price
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return (
|
||||
other.image_generation_call === true &&
|
||||
isPositiveFiniteNumber(other.image_generation_call_price)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the 'other' field from JSON string to object
|
||||
*/
|
||||
|
||||
@@ -106,6 +106,12 @@ export const USAGE_BILLING_PATH = {
|
||||
export type UsageBillingPath =
|
||||
(typeof USAGE_BILLING_PATH)[keyof typeof USAGE_BILLING_PATH]
|
||||
|
||||
export interface ToolSurchargeItem {
|
||||
name: string
|
||||
count: number
|
||||
price: number
|
||||
}
|
||||
|
||||
export interface LogOtherData {
|
||||
admin_info?: {
|
||||
is_multi_key?: boolean
|
||||
@@ -197,11 +203,13 @@ export interface LogOtherData {
|
||||
file_search?: boolean
|
||||
file_search_call_count?: number
|
||||
file_search_price?: number
|
||||
tool_surcharges?: ToolSurchargeItem[]
|
||||
audio_input_seperate_price?: boolean
|
||||
audio_input_token_count?: number
|
||||
audio_input_price?: number
|
||||
image_generation_call?: boolean
|
||||
image_generation_call_price?: number
|
||||
image_generation_call_count?: number
|
||||
is_system_prompt_overwritten?: boolean
|
||||
po?: string[]
|
||||
billing_source?: string
|
||||
|
||||
Reference in New Issue
Block a user