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.
Files changed (108) hide show
  1. package/README.md +51 -12
  2. package/dist/client/185.c47663fefaeb0e5b.js +10 -0
  3. package/dist/client/562.9012cfd1fa04303d.js +10 -0
  4. package/dist/client/685.b5b1e0a5b825d253.js +10 -0
  5. package/dist/client/index.js +1 -1
  6. package/dist/client-v2/185.b552dc91ec2371ba.js +10 -0
  7. package/dist/client-v2/562.db2984167250b1be.js +10 -0
  8. package/dist/client-v2/685.cf16e5b829e06f85.js +10 -0
  9. package/dist/client-v2/index.js +1 -1
  10. package/dist/externalVersion.js +8 -8
  11. package/dist/locale/en-US.json +175 -139
  12. package/dist/locale/vi-VN.json +40 -2
  13. package/dist/locale/zh-CN.json +40 -2
  14. package/dist/server/collections/ai-api-model-metadata.js +26 -0
  15. package/dist/server/collections/ai-api-response-records.js +101 -0
  16. package/dist/server/collections/ai-api-virtual-models.js +68 -0
  17. package/dist/server/middleware/response-record-resource.js +66 -0
  18. package/dist/server/middleware/role-permission.js +43 -18
  19. package/dist/server/migrations/20260901000000-remove-default-group-members.js +60 -0
  20. package/dist/server/migrations/20260902000000-seed-default-role-permissions.js +55 -0
  21. package/dist/server/migrations/20260903000000-seed-sample-response-records.js +170 -0
  22. package/dist/server/plugin.js +66 -16
  23. package/dist/server/routes/chat-completions.js +38 -6
  24. package/dist/server/routes/completions.js +16 -4
  25. package/dist/server/routes/embeddings.js +25 -6
  26. package/dist/server/routes/models.js +29 -0
  27. package/dist/server/routes/responses.js +530 -0
  28. package/dist/server/routes/router.js +65 -10
  29. package/dist/server/usage.js +25 -4
  30. package/dist/server/utils/direct-llm-context.js +1 -1
  31. package/dist/server/utils/resolve-service.js +24 -0
  32. package/dist/server/utils/response-store.js +138 -0
  33. package/dist/server/utils/responses-format.js +686 -0
  34. package/dist/server/utils/responses-stream.js +330 -0
  35. package/dist/server/utils/virtual-models.js +238 -0
  36. package/dist/server/validation.js +44 -2
  37. package/dist/swagger.js +137 -0
  38. package/package.json +34 -32
  39. package/src/__tests__/locale.test.ts +43 -0
  40. package/src/client/__tests__/settings-registration.test.tsx +1 -0
  41. package/src/client/plugin.tsx +9 -1
  42. package/src/client-v2/__tests__/settings-registration.test.tsx +1 -0
  43. package/src/client-v2/pages/ModelMetadataPage.tsx +44 -0
  44. package/src/client-v2/pages/ModelRoutingPage.tsx +238 -0
  45. package/src/client-v2/pages/UsageGroupsPage.tsx +75 -38
  46. package/src/client-v2/plugin.tsx +8 -0
  47. package/src/locale/en-US.json +175 -139
  48. package/src/locale/vi-VN.json +40 -2
  49. package/src/locale/zh-CN.json +40 -2
  50. package/src/server/__tests__/embeddings.test.ts +184 -0
  51. package/src/server/__tests__/models.test.ts +21 -1
  52. package/src/server/__tests__/response-record-resource.test.ts +50 -0
  53. package/src/server/__tests__/response-store-integration.test.ts +341 -0
  54. package/src/server/__tests__/response-store.test.ts +195 -0
  55. package/src/server/__tests__/responses-contract.test.ts +469 -0
  56. package/src/server/__tests__/responses-format.test.ts +299 -0
  57. package/src/server/__tests__/responses-router.test.ts +182 -0
  58. package/src/server/__tests__/responses-streaming.test.ts +368 -0
  59. package/src/server/__tests__/responses.test.ts +462 -0
  60. package/src/server/__tests__/role-permission.test.ts +139 -0
  61. package/src/server/__tests__/seed-role-permission.test.ts +88 -0
  62. package/src/server/__tests__/types/responses-sdk.types.test-d.ts +23 -0
  63. package/src/server/__tests__/usage-groups.test.ts +96 -0
  64. package/src/server/__tests__/usage-route.test.ts +1 -0
  65. package/src/server/__tests__/usage.test.ts +14 -0
  66. package/src/server/__tests__/validation.test.ts +66 -7
  67. package/src/server/__tests__/virtual-model-routing.test.ts +589 -0
  68. package/src/server/collections/ai-api-model-metadata.ts +26 -0
  69. package/src/server/collections/ai-api-response-records.ts +77 -0
  70. package/src/server/collections/ai-api-virtual-models.ts +58 -0
  71. package/src/server/middleware/response-record-resource.ts +44 -0
  72. package/src/server/middleware/role-permission.ts +69 -35
  73. package/src/server/migrations/20260901000000-remove-default-group-members.ts +56 -0
  74. package/src/server/migrations/20260902000000-seed-default-role-permissions.ts +46 -0
  75. package/src/server/migrations/20260903000000-seed-sample-response-records.ts +162 -0
  76. package/src/server/plugin.ts +84 -20
  77. package/src/server/resource/ai-api-config.ts +2 -1
  78. package/src/server/routes/agent-completions.ts +3 -0
  79. package/src/server/routes/chat-completions.ts +34 -10
  80. package/src/server/routes/completions.ts +16 -4
  81. package/src/server/routes/embeddings.ts +32 -10
  82. package/src/server/routes/models.ts +34 -0
  83. package/src/server/routes/responses.ts +640 -0
  84. package/src/server/routes/router.ts +81 -12
  85. package/src/server/services/__tests__/file-processor.test.ts +1 -0
  86. package/src/server/usage.ts +29 -2
  87. package/src/server/utils/app-observability.ts +1 -1
  88. package/src/server/utils/direct-llm-context.ts +2 -1
  89. package/src/server/utils/openai-format.ts +1 -0
  90. package/src/server/utils/resolve-service.ts +39 -1
  91. package/src/server/utils/response-store.ts +148 -0
  92. package/src/server/utils/responses-format.ts +974 -0
  93. package/src/server/utils/responses-stream.ts +384 -0
  94. package/src/server/utils/virtual-models.ts +320 -0
  95. package/src/server/validation.ts +49 -0
  96. package/src/swagger.ts +139 -0
  97. package/dist/client/562.44b16aad4718b4c7.js +0 -10
  98. package/dist/client/685.ae483e17b6b49c98.js +0 -10
  99. package/dist/client-v2/562.45d5c504433be38b.js +0 -10
  100. package/dist/client-v2/685.1030370b309b7d4b.js +0 -10
  101. package/dist/server/collections/ai-api-user-permissions.js +0 -67
  102. package/dist/server/collections/ai-api-user-quota-buckets.js +0 -54
  103. package/dist/server/collections/ai-api-user-quota-policies.js +0 -63
  104. package/dist/server/resource/ai-api-usage-groups.js +0 -168
  105. package/src/server/collections/ai-api-user-permissions.ts +0 -46
  106. package/src/server/collections/ai-api-user-quota-buckets.ts +0 -24
  107. package/src/server/collections/ai-api-user-quota-policies.ts +0 -33
  108. 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 resolved = await (0, import_resolve_service.resolveModelString)(ctx, body.model);
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
- body.model,
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
- body.model,
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(["model", "messages", "prompt", "tools", "tool_choice", "stream", "n"]);
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 resolved = await (0, import_resolve_service.resolveModelString)(ctx, body.model);
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.max_tokens !== void 0) modelOptions.maxTokens = body.max_tokens;
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
- body.model,
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
- body.model,
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 resolved = await (0, import_resolve_service.resolveModelString)(ctx, body.model);
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: body.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
- ctx.status = 500;
171
- ctx.body = (0, import_openai_format.toOpenAIError)(500, err.message || "Failed to generate embeddings", "server_error");
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
  });