@undefineds.co/xpod 0.2.23 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/chatkit/pod-store.d.ts +3 -0
- package/dist/api/chatkit/pod-store.js +139 -0
- package/dist/api/chatkit/pod-store.js.map +1 -1
- package/dist/api/service/VercelChatService.d.ts +4 -15
- package/dist/api/service/VercelChatService.js +80 -302
- package/dist/api/service/VercelChatService.js.map +1 -1
- package/dist/api/service/ai-gateway-transport.d.ts +23 -0
- package/dist/api/service/ai-gateway-transport.js +131 -0
- package/dist/api/service/ai-gateway-transport.js.map +1 -0
- package/dist/api/service/chat-protocol-adapters.d.ts +5 -0
- package/dist/api/service/chat-protocol-adapters.js +146 -0
- package/dist/api/service/chat-protocol-adapters.js.map +1 -0
- package/dist/api/service/chat-routing.d.ts +8 -0
- package/dist/api/service/chat-routing.js +16 -0
- package/dist/api/service/chat-routing.js.map +1 -0
- package/dist/api/service/provider-http-transport.d.ts +9 -0
- package/dist/api/service/provider-http-transport.js +32 -0
- package/dist/api/service/provider-http-transport.js.map +1 -0
- package/package.json +1 -1
|
@@ -5,12 +5,11 @@ import type { UsageRepository } from '../../storage/quota/UsageRepository';
|
|
|
5
5
|
import type { QuotaService } from '../../quota/QuotaService';
|
|
6
6
|
export declare class VercelChatService {
|
|
7
7
|
private readonly store;
|
|
8
|
-
private static readonly AI_GATEWAY_MODEL_CACHE_TTL_MS;
|
|
9
8
|
private readonly logger;
|
|
10
9
|
private usageRepo?;
|
|
11
10
|
private quotaService?;
|
|
12
|
-
private
|
|
13
|
-
private
|
|
11
|
+
private readonly aiGatewayTransport;
|
|
12
|
+
private readonly providerHttpTransport;
|
|
14
13
|
constructor(store: PodChatKitStore);
|
|
15
14
|
/**
|
|
16
15
|
* Set optional usage tracking dependencies (injected after construction)
|
|
@@ -23,19 +22,11 @@ export declare class VercelChatService {
|
|
|
23
22
|
private getAiGatewayBaseUrl;
|
|
24
23
|
private getAiGatewayTimeoutMs;
|
|
25
24
|
private getAiGatewayApiKey;
|
|
26
|
-
private toModelId;
|
|
27
|
-
private isAiGatewayModelCacheFresh;
|
|
28
|
-
private getAiGatewayModelCache;
|
|
29
25
|
private shouldUseAiGateway;
|
|
30
|
-
private
|
|
31
|
-
private
|
|
32
|
-
private sendAiGatewayRequest;
|
|
26
|
+
private toModelId;
|
|
27
|
+
private pushModelsWithDedup;
|
|
33
28
|
private forwardAiGatewayJson;
|
|
34
29
|
private forwardAiGatewayStream;
|
|
35
|
-
private extractCompletionText;
|
|
36
|
-
private buildChatCompletionsBodyFromMessages;
|
|
37
|
-
private mapChatCompletionFinishReason;
|
|
38
|
-
private mapChatCompletionToMessagesResponse;
|
|
39
30
|
private extractTotalTokens;
|
|
40
31
|
private recordForwardedUsage;
|
|
41
32
|
private getProviderConfig;
|
|
@@ -46,8 +37,6 @@ export declare class VercelChatService {
|
|
|
46
37
|
messages(body: any, auth: AuthContext): Promise<any>;
|
|
47
38
|
private responsesViaCompletions;
|
|
48
39
|
private messagesViaCompletions;
|
|
49
|
-
private extractPromptFromResponsesBody;
|
|
50
|
-
private extractPromptFromMessagesBody;
|
|
51
40
|
listModels(_auth?: AuthContext): Promise<any[]>;
|
|
52
41
|
private mapFinishReason;
|
|
53
42
|
/**
|
|
@@ -9,6 +9,10 @@ const AuthContext_1 = require("../auth/AuthContext");
|
|
|
9
9
|
const types_1 = require("../../credential/schema/types");
|
|
10
10
|
const provider_registry_1 = require("./provider-registry");
|
|
11
11
|
const platform_ai_config_1 = require("./platform-ai-config");
|
|
12
|
+
const chat_protocol_adapters_1 = require("./chat-protocol-adapters");
|
|
13
|
+
const ai_gateway_transport_1 = require("./ai-gateway-transport");
|
|
14
|
+
const provider_http_transport_1 = require("./provider-http-transport");
|
|
15
|
+
const chat_routing_1 = require("./chat-routing");
|
|
12
16
|
// Create a proxy-aware fetch function
|
|
13
17
|
function createProxyFetch(proxyUrl) {
|
|
14
18
|
const agent = new undici_1.ProxyAgent(proxyUrl);
|
|
@@ -18,9 +22,13 @@ class VercelChatService {
|
|
|
18
22
|
constructor(store) {
|
|
19
23
|
this.store = store;
|
|
20
24
|
this.logger = (0, global_logger_factory_1.getLoggerFor)(this);
|
|
21
|
-
this.
|
|
22
|
-
this.aiGatewayModelCachePromise = null;
|
|
25
|
+
this.providerHttpTransport = new provider_http_transport_1.ProviderHttpTransport();
|
|
23
26
|
this.logger.info('Initializing VercelChatService with Pod-based config support');
|
|
27
|
+
this.aiGatewayTransport = new ai_gateway_transport_1.AiGatewayTransport({
|
|
28
|
+
getBaseUrl: () => this.getAiGatewayBaseUrl(),
|
|
29
|
+
getApiKey: () => this.getAiGatewayApiKey(),
|
|
30
|
+
getTimeoutMs: () => this.getAiGatewayTimeoutMs(),
|
|
31
|
+
});
|
|
24
32
|
}
|
|
25
33
|
/**
|
|
26
34
|
* Set optional usage tracking dependencies (injected after construction)
|
|
@@ -47,194 +55,27 @@ class VercelChatService {
|
|
|
47
55
|
getAiGatewayApiKey() {
|
|
48
56
|
return (0, platform_ai_config_1.getAiGatewayApiKey)() ?? null;
|
|
49
57
|
}
|
|
58
|
+
async shouldUseAiGateway(model) {
|
|
59
|
+
return this.aiGatewayTransport.shouldHandleModel(model);
|
|
60
|
+
}
|
|
50
61
|
toModelId(model) {
|
|
51
62
|
return typeof model?.id === 'string' ? model.id : JSON.stringify(model);
|
|
52
63
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!this.getAiGatewayBaseUrl()) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
if (this.isAiGatewayModelCacheFresh()) {
|
|
62
|
-
return this.aiGatewayModelCache;
|
|
63
|
-
}
|
|
64
|
-
if (this.aiGatewayModelCachePromise) {
|
|
65
|
-
return this.aiGatewayModelCachePromise;
|
|
66
|
-
}
|
|
67
|
-
this.aiGatewayModelCachePromise = (async () => {
|
|
68
|
-
const response = await this.sendAiGatewayRequest('/v1/models', 'GET', undefined, {
|
|
69
|
-
'Accept': 'application/json',
|
|
70
|
-
});
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
const items = Array.isArray(data.data) ? data.data : [];
|
|
73
|
-
const cache = {
|
|
74
|
-
fetchedAt: Date.now(),
|
|
75
|
-
items,
|
|
76
|
-
modelIds: new Set(items.map((item) => this.toModelId(item))),
|
|
77
|
-
};
|
|
78
|
-
this.aiGatewayModelCache = cache;
|
|
79
|
-
return cache;
|
|
80
|
-
})();
|
|
81
|
-
try {
|
|
82
|
-
return await this.aiGatewayModelCachePromise;
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
if (this.aiGatewayModelCache) {
|
|
86
|
-
this.logger.warn(`Failed to refresh ai-gateway models, using stale cache: ${error}`);
|
|
87
|
-
return this.aiGatewayModelCache;
|
|
64
|
+
pushModelsWithDedup(models, seenModelIds, items) {
|
|
65
|
+
for (const model of items) {
|
|
66
|
+
const modelId = this.toModelId(model);
|
|
67
|
+
if (seenModelIds.has(modelId)) {
|
|
68
|
+
continue;
|
|
88
69
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
this.aiGatewayModelCachePromise = null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async shouldUseAiGateway(model) {
|
|
97
|
-
if (!model || !this.getAiGatewayBaseUrl()) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
const cache = await this.getAiGatewayModelCache();
|
|
101
|
-
return cache?.modelIds.has(model) ?? false;
|
|
102
|
-
}
|
|
103
|
-
buildAiGatewayUrl(path) {
|
|
104
|
-
const baseUrl = this.getAiGatewayBaseUrl();
|
|
105
|
-
if (!baseUrl) {
|
|
106
|
-
throw new Error('DEFAULT_API_BASE is not configured');
|
|
107
|
-
}
|
|
108
|
-
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
109
|
-
if (baseUrl.endsWith('/v1') && normalizedPath.startsWith('/v1/')) {
|
|
110
|
-
return `${baseUrl}${normalizedPath.slice(3)}`;
|
|
111
|
-
}
|
|
112
|
-
return `${baseUrl}${normalizedPath}`;
|
|
113
|
-
}
|
|
114
|
-
createAiGatewayAbortSignal() {
|
|
115
|
-
const abortSignal = AbortSignal;
|
|
116
|
-
return typeof abortSignal.timeout === 'function'
|
|
117
|
-
? abortSignal.timeout(this.getAiGatewayTimeoutMs())
|
|
118
|
-
: undefined;
|
|
119
|
-
}
|
|
120
|
-
async sendAiGatewayRequest(path, method, body, headers) {
|
|
121
|
-
const apiKey = this.getAiGatewayApiKey();
|
|
122
|
-
if (!apiKey) {
|
|
123
|
-
throw new Error('DEFAULT_API_KEY is not configured');
|
|
124
|
-
}
|
|
125
|
-
const requestHeaders = new Headers(headers);
|
|
126
|
-
requestHeaders.set('Authorization', `Bearer ${apiKey}`);
|
|
127
|
-
if (body !== undefined && !requestHeaders.has('Content-Type')) {
|
|
128
|
-
requestHeaders.set('Content-Type', 'application/json');
|
|
129
|
-
}
|
|
130
|
-
const response = await fetch(this.buildAiGatewayUrl(path), {
|
|
131
|
-
method,
|
|
132
|
-
headers: requestHeaders,
|
|
133
|
-
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
134
|
-
signal: this.createAiGatewayAbortSignal(),
|
|
135
|
-
});
|
|
136
|
-
if (!response.ok) {
|
|
137
|
-
const errorText = await response.text().catch(() => '');
|
|
138
|
-
this.logger.warn(`Platform AI request failed: ${response.status} ${errorText}`);
|
|
139
|
-
const error = new Error(`Platform AI error: ${response.status} ${response.statusText}`);
|
|
140
|
-
error.status = response.status;
|
|
141
|
-
error.headers = response.headers;
|
|
142
|
-
error.body = errorText;
|
|
143
|
-
throw error;
|
|
70
|
+
seenModelIds.add(modelId);
|
|
71
|
+
models.push(model);
|
|
144
72
|
}
|
|
145
|
-
return response;
|
|
146
73
|
}
|
|
147
74
|
async forwardAiGatewayJson(path, body, _auth) {
|
|
148
|
-
|
|
149
|
-
'Accept': 'application/json',
|
|
150
|
-
});
|
|
151
|
-
return response.json();
|
|
75
|
+
return this.aiGatewayTransport.sendJson(path, body);
|
|
152
76
|
}
|
|
153
77
|
async forwardAiGatewayStream(path, body, _auth) {
|
|
154
|
-
|
|
155
|
-
'Accept': 'text/event-stream',
|
|
156
|
-
});
|
|
157
|
-
return {
|
|
158
|
-
toTextStreamResponse: () => new Response(response.body, {
|
|
159
|
-
status: response.status,
|
|
160
|
-
statusText: response.statusText,
|
|
161
|
-
headers: new Headers(response.headers),
|
|
162
|
-
}),
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
extractCompletionText(content) {
|
|
166
|
-
if (typeof content === 'string') {
|
|
167
|
-
return content;
|
|
168
|
-
}
|
|
169
|
-
if (Array.isArray(content)) {
|
|
170
|
-
return content
|
|
171
|
-
.filter((item) => item && typeof item === 'object' && typeof item.text === 'string')
|
|
172
|
-
.map((item) => item.text)
|
|
173
|
-
.join('\n');
|
|
174
|
-
}
|
|
175
|
-
return content == null ? '' : String(content);
|
|
176
|
-
}
|
|
177
|
-
buildChatCompletionsBodyFromMessages(body) {
|
|
178
|
-
const messages = [];
|
|
179
|
-
if (body?.system) {
|
|
180
|
-
const systemText = this.extractCompletionText(body.system);
|
|
181
|
-
if (systemText) {
|
|
182
|
-
messages.push({ role: 'system', content: systemText });
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (Array.isArray(body?.messages)) {
|
|
186
|
-
for (const message of body.messages) {
|
|
187
|
-
if (!message?.role || message?.content == null) {
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
messages.push({
|
|
191
|
-
role: String(message.role),
|
|
192
|
-
content: this.extractCompletionText(message.content),
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (messages.length === 0 && body?.content != null) {
|
|
197
|
-
messages.push({
|
|
198
|
-
role: 'user',
|
|
199
|
-
content: this.extractCompletionText(body.content),
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
model: body?.model,
|
|
204
|
-
messages,
|
|
205
|
-
...(body?.temperature != null ? { temperature: body.temperature } : {}),
|
|
206
|
-
...(body?.max_tokens != null ? { max_tokens: body.max_tokens } : {}),
|
|
207
|
-
...(Array.isArray(body?.stop_sequences) && body.stop_sequences.length > 0
|
|
208
|
-
? { stop: body.stop_sequences }
|
|
209
|
-
: {}),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
mapChatCompletionFinishReason(reason) {
|
|
213
|
-
if (reason === 'length') {
|
|
214
|
-
return 'max_tokens';
|
|
215
|
-
}
|
|
216
|
-
if (reason === 'content_filter') {
|
|
217
|
-
return 'stop_sequence';
|
|
218
|
-
}
|
|
219
|
-
return 'end_turn';
|
|
220
|
-
}
|
|
221
|
-
mapChatCompletionToMessagesResponse(body, completion) {
|
|
222
|
-
const choice = Array.isArray(completion?.choices) ? completion.choices[0] : undefined;
|
|
223
|
-
const text = this.extractCompletionText(choice?.message?.content);
|
|
224
|
-
const prompt = this.extractPromptFromMessagesBody(body);
|
|
225
|
-
return {
|
|
226
|
-
id: completion?.id ?? `msg_${Date.now()}`,
|
|
227
|
-
type: 'message',
|
|
228
|
-
role: 'assistant',
|
|
229
|
-
model: completion?.model ?? body?.model,
|
|
230
|
-
content: [{ type: 'text', text }],
|
|
231
|
-
stop_reason: this.mapChatCompletionFinishReason(choice?.finish_reason),
|
|
232
|
-
stop_sequence: null,
|
|
233
|
-
usage: {
|
|
234
|
-
input_tokens: completion?.usage?.prompt_tokens ?? prompt.length,
|
|
235
|
-
output_tokens: completion?.usage?.completion_tokens ?? text.length,
|
|
236
|
-
},
|
|
237
|
-
};
|
|
78
|
+
return this.aiGatewayTransport.sendStream(path, body);
|
|
238
79
|
}
|
|
239
80
|
extractTotalTokens(usage) {
|
|
240
81
|
if (!usage || typeof usage !== 'object') {
|
|
@@ -308,7 +149,7 @@ class VercelChatService {
|
|
|
308
149
|
if (accountId) {
|
|
309
150
|
await this.checkTokenQuota(accountId);
|
|
310
151
|
}
|
|
311
|
-
if (await this.shouldUseAiGateway(
|
|
152
|
+
if (await (0, chat_routing_1.resolveChatExecutionRoute)({ model, shouldUseAiGateway: this.shouldUseAiGateway.bind(this) }) === 'ai-gateway') {
|
|
312
153
|
this.logger.info(`Forwarding chat completion for model ${model} to ai-gateway`);
|
|
313
154
|
const result = await this.forwardAiGatewayJson('/v1/chat/completions', request, auth);
|
|
314
155
|
this.recordForwardedUsage(accountId, String(context.userId), result);
|
|
@@ -377,7 +218,7 @@ class VercelChatService {
|
|
|
377
218
|
async stream(request, auth) {
|
|
378
219
|
const { model, messages, temperature, max_tokens } = request;
|
|
379
220
|
const context = this.createStoreContext(auth);
|
|
380
|
-
if (await this.shouldUseAiGateway(
|
|
221
|
+
if (await (0, chat_routing_1.resolveChatExecutionRoute)({ model, shouldUseAiGateway: this.shouldUseAiGateway.bind(this) }) === 'ai-gateway') {
|
|
381
222
|
this.logger.info(`Forwarding chat stream for model ${model} to ai-gateway`);
|
|
382
223
|
return this.forwardAiGatewayStream('/v1/chat/completions', request, auth);
|
|
383
224
|
}
|
|
@@ -403,9 +244,10 @@ class VercelChatService {
|
|
|
403
244
|
const context = this.createStoreContext(auth);
|
|
404
245
|
const displayName = (0, AuthContext_1.getDisplayName)(auth) || context.userId;
|
|
405
246
|
const accountId = (0, AuthContext_1.getAccountId)(auth);
|
|
406
|
-
if (await
|
|
247
|
+
if (await (0, chat_routing_1.resolveChatExecutionRoute)({ model: body?.model, shouldUseAiGateway: this.shouldUseAiGateway.bind(this) }) === 'ai-gateway') {
|
|
407
248
|
this.logger.info(`Forwarding responses request for model ${body?.model} to ai-gateway for ${displayName} (acc: ${accountId})`);
|
|
408
|
-
const
|
|
249
|
+
const sanitizedBody = (0, chat_protocol_adapters_1.sanitizeAiGatewayResponsesBody)(body);
|
|
250
|
+
const result = await this.forwardAiGatewayJson('/v1/responses', sanitizedBody, auth);
|
|
409
251
|
this.recordForwardedUsage(accountId, String(context.userId), result);
|
|
410
252
|
return result;
|
|
411
253
|
}
|
|
@@ -417,7 +259,7 @@ class VercelChatService {
|
|
|
417
259
|
}
|
|
418
260
|
const { baseURL } = providerConfig;
|
|
419
261
|
// Only OpenAI natively supports /v1/responses; all others go through Chat Completions
|
|
420
|
-
if (
|
|
262
|
+
if ((0, chat_routing_1.resolveResponsesProviderRoute)(baseURL) === 'chat-fallback') {
|
|
421
263
|
this.logger.info(`Provider ${baseURL} does not support Responses API, converting to Chat Completions for ${displayName} (acc: ${accountId})`);
|
|
422
264
|
return this.responsesViaCompletions(body, context, providerConfig);
|
|
423
265
|
}
|
|
@@ -426,33 +268,29 @@ class VercelChatService {
|
|
|
426
268
|
const cleanBaseUrl = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
|
|
427
269
|
const url = `${cleanBaseUrl}/responses`;
|
|
428
270
|
this.logger.info(`Proxying responses request to ${url} for ${displayName} (acc: ${accountId}), proxy: ${proxy || 'none'}`);
|
|
429
|
-
const fetchFn = proxy ? createProxyFetch(proxy) : fetch;
|
|
430
271
|
try {
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
},
|
|
437
|
-
body: JSON.stringify(body),
|
|
272
|
+
const result = await this.providerHttpTransport.postJson({
|
|
273
|
+
url,
|
|
274
|
+
apiKey,
|
|
275
|
+
proxy,
|
|
276
|
+
body,
|
|
438
277
|
});
|
|
439
|
-
if (!response.ok) {
|
|
440
|
-
const errorText = await response.text();
|
|
441
|
-
this.logger.error(`Responses API failed: ${response.status} ${errorText}`);
|
|
442
|
-
// Handle error and update credential status
|
|
443
|
-
if (credentialId) {
|
|
444
|
-
await this.handleApiError({ status: response.status, headers: response.headers }, context, credentialId);
|
|
445
|
-
}
|
|
446
|
-
throw new Error(`Provider error: ${response.statusText}`);
|
|
447
|
-
}
|
|
448
|
-
// Record successful API call
|
|
449
278
|
if (credentialId) {
|
|
450
279
|
this.store.recordCredentialSuccess(context, credentialId).catch(() => { });
|
|
451
280
|
}
|
|
452
|
-
return
|
|
281
|
+
return result;
|
|
453
282
|
}
|
|
454
283
|
catch (error) {
|
|
455
|
-
|
|
284
|
+
const status = error?.status;
|
|
285
|
+
const headers = error?.headers;
|
|
286
|
+
const bodyText = error?.body;
|
|
287
|
+
if (typeof status === 'number') {
|
|
288
|
+
this.logger.error(`Responses API failed: ${status} ${bodyText ?? ''}`);
|
|
289
|
+
if (credentialId) {
|
|
290
|
+
await this.handleApiError({ status, headers }, context, credentialId);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if (credentialId) {
|
|
456
294
|
await this.handleApiError(error, context, credentialId);
|
|
457
295
|
}
|
|
458
296
|
throw error;
|
|
@@ -462,11 +300,11 @@ class VercelChatService {
|
|
|
462
300
|
const context = this.createStoreContext(auth);
|
|
463
301
|
const displayName = (0, AuthContext_1.getDisplayName)(auth) || context.userId;
|
|
464
302
|
const accountId = (0, AuthContext_1.getAccountId)(auth);
|
|
465
|
-
if (await
|
|
303
|
+
if (await (0, chat_routing_1.resolveChatExecutionRoute)({ model: body?.model, shouldUseAiGateway: this.shouldUseAiGateway.bind(this) }) === 'ai-gateway') {
|
|
466
304
|
this.logger.info(`Forwarding messages request for model ${body?.model} to ai-gateway for ${displayName} (acc: ${accountId})`);
|
|
467
|
-
const completionBody =
|
|
305
|
+
const completionBody = (0, chat_protocol_adapters_1.buildChatCompletionsBodyFromMessages)(body);
|
|
468
306
|
const completion = await this.forwardAiGatewayJson('/v1/chat/completions', completionBody, auth);
|
|
469
|
-
const result =
|
|
307
|
+
const result = (0, chat_protocol_adapters_1.mapChatCompletionToMessagesResponse)(body, completion);
|
|
470
308
|
this.recordForwardedUsage(accountId, String(context.userId), result);
|
|
471
309
|
return result;
|
|
472
310
|
}
|
|
@@ -478,7 +316,7 @@ class VercelChatService {
|
|
|
478
316
|
}
|
|
479
317
|
const { baseURL } = providerConfig;
|
|
480
318
|
// Only Anthropic natively supports /v1/messages; all others go through Chat Completions
|
|
481
|
-
if (
|
|
319
|
+
if ((0, chat_routing_1.resolveMessagesProviderRoute)(baseURL) === 'chat-fallback') {
|
|
482
320
|
this.logger.info(`Provider ${baseURL} does not support Messages API, converting to Chat Completions for ${displayName} (acc: ${accountId})`);
|
|
483
321
|
return this.messagesViaCompletions(body, context, providerConfig);
|
|
484
322
|
}
|
|
@@ -487,42 +325,40 @@ class VercelChatService {
|
|
|
487
325
|
const cleanBaseUrl = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
|
|
488
326
|
const url = `${cleanBaseUrl}/messages`;
|
|
489
327
|
this.logger.info(`Proxying messages request to ${url} for ${displayName} (acc: ${accountId}), proxy: ${proxy || 'none'}`);
|
|
490
|
-
const fetchFn = proxy ? createProxyFetch(proxy) : fetch;
|
|
491
328
|
try {
|
|
492
|
-
const
|
|
493
|
-
|
|
329
|
+
const result = await this.providerHttpTransport.postJson({
|
|
330
|
+
url,
|
|
331
|
+
apiKey,
|
|
332
|
+
proxy,
|
|
333
|
+
body,
|
|
494
334
|
headers: {
|
|
495
|
-
'Content-Type': 'application/json',
|
|
496
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
497
335
|
'x-api-key': apiKey,
|
|
498
336
|
'anthropic-version': '2023-06-01',
|
|
499
337
|
},
|
|
500
|
-
body: JSON.stringify(body),
|
|
501
338
|
});
|
|
502
|
-
if (!response.ok) {
|
|
503
|
-
const errorText = await response.text();
|
|
504
|
-
this.logger.error(`Messages API failed: ${response.status} ${errorText}`);
|
|
505
|
-
// Handle error and update credential status
|
|
506
|
-
if (credentialId) {
|
|
507
|
-
await this.handleApiError({ status: response.status, headers: response.headers }, context, credentialId);
|
|
508
|
-
}
|
|
509
|
-
throw new Error(`Provider error: ${response.statusText}`);
|
|
510
|
-
}
|
|
511
|
-
// Record successful API call
|
|
512
339
|
if (credentialId) {
|
|
513
340
|
this.store.recordCredentialSuccess(context, credentialId).catch(() => { });
|
|
514
341
|
}
|
|
515
|
-
return
|
|
342
|
+
return result;
|
|
516
343
|
}
|
|
517
344
|
catch (error) {
|
|
518
|
-
|
|
345
|
+
const status = error?.status;
|
|
346
|
+
const headers = error?.headers;
|
|
347
|
+
const bodyText = error?.body;
|
|
348
|
+
if (typeof status === 'number') {
|
|
349
|
+
this.logger.error(`Messages API failed: ${status} ${bodyText ?? ''}`);
|
|
350
|
+
if (credentialId) {
|
|
351
|
+
await this.handleApiError({ status, headers }, context, credentialId);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else if (credentialId) {
|
|
519
355
|
await this.handleApiError(error, context, credentialId);
|
|
520
356
|
}
|
|
521
357
|
throw error;
|
|
522
358
|
}
|
|
523
359
|
}
|
|
524
360
|
async responsesViaCompletions(body, context, providerConfig) {
|
|
525
|
-
const prompt =
|
|
361
|
+
const prompt = (0, chat_protocol_adapters_1.extractPromptFromResponsesBody)(body);
|
|
526
362
|
const model = body?.model || (0, platform_ai_config_1.getPlatformDefaultModel)();
|
|
527
363
|
const provider = await this.getProvider(context);
|
|
528
364
|
const result = await (0, ai_1.generateText)({
|
|
@@ -555,7 +391,7 @@ class VercelChatService {
|
|
|
555
391
|
};
|
|
556
392
|
}
|
|
557
393
|
async messagesViaCompletions(body, context, providerConfig) {
|
|
558
|
-
const prompt =
|
|
394
|
+
const prompt = (0, chat_protocol_adapters_1.extractPromptFromMessagesBody)(body);
|
|
559
395
|
const model = body?.model || (0, platform_ai_config_1.getPlatformDefaultModel)();
|
|
560
396
|
const coreMessages = [];
|
|
561
397
|
if (body?.system) {
|
|
@@ -608,85 +444,29 @@ class VercelChatService {
|
|
|
608
444
|
},
|
|
609
445
|
};
|
|
610
446
|
}
|
|
611
|
-
extractPromptFromResponsesBody(body) {
|
|
612
|
-
if (!body || typeof body !== 'object') {
|
|
613
|
-
return '';
|
|
614
|
-
}
|
|
615
|
-
if (typeof body.input === 'string') {
|
|
616
|
-
return body.input;
|
|
617
|
-
}
|
|
618
|
-
if (typeof body.prompt === 'string') {
|
|
619
|
-
return body.prompt;
|
|
620
|
-
}
|
|
621
|
-
if (Array.isArray(body.input)) {
|
|
622
|
-
const textParts = [];
|
|
623
|
-
for (const item of body.input) {
|
|
624
|
-
if (item && typeof item === 'object') {
|
|
625
|
-
const candidate = item.content;
|
|
626
|
-
if (typeof candidate === 'string') {
|
|
627
|
-
textParts.push(candidate);
|
|
628
|
-
}
|
|
629
|
-
else if (Array.isArray(candidate)) {
|
|
630
|
-
for (const part of candidate) {
|
|
631
|
-
if (part && typeof part === 'object' && typeof part.text === 'string') {
|
|
632
|
-
textParts.push(part.text);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
if (textParts.length > 0) {
|
|
639
|
-
return textParts.join('\n');
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return '';
|
|
643
|
-
}
|
|
644
|
-
extractPromptFromMessagesBody(body) {
|
|
645
|
-
if (!body || typeof body !== 'object') {
|
|
646
|
-
return '';
|
|
647
|
-
}
|
|
648
|
-
if (typeof body.content === 'string') {
|
|
649
|
-
return body.content;
|
|
650
|
-
}
|
|
651
|
-
if (Array.isArray(body.messages)) {
|
|
652
|
-
const lastUser = [...body.messages].reverse().find((item) => item?.role === 'user');
|
|
653
|
-
if (lastUser) {
|
|
654
|
-
if (typeof lastUser.content === 'string') {
|
|
655
|
-
return lastUser.content;
|
|
656
|
-
}
|
|
657
|
-
if (Array.isArray(lastUser.content)) {
|
|
658
|
-
return lastUser.content
|
|
659
|
-
.filter((part) => part && typeof part === 'object' && typeof part.text === 'string')
|
|
660
|
-
.map((part) => part.text)
|
|
661
|
-
.join('\n');
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return '';
|
|
666
|
-
}
|
|
667
447
|
async listModels(_auth) {
|
|
668
448
|
const models = [];
|
|
669
449
|
const seenModelIds = new Set();
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
seenModelIds.add(modelId);
|
|
677
|
-
models.push(model);
|
|
450
|
+
if (_auth) {
|
|
451
|
+
try {
|
|
452
|
+
const context = this.createStoreContext(_auth);
|
|
453
|
+
const userModels = await this.store.listAvailableModels(context);
|
|
454
|
+
this.pushModelsWithDedup(models, seenModelIds, userModels);
|
|
678
455
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
456
|
+
catch (error) {
|
|
457
|
+
this.logger.warn(`Failed to load user Pod models: ${error}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const aiGatewayModels = await this.aiGatewayTransport.listModels();
|
|
461
|
+
if (aiGatewayModels) {
|
|
462
|
+
this.pushModelsWithDedup(models, seenModelIds, aiGatewayModels);
|
|
683
463
|
}
|
|
684
464
|
// 平台 Provider 模型(从 DEFAULT_API_BASE 获取)
|
|
685
465
|
const platformBase = (0, platform_ai_config_1.getPlatformApiBaseUrl)();
|
|
686
466
|
const platformKey = (0, platform_ai_config_1.getPlatformApiKey)();
|
|
687
467
|
const aiGatewayBase = this.getAiGatewayBaseUrl();
|
|
688
468
|
const normalizedAiGatewayModelsUrl = aiGatewayBase
|
|
689
|
-
? this.
|
|
469
|
+
? this.aiGatewayTransport.buildUrl('/v1/models')
|
|
690
470
|
: undefined;
|
|
691
471
|
const normalizedPlatformModelsUrl = platformBase
|
|
692
472
|
? `${platformBase.replace(/\/$/, '')}/models`
|
|
@@ -702,7 +482,7 @@ class VercelChatService {
|
|
|
702
482
|
if (resp.ok) {
|
|
703
483
|
const data = await resp.json();
|
|
704
484
|
if (Array.isArray(data.data)) {
|
|
705
|
-
|
|
485
|
+
this.pushModelsWithDedup(models, seenModelIds, data.data);
|
|
706
486
|
}
|
|
707
487
|
}
|
|
708
488
|
else {
|
|
@@ -713,7 +493,6 @@ class VercelChatService {
|
|
|
713
493
|
this.logger.warn(`Failed to fetch platform models: ${error}`);
|
|
714
494
|
}
|
|
715
495
|
}
|
|
716
|
-
// TODO: 合并用户 Pod Providers 的模型
|
|
717
496
|
return models;
|
|
718
497
|
}
|
|
719
498
|
mapFinishReason(reason) {
|
|
@@ -815,5 +594,4 @@ class VercelChatService {
|
|
|
815
594
|
}
|
|
816
595
|
}
|
|
817
596
|
exports.VercelChatService = VercelChatService;
|
|
818
|
-
VercelChatService.AI_GATEWAY_MODEL_CACHE_TTL_MS = 30_000;
|
|
819
597
|
//# sourceMappingURL=VercelChatService.js.map
|