plugin-ai-api 1.1.1 → 1.1.2
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/README.md +51 -12
- package/dist/client/185.c47663fefaeb0e5b.js +10 -0
- package/dist/client/562.9012cfd1fa04303d.js +10 -0
- package/dist/client/685.b5b1e0a5b825d253.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/185.b552dc91ec2371ba.js +10 -0
- package/dist/client-v2/562.db2984167250b1be.js +10 -0
- package/dist/client-v2/685.cf16e5b829e06f85.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +8 -8
- package/dist/locale/en-US.json +175 -139
- package/dist/locale/vi-VN.json +40 -2
- package/dist/locale/zh-CN.json +40 -2
- package/dist/server/collections/ai-api-model-metadata.js +26 -0
- package/dist/server/collections/ai-api-response-records.js +101 -0
- package/dist/server/collections/ai-api-virtual-models.js +68 -0
- package/dist/server/middleware/response-record-resource.js +66 -0
- package/dist/server/middleware/role-permission.js +43 -18
- package/dist/server/migrations/20260901000000-remove-default-group-members.js +60 -0
- package/dist/server/migrations/20260902000000-seed-default-role-permissions.js +55 -0
- package/dist/server/migrations/20260903000000-seed-sample-response-records.js +170 -0
- package/dist/server/plugin.js +66 -16
- package/dist/server/routes/chat-completions.js +38 -6
- package/dist/server/routes/completions.js +16 -4
- package/dist/server/routes/embeddings.js +25 -6
- package/dist/server/routes/models.js +29 -0
- package/dist/server/routes/responses.js +530 -0
- package/dist/server/routes/router.js +65 -10
- package/dist/server/usage.js +25 -4
- package/dist/server/utils/direct-llm-context.js +1 -1
- package/dist/server/utils/resolve-service.js +24 -0
- package/dist/server/utils/response-store.js +138 -0
- package/dist/server/utils/responses-format.js +686 -0
- package/dist/server/utils/responses-stream.js +330 -0
- package/dist/server/utils/virtual-models.js +238 -0
- package/dist/server/validation.js +44 -2
- package/dist/swagger.js +137 -0
- package/package.json +34 -32
- package/src/__tests__/locale.test.ts +43 -0
- package/src/client/__tests__/settings-registration.test.tsx +1 -0
- package/src/client/plugin.tsx +9 -1
- package/src/client-v2/__tests__/settings-registration.test.tsx +1 -0
- package/src/client-v2/pages/ModelMetadataPage.tsx +44 -0
- package/src/client-v2/pages/ModelRoutingPage.tsx +238 -0
- package/src/client-v2/pages/UsageGroupsPage.tsx +75 -38
- package/src/client-v2/plugin.tsx +8 -0
- package/src/locale/en-US.json +175 -139
- package/src/locale/vi-VN.json +40 -2
- package/src/locale/zh-CN.json +40 -2
- package/src/server/__tests__/embeddings.test.ts +184 -0
- package/src/server/__tests__/models.test.ts +21 -1
- package/src/server/__tests__/response-record-resource.test.ts +50 -0
- package/src/server/__tests__/response-store-integration.test.ts +341 -0
- package/src/server/__tests__/response-store.test.ts +195 -0
- package/src/server/__tests__/responses-contract.test.ts +469 -0
- package/src/server/__tests__/responses-format.test.ts +299 -0
- package/src/server/__tests__/responses-router.test.ts +182 -0
- package/src/server/__tests__/responses-streaming.test.ts +368 -0
- package/src/server/__tests__/responses.test.ts +462 -0
- package/src/server/__tests__/role-permission.test.ts +139 -0
- package/src/server/__tests__/seed-role-permission.test.ts +88 -0
- package/src/server/__tests__/types/responses-sdk.types.test-d.ts +23 -0
- package/src/server/__tests__/usage-groups.test.ts +96 -0
- package/src/server/__tests__/usage-route.test.ts +1 -0
- package/src/server/__tests__/usage.test.ts +14 -0
- package/src/server/__tests__/validation.test.ts +66 -7
- package/src/server/__tests__/virtual-model-routing.test.ts +589 -0
- package/src/server/collections/ai-api-model-metadata.ts +26 -0
- package/src/server/collections/ai-api-response-records.ts +77 -0
- package/src/server/collections/ai-api-virtual-models.ts +58 -0
- package/src/server/middleware/response-record-resource.ts +44 -0
- package/src/server/middleware/role-permission.ts +69 -35
- package/src/server/migrations/20260901000000-remove-default-group-members.ts +56 -0
- package/src/server/migrations/20260902000000-seed-default-role-permissions.ts +46 -0
- package/src/server/migrations/20260903000000-seed-sample-response-records.ts +162 -0
- package/src/server/plugin.ts +84 -20
- package/src/server/resource/ai-api-config.ts +2 -1
- package/src/server/routes/agent-completions.ts +3 -0
- package/src/server/routes/chat-completions.ts +34 -10
- package/src/server/routes/completions.ts +16 -4
- package/src/server/routes/embeddings.ts +32 -10
- package/src/server/routes/models.ts +34 -0
- package/src/server/routes/responses.ts +640 -0
- package/src/server/routes/router.ts +81 -12
- package/src/server/services/__tests__/file-processor.test.ts +1 -0
- package/src/server/usage.ts +29 -2
- package/src/server/utils/app-observability.ts +1 -1
- package/src/server/utils/direct-llm-context.ts +2 -1
- package/src/server/utils/openai-format.ts +1 -0
- package/src/server/utils/resolve-service.ts +39 -1
- package/src/server/utils/response-store.ts +148 -0
- package/src/server/utils/responses-format.ts +974 -0
- package/src/server/utils/responses-stream.ts +384 -0
- package/src/server/utils/virtual-models.ts +320 -0
- package/src/server/validation.ts +49 -0
- package/src/swagger.ts +139 -0
- package/dist/client/562.44b16aad4718b4c7.js +0 -10
- package/dist/client/685.ae483e17b6b49c98.js +0 -10
- package/dist/client-v2/562.45d5c504433be38b.js +0 -10
- package/dist/client-v2/685.1030370b309b7d4b.js +0 -10
- package/dist/server/collections/ai-api-user-permissions.js +0 -67
- package/dist/server/collections/ai-api-user-quota-buckets.js +0 -54
- package/dist/server/collections/ai-api-user-quota-policies.js +0 -63
- package/dist/server/resource/ai-api-usage-groups.js +0 -168
- package/src/server/collections/ai-api-user-permissions.ts +0 -46
- package/src/server/collections/ai-api-user-quota-buckets.ts +0 -24
- package/src/server/collections/ai-api-user-quota-policies.ts +0 -33
- package/src/server/resource/ai-api-usage-groups.ts +0 -171
|
@@ -27,16 +27,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
27
27
|
var chat_completions_exports = {};
|
|
28
28
|
__export(chat_completions_exports, {
|
|
29
29
|
applyProviderRequestParameters: () => applyProviderRequestParameters,
|
|
30
|
+
bindRequestTools: () => bindRequestTools,
|
|
30
31
|
extractFinishReason: () => extractFinishReason,
|
|
31
32
|
findContentBlockProblem: () => findContentBlockProblem,
|
|
32
33
|
findMessageProblem: () => findMessageProblem,
|
|
33
34
|
getProviderRequestParameters: () => getProviderRequestParameters,
|
|
34
35
|
handleChatCompletions: () => handleChatCompletions,
|
|
35
|
-
normalizeMessageContent: () => normalizeMessageContent
|
|
36
|
+
normalizeMessageContent: () => normalizeMessageContent,
|
|
37
|
+
normalizeToolCallChunks: () => normalizeToolCallChunks,
|
|
38
|
+
normalizeToolCalls: () => normalizeToolCalls,
|
|
39
|
+
processMessageContentFileBlocks: () => processMessageContentFileBlocks
|
|
36
40
|
});
|
|
37
41
|
module.exports = __toCommonJS(chat_completions_exports);
|
|
38
42
|
var import_openai_format = require("../utils/openai-format");
|
|
39
43
|
var import_resolve_service = require("../utils/resolve-service");
|
|
44
|
+
var import_virtual_models = require("../utils/virtual-models");
|
|
40
45
|
var import_streaming = require("../utils/streaming");
|
|
41
46
|
var import_user_permissions = require("../utils/user-permissions");
|
|
42
47
|
var import_request_cache = require("../utils/request-cache");
|
|
@@ -91,7 +96,16 @@ async function handleChatCompletions(ctx, plugin) {
|
|
|
91
96
|
return;
|
|
92
97
|
}
|
|
93
98
|
const stream = (0, import_streaming.isStreamingRequested)(body.stream);
|
|
94
|
-
const
|
|
99
|
+
const virtual = await (0, import_virtual_models.resolveVirtualModel)(ctx, body.model, body, "chat");
|
|
100
|
+
if ((virtual == null ? void 0 : virtual.status) === "unavailable") {
|
|
101
|
+
(0, import_virtual_models.respondVirtualModelUnavailable)(ctx, virtual);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if ((virtual == null ? void 0 : virtual.status) === "resolved") {
|
|
105
|
+
ctx.state.aiApiVirtualModel = virtual.virtualModel;
|
|
106
|
+
ctx.state.aiApiRoutingReason = virtual.reason;
|
|
107
|
+
}
|
|
108
|
+
const resolved = (virtual == null ? void 0 : virtual.resolved) ?? await (0, import_resolve_service.resolveModelString)(ctx, body.model);
|
|
95
109
|
if (!resolved) {
|
|
96
110
|
ctx.status = 404;
|
|
97
111
|
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
@@ -199,7 +213,7 @@ async function handleChatCompletions(ctx, plugin) {
|
|
|
199
213
|
chatModel,
|
|
200
214
|
langchainMessages,
|
|
201
215
|
completionId,
|
|
202
|
-
|
|
216
|
+
`${service.name}/${modelId}`,
|
|
203
217
|
providerRequestParameters
|
|
204
218
|
);
|
|
205
219
|
} else {
|
|
@@ -208,7 +222,7 @@ async function handleChatCompletions(ctx, plugin) {
|
|
|
208
222
|
chatModel,
|
|
209
223
|
langchainMessages,
|
|
210
224
|
completionId,
|
|
211
|
-
|
|
225
|
+
`${service.name}/${modelId}`,
|
|
212
226
|
providerRequestParameters
|
|
213
227
|
);
|
|
214
228
|
}
|
|
@@ -570,7 +584,21 @@ async function processFileBlockChain(block, ctx, plugin, depth) {
|
|
|
570
584
|
}
|
|
571
585
|
return next;
|
|
572
586
|
}
|
|
573
|
-
const GATEWAY_MANAGED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
587
|
+
const GATEWAY_MANAGED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
588
|
+
"model",
|
|
589
|
+
"messages",
|
|
590
|
+
"prompt",
|
|
591
|
+
"tools",
|
|
592
|
+
"tool_choice",
|
|
593
|
+
"stream",
|
|
594
|
+
"n",
|
|
595
|
+
// Responses API specific fields that should not be passed to providers
|
|
596
|
+
"input",
|
|
597
|
+
"previous_response_id",
|
|
598
|
+
"store",
|
|
599
|
+
"truncation",
|
|
600
|
+
"metadata"
|
|
601
|
+
]);
|
|
574
602
|
function getProviderRequestParameters(body) {
|
|
575
603
|
return Object.fromEntries(
|
|
576
604
|
Object.entries(body).filter(([name, value]) => !GATEWAY_MANAGED_PARAMETERS.has(name) && value !== void 0)
|
|
@@ -640,10 +668,14 @@ function serializeToolArguments(value) {
|
|
|
640
668
|
// Annotate the CommonJS export names for ESM import in node:
|
|
641
669
|
0 && (module.exports = {
|
|
642
670
|
applyProviderRequestParameters,
|
|
671
|
+
bindRequestTools,
|
|
643
672
|
extractFinishReason,
|
|
644
673
|
findContentBlockProblem,
|
|
645
674
|
findMessageProblem,
|
|
646
675
|
getProviderRequestParameters,
|
|
647
676
|
handleChatCompletions,
|
|
648
|
-
normalizeMessageContent
|
|
677
|
+
normalizeMessageContent,
|
|
678
|
+
normalizeToolCallChunks,
|
|
679
|
+
normalizeToolCalls,
|
|
680
|
+
processMessageContentFileBlocks
|
|
649
681
|
});
|
|
@@ -31,6 +31,7 @@ __export(completions_exports, {
|
|
|
31
31
|
module.exports = __toCommonJS(completions_exports);
|
|
32
32
|
var import_openai_format = require("../utils/openai-format");
|
|
33
33
|
var import_resolve_service = require("../utils/resolve-service");
|
|
34
|
+
var import_virtual_models = require("../utils/virtual-models");
|
|
34
35
|
var import_user_permissions = require("../utils/user-permissions");
|
|
35
36
|
var import_streaming = require("../utils/streaming");
|
|
36
37
|
var import_chat_completions = require("./chat-completions");
|
|
@@ -63,7 +64,16 @@ async function handleCompletions(ctx, plugin) {
|
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
const stream = (0, import_streaming.isStreamingRequested)(body.stream);
|
|
66
|
-
const
|
|
67
|
+
const virtual = await (0, import_virtual_models.resolveVirtualModel)(ctx, body.model, body, "chat");
|
|
68
|
+
if ((virtual == null ? void 0 : virtual.status) === "unavailable") {
|
|
69
|
+
(0, import_virtual_models.respondVirtualModelUnavailable)(ctx, virtual);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if ((virtual == null ? void 0 : virtual.status) === "resolved") {
|
|
73
|
+
ctx.state.aiApiVirtualModel = virtual.virtualModel;
|
|
74
|
+
ctx.state.aiApiRoutingReason = virtual.reason;
|
|
75
|
+
}
|
|
76
|
+
const resolved = (virtual == null ? void 0 : virtual.resolved) ?? await (0, import_resolve_service.resolveModelString)(ctx, body.model);
|
|
67
77
|
if (!resolved) {
|
|
68
78
|
ctx.status = 404;
|
|
69
79
|
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
@@ -108,7 +118,8 @@ async function handleCompletions(ctx, plugin) {
|
|
|
108
118
|
};
|
|
109
119
|
if (body.temperature !== void 0) modelOptions.temperature = body.temperature;
|
|
110
120
|
if (body.top_p !== void 0) modelOptions.topP = body.top_p;
|
|
111
|
-
if (body.
|
|
121
|
+
if (body.max_completion_tokens !== void 0) modelOptions.maxTokens = body.max_completion_tokens;
|
|
122
|
+
else if (body.max_tokens !== void 0) modelOptions.maxTokens = body.max_tokens;
|
|
112
123
|
if (body.stop !== void 0) modelOptions.stop = body.stop;
|
|
113
124
|
const prompt = typeof body.prompt === "string" ? body.prompt : Array.isArray(body.prompt) ? body.prompt.join("\n") : String(body.prompt);
|
|
114
125
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -116,6 +127,7 @@ async function handleCompletions(ctx, plugin) {
|
|
|
116
127
|
serviceName: service.name,
|
|
117
128
|
modelId,
|
|
118
129
|
messages,
|
|
130
|
+
maxCompletionTokens: body.max_completion_tokens,
|
|
119
131
|
maxTokens: body.max_tokens
|
|
120
132
|
});
|
|
121
133
|
await (0, import_billing.prepareLlmBilling)(ctx, resolved);
|
|
@@ -140,7 +152,7 @@ async function handleCompletions(ctx, plugin) {
|
|
|
140
152
|
chatModel,
|
|
141
153
|
langchainMessages,
|
|
142
154
|
completionId,
|
|
143
|
-
|
|
155
|
+
`${service.name}/${modelId}`,
|
|
144
156
|
body.stream_options,
|
|
145
157
|
providerRequestParameters
|
|
146
158
|
);
|
|
@@ -150,7 +162,7 @@ async function handleCompletions(ctx, plugin) {
|
|
|
150
162
|
chatModel,
|
|
151
163
|
langchainMessages,
|
|
152
164
|
completionId,
|
|
153
|
-
|
|
165
|
+
`${service.name}/${modelId}`,
|
|
154
166
|
providerRequestParameters
|
|
155
167
|
);
|
|
156
168
|
}
|
|
@@ -31,6 +31,7 @@ __export(embeddings_exports, {
|
|
|
31
31
|
module.exports = __toCommonJS(embeddings_exports);
|
|
32
32
|
var import_openai_format = require("../utils/openai-format");
|
|
33
33
|
var import_resolve_service = require("../utils/resolve-service");
|
|
34
|
+
var import_virtual_models = require("../utils/virtual-models");
|
|
34
35
|
var import_user_permissions = require("../utils/user-permissions");
|
|
35
36
|
var import_request_cache = require("../utils/request-cache");
|
|
36
37
|
var import_usage = require("../usage");
|
|
@@ -82,7 +83,16 @@ async function handleEmbeddings(ctx, plugin) {
|
|
|
82
83
|
ctx.body = (0, import_openai_format.toOpenAIError)(400, "'input' must be a string or array of strings", "invalid_request_error");
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
85
|
-
const
|
|
86
|
+
const virtual = await (0, import_virtual_models.resolveVirtualModel)(ctx, body.model, body, "embedding");
|
|
87
|
+
if ((virtual == null ? void 0 : virtual.status) === "unavailable") {
|
|
88
|
+
(0, import_virtual_models.respondVirtualModelUnavailable)(ctx, virtual);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if ((virtual == null ? void 0 : virtual.status) === "resolved") {
|
|
92
|
+
ctx.state.aiApiVirtualModel = virtual.virtualModel;
|
|
93
|
+
ctx.state.aiApiRoutingReason = virtual.reason;
|
|
94
|
+
}
|
|
95
|
+
const resolved = (virtual == null ? void 0 : virtual.resolved) ?? await (0, import_resolve_service.resolveModelString)(ctx, body.model);
|
|
86
96
|
if (!resolved) {
|
|
87
97
|
ctx.status = 404;
|
|
88
98
|
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
@@ -94,7 +104,6 @@ async function handleEmbeddings(ctx, plugin) {
|
|
|
94
104
|
return;
|
|
95
105
|
}
|
|
96
106
|
const { service, modelId } = resolved;
|
|
97
|
-
await (0, import_billing.prepareLlmBilling)(ctx, resolved);
|
|
98
107
|
if (service.enabled === false) {
|
|
99
108
|
ctx.status = 404;
|
|
100
109
|
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
@@ -137,6 +146,7 @@ async function handleEmbeddings(ctx, plugin) {
|
|
|
137
146
|
return;
|
|
138
147
|
}
|
|
139
148
|
try {
|
|
149
|
+
await (0, import_billing.prepareLlmBilling)(ctx, resolved);
|
|
140
150
|
const EmbeddingClass = providerMeta.embedding;
|
|
141
151
|
const embeddingProvider = new EmbeddingClass({
|
|
142
152
|
app: ctx.app,
|
|
@@ -155,11 +165,10 @@ async function handleEmbeddings(ctx, plugin) {
|
|
|
155
165
|
total_tokens: estimatedInputTokens
|
|
156
166
|
};
|
|
157
167
|
(0, import_usage.setAiApiUsageResult)(ctx, usage);
|
|
158
|
-
await (0, import_billing.finalizeLlmBilling)(ctx, usage, true);
|
|
159
168
|
ctx.status = 200;
|
|
160
169
|
ctx.set("Content-Type", "application/json");
|
|
161
170
|
ctx.body = (0, import_openai_format.toOpenAIEmbeddingsResponse)({
|
|
162
|
-
model:
|
|
171
|
+
model: `${service.name}/${modelId}`,
|
|
163
172
|
embeddings: vectors,
|
|
164
173
|
// LangChain's EmbeddingsInterface does not expose token counts.
|
|
165
174
|
promptTokens: null
|
|
@@ -167,11 +176,21 @@ async function handleEmbeddings(ctx, plugin) {
|
|
|
167
176
|
} catch (err) {
|
|
168
177
|
ctx.log.error("AI API embeddings error:", err);
|
|
169
178
|
if (!ctx.res.headersSent) {
|
|
170
|
-
|
|
171
|
-
ctx.
|
|
179
|
+
const isQuotaError = err instanceof import_billing.AiApiQuotaError;
|
|
180
|
+
ctx.status = isQuotaError ? 429 : 500;
|
|
181
|
+
if (isQuotaError) ctx.set("X-RateLimit-Reason", err.code);
|
|
182
|
+
ctx.body = (0, import_openai_format.toOpenAIError)(
|
|
183
|
+
ctx.status,
|
|
184
|
+
getErrorMessage(err, "Failed to generate embeddings"),
|
|
185
|
+
isQuotaError ? "quota_error" : "server_error",
|
|
186
|
+
isQuotaError ? err.code : void 0
|
|
187
|
+
);
|
|
172
188
|
}
|
|
173
189
|
}
|
|
174
190
|
}
|
|
191
|
+
function getErrorMessage(error, fallback) {
|
|
192
|
+
return error instanceof Error && error.message ? error.message : fallback;
|
|
193
|
+
}
|
|
175
194
|
// Annotate the CommonJS export names for ESM import in node:
|
|
176
195
|
0 && (module.exports = {
|
|
177
196
|
handleEmbeddings
|
|
@@ -27,6 +27,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
27
27
|
var models_exports = {};
|
|
28
28
|
__export(models_exports, {
|
|
29
29
|
buildModelObject: () => buildModelObject,
|
|
30
|
+
buildVirtualModelObject: () => buildVirtualModelObject,
|
|
30
31
|
handleGetModel: () => handleGetModel,
|
|
31
32
|
handleListModels: () => handleListModels
|
|
32
33
|
});
|
|
@@ -34,6 +35,7 @@ module.exports = __toCommonJS(models_exports);
|
|
|
34
35
|
var import_openai_format = require("../utils/openai-format");
|
|
35
36
|
var import_user_permissions = require("../utils/user-permissions");
|
|
36
37
|
var import_request_cache = require("../utils/request-cache");
|
|
38
|
+
var import_virtual_models = require("../utils/virtual-models");
|
|
37
39
|
async function handleListModels(ctx, plugin) {
|
|
38
40
|
var _a;
|
|
39
41
|
try {
|
|
@@ -73,6 +75,13 @@ async function handleListModels(ctx, plugin) {
|
|
|
73
75
|
models.push(buildModelObject(fullId, now, serviceLabel, meta));
|
|
74
76
|
}
|
|
75
77
|
}
|
|
78
|
+
try {
|
|
79
|
+
const virtualModels = await (0, import_virtual_models.listAccessibleVirtualModels)(ctx, scope, config == null ? void 0 : config.enabledLlmServices);
|
|
80
|
+
for (const virtualModel of virtualModels) {
|
|
81
|
+
models.push(buildVirtualModelObject(virtualModel, now));
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
76
85
|
ctx.status = 200;
|
|
77
86
|
ctx.body = {
|
|
78
87
|
object: "list",
|
|
@@ -121,6 +130,14 @@ async function handleGetModel(ctx, modelId, plugin) {
|
|
|
121
130
|
}
|
|
122
131
|
if (found) break;
|
|
123
132
|
}
|
|
133
|
+
if (!found) {
|
|
134
|
+
try {
|
|
135
|
+
const virtualModels = await (0, import_virtual_models.listAccessibleVirtualModels)(ctx, scope, config == null ? void 0 : config.enabledLlmServices);
|
|
136
|
+
const virtualModel = virtualModels.find((candidate) => candidate.name === modelId);
|
|
137
|
+
if (virtualModel) found = buildVirtualModelObject(virtualModel, now);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
124
141
|
if (!found) {
|
|
125
142
|
ctx.status = 404;
|
|
126
143
|
ctx.body = (0, import_openai_format.toOpenAIError)(404, `Model '${modelId}' not found`, "invalid_request_error", "model_not_found");
|
|
@@ -194,6 +211,17 @@ function buildModelObject(fullId, created, serviceLabel, meta) {
|
|
|
194
211
|
}
|
|
195
212
|
return model;
|
|
196
213
|
}
|
|
214
|
+
function buildVirtualModelObject(virtualModel, created) {
|
|
215
|
+
return {
|
|
216
|
+
id: virtualModel.name,
|
|
217
|
+
object: "model",
|
|
218
|
+
created,
|
|
219
|
+
owned_by: "ai-api-gateway",
|
|
220
|
+
virtual: true,
|
|
221
|
+
mode: virtualModel.mode,
|
|
222
|
+
description: `Virtual ${virtualModel.mode} alias routed to a concrete model based on the request shape.`
|
|
223
|
+
};
|
|
224
|
+
}
|
|
197
225
|
function toPositiveInt(value) {
|
|
198
226
|
const n = Number(value);
|
|
199
227
|
return Number.isSafeInteger(n) && n > 0 ? n : null;
|
|
@@ -235,6 +263,7 @@ function getRecommendedModelsForProvider(provider) {
|
|
|
235
263
|
// Annotate the CommonJS export names for ESM import in node:
|
|
236
264
|
0 && (module.exports = {
|
|
237
265
|
buildModelObject,
|
|
266
|
+
buildVirtualModelObject,
|
|
238
267
|
handleGetModel,
|
|
239
268
|
handleListModels
|
|
240
269
|
});
|