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
@@ -1,168 +0,0 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
- var __defProp = Object.defineProperty;
11
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
- var __getOwnPropNames = Object.getOwnPropertyNames;
13
- var __hasOwnProp = Object.prototype.hasOwnProperty;
14
- var __export = (target, all) => {
15
- for (var name in all)
16
- __defProp(target, name, { get: all[name], enumerable: true });
17
- };
18
- var __copyProps = (to, from, except, desc) => {
19
- if (from && typeof from === "object" || typeof from === "function") {
20
- for (let key of __getOwnPropNames(from))
21
- if (!__hasOwnProp.call(to, key) && key !== except)
22
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
- }
24
- return to;
25
- };
26
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
- var ai_api_usage_groups_exports = {};
28
- __export(ai_api_usage_groups_exports, {
29
- default: () => ai_api_usage_groups_default
30
- });
31
- module.exports = __toCommonJS(ai_api_usage_groups_exports);
32
- const USER_SUMMARY_FIELDS = ["id", "username", "nickname", "email"];
33
- async function getByUser(ctx, next) {
34
- const { userId } = ctx.action.params;
35
- if (!userId) {
36
- ctx.throw(400, "userId is required");
37
- }
38
- const member = await ctx.db.getRepository("aiApiGroupMembers").findOne({
39
- filter: { userId },
40
- appends: ["group"]
41
- });
42
- if (member && member.get("group")) {
43
- ctx.body = member.get("group");
44
- return next();
45
- }
46
- const defaultGroup = await ctx.db.getRepository("aiApiUsageGroups").findOne({
47
- filter: { isDefault: true }
48
- });
49
- if (!defaultGroup) {
50
- ctx.throw(404, "Default group not found");
51
- }
52
- ctx.body = defaultGroup;
53
- return next();
54
- }
55
- async function listUnassignedUsers(ctx, next) {
56
- const { page = 1, pageSize = 20, keyword } = ctx.action.params;
57
- const memberRows = await ctx.db.getRepository("aiApiGroupMembers").find({
58
- fields: ["userId"],
59
- pageSize: 1e4
60
- });
61
- const assignedUserIds = memberRows.map((row) => {
62
- var _a;
63
- const value = ((_a = row.get) == null ? void 0 : _a.call(row, "userId")) ?? row.userId;
64
- return String(value);
65
- });
66
- const where = {};
67
- if (keyword) {
68
- where.$or = [
69
- { username: { $includes: keyword } },
70
- { email: { $includes: keyword } },
71
- { nickname: { $includes: keyword } }
72
- ];
73
- }
74
- if (assignedUserIds.length > 0) {
75
- where.id = { $notIn: assignedUserIds };
76
- }
77
- const repository = ctx.db.getRepository("users");
78
- const [rows, count] = await repository.findAndCount({
79
- filter: where,
80
- fields: USER_SUMMARY_FIELDS,
81
- page: Number(page),
82
- pageSize: Number(pageSize)
83
- });
84
- ctx.body = { rows, count };
85
- return next();
86
- }
87
- async function searchUsers(ctx, next) {
88
- const { keyword, pageSize = 20 } = ctx.action.params;
89
- const where = {};
90
- if (keyword) {
91
- where.$or = [
92
- { username: { $includes: keyword } },
93
- { email: { $includes: keyword } },
94
- { nickname: { $includes: keyword } }
95
- ];
96
- }
97
- const [rows, count] = await ctx.db.getRepository("users").findAndCount({
98
- filter: where,
99
- fields: USER_SUMMARY_FIELDS,
100
- page: 1,
101
- pageSize: Number(pageSize)
102
- });
103
- ctx.body = { rows, count };
104
- return next();
105
- }
106
- async function addMember(ctx, next) {
107
- const { groupId, userId } = ctx.action.params.values || ctx.action.params || {};
108
- if (!groupId || !userId) {
109
- ctx.throw(400, "groupId and userId are required");
110
- }
111
- const group = await ctx.db.getRepository("aiApiUsageGroups").findOne({
112
- filterByTk: groupId
113
- });
114
- if (!group) {
115
- ctx.throw(404, "Usage group not found");
116
- }
117
- if (group.get("isDefault")) {
118
- ctx.throw(400, "Cannot add members to the default group: users without another group use it automatically");
119
- }
120
- const existing = await ctx.db.getRepository("aiApiGroupMembers").findOne({
121
- filter: { userId }
122
- });
123
- if (existing) {
124
- ctx.throw(409, "User already belongs to a group");
125
- }
126
- const member = await ctx.db.getRepository("aiApiGroupMembers").create({
127
- values: { groupId, userId }
128
- });
129
- ctx.body = member;
130
- return next();
131
- }
132
- async function removeMember(ctx, next) {
133
- const { groupId, userId } = ctx.action.params.values || ctx.action.params || {};
134
- if (!groupId || !userId) {
135
- ctx.throw(400, "groupId and userId are required");
136
- }
137
- const member = await ctx.db.getRepository("aiApiGroupMembers").findOne({
138
- filter: { groupId, userId }
139
- });
140
- if (!member) {
141
- ctx.throw(404, "Member not found");
142
- }
143
- await ctx.db.getRepository("aiApiGroupMembers").destroy({
144
- filterByTk: member.get("id")
145
- });
146
- ctx.body = member;
147
- return next();
148
- }
149
- var ai_api_usage_groups_default = {
150
- name: "aiApiUsageGroups",
151
- actions: {
152
- getByUser: {
153
- handler: getByUser
154
- },
155
- listUnassignedUsers: {
156
- handler: listUnassignedUsers
157
- },
158
- searchUsers: {
159
- handler: searchUsers
160
- },
161
- addMember: {
162
- handler: addMember
163
- },
164
- removeMember: {
165
- handler: removeMember
166
- }
167
- }
168
- };
@@ -1,46 +0,0 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
- import { defineCollection } from '@nocobase/database';
11
-
12
- export default defineCollection({
13
- name: 'aiApiUserPermissions',
14
- autoGenId: true,
15
- fields: [
16
- { name: 'userId', type: 'bigInt', allowNull: false, index: true },
17
- {
18
- name: 'user',
19
- type: 'belongsTo',
20
- target: 'users',
21
- targetKey: 'id',
22
- foreignKey: 'userId',
23
- constraints: false,
24
- },
25
- { name: 'enabled', type: 'boolean', defaultValue: true, index: true },
26
- {
27
- name: 'allowedLlmServices',
28
- type: 'json',
29
- defaultValue: [],
30
- comment: 'LLM service names/titles this user may use. Empty means the user is denied every service.',
31
- },
32
- { name: 'allowAllModels', type: 'boolean', defaultValue: true },
33
- {
34
- name: 'allowedModels',
35
- type: 'json',
36
- defaultValue: [],
37
- comment: 'Array of "serviceName/modelId" this user may use (when allowAllModels=false)',
38
- },
39
- ],
40
- indexes: [
41
- {
42
- fields: ['userId'],
43
- unique: true,
44
- },
45
- ],
46
- });
@@ -1,24 +0,0 @@
1
- import { defineCollection } from '@nocobase/database';
2
-
3
- export default defineCollection({
4
- name: 'aiApiUserQuotaBuckets',
5
- autoGenId: true,
6
- fields: [
7
- { name: 'policyId', type: 'bigInt', allowNull: false, index: true },
8
- { name: 'userId', type: 'bigInt', allowNull: false, index: true },
9
- { name: 'periodStart', type: 'datetimeTz', allowNull: false, index: true },
10
- { name: 'periodEnd', type: 'datetimeTz', allowNull: false, index: true },
11
- { name: 'requestCount', type: 'bigInt', allowNull: false, defaultValue: 0 },
12
- { name: 'totalTokens', type: 'bigInt', allowNull: false, defaultValue: 0 },
13
- { name: 'cost', type: 'decimal', precision: 20, scale: 8, allowNull: false, defaultValue: 0 },
14
- { name: 'reservedRequests', type: 'bigInt', allowNull: false, defaultValue: 0 },
15
- { name: 'reservedTokens', type: 'bigInt', allowNull: false, defaultValue: 0 },
16
- { name: 'reservedCost', type: 'decimal', precision: 20, scale: 8, allowNull: false, defaultValue: 0 },
17
- ],
18
- indexes: [
19
- {
20
- fields: ['policyId', 'periodStart'],
21
- unique: true,
22
- },
23
- ],
24
- });
@@ -1,33 +0,0 @@
1
- import { defineCollection } from '@nocobase/database';
2
-
3
- export default defineCollection({
4
- name: 'aiApiUserQuotaPolicies',
5
- autoGenId: true,
6
- fields: [
7
- { name: 'userId', type: 'bigInt', allowNull: false, index: true },
8
- {
9
- name: 'user',
10
- type: 'belongsTo',
11
- target: 'users',
12
- targetKey: 'id',
13
- foreignKey: 'userId',
14
- constraints: false,
15
- },
16
- { name: 'enabled', type: 'boolean', defaultValue: true, index: true },
17
- { name: 'periodType', type: 'string', allowNull: false, defaultValue: 'monthly' },
18
- { name: 'timezone', type: 'string', allowNull: false, defaultValue: 'UTC' },
19
- { name: 'requestLimit', type: 'bigInt', allowNull: true },
20
- { name: 'totalTokenLimit', type: 'bigInt', allowNull: true },
21
- { name: 'costLimit', type: 'decimal', precision: 20, scale: 8, allowNull: true },
22
- { name: 'currency', type: 'string', allowNull: false, defaultValue: 'USD' },
23
- { name: 'rejectUnpricedModel', type: 'boolean', defaultValue: true },
24
- { name: 'missingUsageBehavior', type: 'string', allowNull: false, defaultValue: 'use_reserved' },
25
- { name: 'contextOverflowBehavior', type: 'string', allowNull: false, defaultValue: 'reject' },
26
- ],
27
- indexes: [
28
- {
29
- fields: ['userId'],
30
- unique: true,
31
- },
32
- ],
33
- });
@@ -1,171 +0,0 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
- import { Context } from '@nocobase/actions';
11
-
12
- const USER_SUMMARY_FIELDS = ['id', 'username', 'nickname', 'email'];
13
-
14
- async function getByUser(ctx: Context, next: () => Promise<void>) {
15
- const { userId } = ctx.action.params;
16
- if (!userId) {
17
- ctx.throw(400, 'userId is required');
18
- }
19
- const member = await ctx.db.getRepository('aiApiGroupMembers').findOne({
20
- filter: { userId },
21
- appends: ['group'],
22
- });
23
-
24
- if (member && member.get('group')) {
25
- ctx.body = member.get('group');
26
- return next();
27
- }
28
-
29
- const defaultGroup = await ctx.db.getRepository('aiApiUsageGroups').findOne({
30
- filter: { isDefault: true },
31
- });
32
-
33
- if (!defaultGroup) {
34
- ctx.throw(404, 'Default group not found');
35
- }
36
-
37
- ctx.body = defaultGroup;
38
- return next();
39
- }
40
-
41
- async function listUnassignedUsers(ctx: Context, next: () => Promise<void>) {
42
- const { page = 1, pageSize = 20, keyword } = ctx.action.params;
43
-
44
- const memberRows = (await ctx.db.getRepository('aiApiGroupMembers').find({
45
- fields: ['userId'],
46
- pageSize: 10000,
47
- })) as unknown[];
48
- const assignedUserIds = memberRows.map((row) => {
49
- const value = (row as Record<string, unknown>).get?.('userId') ?? (row as Record<string, unknown>).userId;
50
- return String(value);
51
- });
52
-
53
- const where: Record<string, unknown> = {};
54
- if (keyword) {
55
- where.$or = [
56
- { username: { $includes: keyword } },
57
- { email: { $includes: keyword } },
58
- { nickname: { $includes: keyword } },
59
- ];
60
- }
61
- if (assignedUserIds.length > 0) {
62
- where.id = { $notIn: assignedUserIds };
63
- }
64
-
65
- const repository = ctx.db.getRepository('users');
66
- const [rows, count] = await repository.findAndCount({
67
- filter: where,
68
- fields: USER_SUMMARY_FIELDS,
69
- page: Number(page),
70
- pageSize: Number(pageSize),
71
- });
72
-
73
- ctx.body = { rows, count };
74
- return next();
75
- }
76
-
77
- async function searchUsers(ctx: Context, next: () => Promise<void>) {
78
- const { keyword, pageSize = 20 } = ctx.action.params;
79
-
80
- const where: Record<string, unknown> = {};
81
- if (keyword) {
82
- where.$or = [
83
- { username: { $includes: keyword } },
84
- { email: { $includes: keyword } },
85
- { nickname: { $includes: keyword } },
86
- ];
87
- }
88
-
89
- const [rows, count] = await ctx.db.getRepository('users').findAndCount({
90
- filter: where,
91
- fields: USER_SUMMARY_FIELDS,
92
- page: 1,
93
- pageSize: Number(pageSize),
94
- });
95
-
96
- ctx.body = { rows, count };
97
- return next();
98
- }
99
-
100
- async function addMember(ctx: Context, next: () => Promise<void>) {
101
- const { groupId, userId } = ctx.action.params.values || ctx.action.params || {};
102
- if (!groupId || !userId) {
103
- ctx.throw(400, 'groupId and userId are required');
104
- }
105
-
106
- const group = await ctx.db.getRepository('aiApiUsageGroups').findOne({
107
- filterByTk: groupId,
108
- });
109
- if (!group) {
110
- ctx.throw(404, 'Usage group not found');
111
- }
112
- // The default group is a fallback, not a membership target: an explicit row would
113
- // pin the user there and hide them from listUnassignedUsers forever.
114
- if (group.get('isDefault')) {
115
- ctx.throw(400, 'Cannot add members to the default group: users without another group use it automatically');
116
- }
117
-
118
- const existing = await ctx.db.getRepository('aiApiGroupMembers').findOne({
119
- filter: { userId },
120
- });
121
- if (existing) {
122
- ctx.throw(409, 'User already belongs to a group');
123
- }
124
-
125
- const member = await ctx.db.getRepository('aiApiGroupMembers').create({
126
- values: { groupId, userId },
127
- });
128
- ctx.body = member;
129
- return next();
130
- }
131
-
132
- async function removeMember(ctx: Context, next: () => Promise<void>) {
133
- const { groupId, userId } = ctx.action.params.values || ctx.action.params || {};
134
- if (!groupId || !userId) {
135
- ctx.throw(400, 'groupId and userId are required');
136
- }
137
-
138
- const member = await ctx.db.getRepository('aiApiGroupMembers').findOne({
139
- filter: { groupId, userId },
140
- });
141
- if (!member) {
142
- ctx.throw(404, 'Member not found');
143
- }
144
-
145
- await ctx.db.getRepository('aiApiGroupMembers').destroy({
146
- filterByTk: member.get('id'),
147
- });
148
- ctx.body = member;
149
- return next();
150
- }
151
-
152
- export default {
153
- name: 'aiApiUsageGroups',
154
- actions: {
155
- getByUser: {
156
- handler: getByUser,
157
- },
158
- listUnassignedUsers: {
159
- handler: listUnassignedUsers,
160
- },
161
- searchUsers: {
162
- handler: searchUsers,
163
- },
164
- addMember: {
165
- handler: addMember,
166
- },
167
- removeMember: {
168
- handler: removeMember,
169
- },
170
- },
171
- };