File size: 1,711 Bytes
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1cfe1b
 
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { NewApiCostResolver } from './new-api';
import type { ActualCostDetails, ResolveActualCostInput, UpstreamCostProvider } from './types';

export type { ActualCostDetails, ActualCostConfidence, ActualCostSource, UpstreamCostProvider } from './types';

function unavailable(reason: string, upstreamProvider: UpstreamCostProvider = 'unknown'): ActualCostDetails {
    return {
        currency: 'usd-equivalent',
        source: 'unavailable',
        confidence: 'none',
        upstreamProvider,
        reason
    };
}

function detectKnownProvider(apiBaseUrl: string): UpstreamCostProvider {
    let upstreamProvider: UpstreamCostProvider = 'unknown';
    try {
        const parsed = new URL(apiBaseUrl);
        const pathIsOpenAiApiRoot =
            parsed.pathname === '/' || parsed.pathname === '/v1' || parsed.pathname.startsWith('/v1/');
        if (parsed.protocol === 'https:' && parsed.hostname === 'api.openai.com' && pathIsOpenAiApiRoot) {
            upstreamProvider = 'openai';
        }
    } catch (error) {
        console.debug('解析上游 API URL 失败。', { apiBaseUrl, error });
        if (!(error instanceof TypeError)) {
            throw error;
        }
    }
    return upstreamProvider;
}

export async function resolveActualCost(input: ResolveActualCostInput): Promise<ActualCostDetails> {
    if (!input.apiBaseUrl || !input.apiKey) {
        return unavailable('缺少上游 API URL 或 API key。');
    }

    const knownProvider = detectKnownProvider(input.apiBaseUrl);
    if (knownProvider === 'openai') {
        return unavailable('OpenAI 官方接口不提供 new-api 扣费日志。', 'openai');
    }

    return new NewApiCostResolver().resolve(input);
}