@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.
@@ -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 aiGatewayModelCache;
13
- private aiGatewayModelCachePromise;
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 buildAiGatewayUrl;
31
- private createAiGatewayAbortSignal;
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.aiGatewayModelCache = null;
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
- isAiGatewayModelCacheFresh() {
54
- return !!this.aiGatewayModelCache
55
- && Date.now() - this.aiGatewayModelCache.fetchedAt < VercelChatService.AI_GATEWAY_MODEL_CACHE_TTL_MS;
56
- }
57
- async getAiGatewayModelCache() {
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
- this.logger.warn(`Failed to fetch ai-gateway models: ${error}`);
90
- return null;
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
- const response = await this.sendAiGatewayRequest(path, 'POST', body, {
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
- const response = await this.sendAiGatewayRequest(path, 'POST', body, {
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(model)) {
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(model)) {
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 this.shouldUseAiGateway(body?.model)) {
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 result = await this.forwardAiGatewayJson('/v1/responses', body, auth);
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 (!(0, provider_registry_1.supportsResponsesApi)(baseURL)) {
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 response = await fetchFn(url, {
432
- method: 'POST',
433
- headers: {
434
- 'Content-Type': 'application/json',
435
- 'Authorization': `Bearer ${apiKey}`,
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 response.json();
281
+ return result;
453
282
  }
454
283
  catch (error) {
455
- if (credentialId && !(error instanceof Error && error.message.startsWith('Provider error'))) {
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 this.shouldUseAiGateway(body?.model)) {
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 = this.buildChatCompletionsBodyFromMessages(body);
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 = this.mapChatCompletionToMessagesResponse(body, completion);
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 (!(0, provider_registry_1.supportsMessagesApi)(baseURL)) {
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 response = await fetchFn(url, {
493
- method: 'POST',
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 response.json();
342
+ return result;
516
343
  }
517
344
  catch (error) {
518
- if (credentialId && !(error instanceof Error && error.message.startsWith('Provider error'))) {
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 = this.extractPromptFromResponsesBody(body);
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 = this.extractPromptFromMessagesBody(body);
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
- const pushModels = (items) => {
671
- for (const model of items) {
672
- const modelId = this.toModelId(model);
673
- if (seenModelIds.has(modelId)) {
674
- continue;
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
- const aiGatewayCache = await this.getAiGatewayModelCache();
681
- if (aiGatewayCache) {
682
- pushModels(aiGatewayCache.items);
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.buildAiGatewayUrl('/v1/models')
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
- pushModels(data.data);
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