plugin-ai-api 1.0.0

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 (49) hide show
  1. package/README.md +1 -0
  2. package/client.d.ts +2 -0
  3. package/client.js +1 -0
  4. package/dist/client/824c2f7487ee05fd.js +1 -0
  5. package/dist/client/AiApiConfigPage.d.ts +3 -0
  6. package/dist/client/index.d.ts +1 -0
  7. package/dist/client/index.js +1 -0
  8. package/dist/client/locale.d.ts +10 -0
  9. package/dist/client/models/index.d.ts +10 -0
  10. package/dist/client/plugin.d.ts +6 -0
  11. package/dist/externalVersion.js +11 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +39 -0
  14. package/dist/locale/en-US.json +10 -0
  15. package/dist/locale/zh-CN.json +10 -0
  16. package/dist/server/collections/ai-api-config.d.ts +2 -0
  17. package/dist/server/collections/ai-api-config.js +63 -0
  18. package/dist/server/index.d.ts +1 -0
  19. package/dist/server/index.js +33 -0
  20. package/dist/server/middleware/rate-limit.d.ts +18 -0
  21. package/dist/server/middleware/rate-limit.js +61 -0
  22. package/dist/server/plugin.d.ts +18 -0
  23. package/dist/server/plugin.js +87 -0
  24. package/dist/server/resource/ai-api-config.d.ts +10 -0
  25. package/dist/server/resource/ai-api-config.js +73 -0
  26. package/dist/server/routes/agent-completions.d.ts +34 -0
  27. package/dist/server/routes/agent-completions.js +300 -0
  28. package/dist/server/routes/auth.d.ts +8 -0
  29. package/dist/server/routes/auth.js +110 -0
  30. package/dist/server/routes/chat-completions.d.ts +9 -0
  31. package/dist/server/routes/chat-completions.js +237 -0
  32. package/dist/server/routes/completions.d.ts +10 -0
  33. package/dist/server/routes/completions.js +245 -0
  34. package/dist/server/routes/embeddings.d.ts +22 -0
  35. package/dist/server/routes/embeddings.js +166 -0
  36. package/dist/server/routes/models.d.ts +21 -0
  37. package/dist/server/routes/models.js +154 -0
  38. package/dist/server/routes/router.d.ts +25 -0
  39. package/dist/server/routes/router.js +189 -0
  40. package/dist/server/utils/openai-format.d.ts +99 -0
  41. package/dist/server/utils/openai-format.js +140 -0
  42. package/dist/server/utils/rate-limiter.d.ts +35 -0
  43. package/dist/server/utils/rate-limiter.js +81 -0
  44. package/dist/server/utils/resolve-service.d.ts +19 -0
  45. package/dist/server/utils/resolve-service.js +71 -0
  46. package/nocobase-plugin-ai-api-2.0.20.tgz +0 -0
  47. package/package.json +13 -0
  48. package/server.d.ts +2 -0
  49. package/server.js +1 -0
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # @nocobase/plugin-ai-api
package/client.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './dist/client';
2
+ export { default } from './dist/client';
package/client.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/client/index.js');
@@ -0,0 +1 @@
1
+ "use strict";(self.webpackChunkplugin_ai_api=self.webpackChunkplugin_ai_api||[]).push([["503"],{968:function(e,t,n){n.r(t),n.d(t,{AiApiConfigPage:function(){return y}});var a=n(156),r=n.n(a),l=n(721),o=n(772);function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,a=Array(t);n<t;n++)a[n]=e[n];return a}function c(e,t,n,a,r,l,o){try{var i=e[l](o),c=i.value}catch(e){n(e);return}i.done?t(c):Promise.resolve(c).then(a,r)}function u(e){return function(){var t=this,n=arguments;return new Promise(function(a,r){var l=e.apply(t,n);function o(e){c(l,a,r,o,i,"next",e)}function i(e){c(l,a,r,o,i,"throw",e)}o(void 0)})}}function s(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n,a,r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var l=[],o=!0,i=!1;try{for(r=r.call(e);!(o=(n=r.next()).done)&&(l.push(n.value),!t||l.length!==t);o=!0);}catch(e){i=!0,a=e}finally{try{o||null==r.return||r.return()}finally{if(i)throw a}}return l}}(e,t)||function(e,t){if(e){if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(e,t)}}(e,t)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function m(e,t){var n,a,r,l,o={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return l={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(l[Symbol.iterator]=function(){return this}),l;function i(l){return function(i){var c=[l,i];if(n)throw TypeError("Generator is already executing.");for(;o;)try{if(n=1,a&&(r=2&c[0]?a.return:c[0]?a.throw||((r=a.return)&&r.call(a),0):a.next)&&!(r=r.call(a,c[1])).done)return r;switch(a=0,r&&(c=[2&c[0],r.value]),c[0]){case 0:case 1:r=c;break;case 4:return o.label++,{value:c[1],done:!1};case 5:o.label++,a=c[1],c=[0];continue;case 7:c=o.ops.pop(),o.trys.pop();continue;default:if(!(r=(r=o.trys).length>0&&r[r.length-1])&&(6===c[0]||2===c[0])){o=0;continue}if(3===c[0]&&(!r||c[1]>r[0]&&c[1]<r[3])){o.label=c[1];break}if(6===c[0]&&o.label<r[1]){o.label=r[1],r=c;break}if(r&&o.label<r[2]){o.label=r[2],o.ops.push(c);break}r[2]&&o.ops.pop(),o.trys.pop();continue}c=t.call(e,o)}catch(e){c=[6,e],a=0}finally{n=r=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}var p=l.Typography.Title,d=l.Typography.Text,f=l.Typography.Paragraph;function y(){var e,t,n=(0,o.useAPIClient)(),i=s(l.Form.useForm(),1)[0],c=s((0,a.useState)(!0),2),y=c[0],v=c[1],h=s((0,a.useState)(!1),2),b=h[0],g=h[1],E=s((0,a.useState)([]),2),A=E[0],S=E[1],w=s((0,a.useState)([]),2),I=w[0],L=w[1],k=s((0,a.useState)(""),2),C=k[0],P=k[1];(0,a.useEffect)(function(){x(),P("".concat(window.location.origin,"/api/ai-llm/v1"))},[]);var x=(e=u(function(){var e,t,a,r,l,o,c;return m(this,function(u){switch(u.label){case 0:v(!0),u.label=1;case 1:return u.trys.push([1,5,6,7]),[4,n.request({url:"aiApiConfig:get"})];case 2:return(l=null==(r=u.sent())||null==(e=r.data)?void 0:e.data)&&i.setFieldsValue({mode:l.mode||"llm",defaultAiEmployee:l.defaultAiEmployee||void 0,defaultLlmService:l.defaultLlmService||void 0,enabledLlmServices:l.enabledLlmServices||[],rateLimitPerMinute:l.rateLimitPerMinute||60}),[4,n.request({url:"aiEmployees:list",params:{paginate:!1}})];case 3:return S(((null==(o=u.sent())||null==(t=o.data)?void 0:t.data)||[]).map(function(e){return{username:e.username,nickname:e.nickname||e.username}})),[4,n.request({url:"ai:listLLMServices"})];case 4:return L(((null==(c=u.sent())||null==(a=c.data)?void 0:a.data)||[]).map(function(e){return{name:e.name,title:e.title||e.name,provider:e.provider,enabled:!1!==e.enabled}})),[3,7];case 5:return console.error("Failed to load config:",u.sent()),[3,7];case 6:return v(!1),[7];case 7:return[2]}})}),function(){return e.apply(this,arguments)}),M=(t=u(function(){var e;return m(this,function(t){switch(t.label){case 0:g(!0),t.label=1;case 1:return t.trys.push([1,3,4,5]),e=i.getFieldsValue(),[4,n.request({url:"aiApiConfig:save",method:"post",data:e})];case 2:return t.sent(),l.message.success("Configuration saved"),[3,5];case 3:return t.sent(),l.message.error("Failed to save configuration"),[3,5];case 4:return g(!1),[7];case 5:return[2]}})}),function(){return t.apply(this,arguments)});return y?r().createElement("div",{style:{display:"flex",justifyContent:"center",padding:60}},r().createElement(l.Spin,{size:"large"})):r().createElement("div",{style:{maxWidth:800,margin:"0 auto",padding:"24px 0"}},r().createElement(p,{level:3},"AI API Gateway Configuration"),r().createElement(f,{type:"secondary"},"Configure the OpenAI-compatible API endpoint. External applications can connect using the base URL and a NocoBase API key."),r().createElement(l.Card,{style:{marginBottom:24}},r().createElement(l.Alert,{type:"info",showIcon:!0,message:"API Endpoint",description:r().createElement(l.Space,{direction:"vertical",size:4},r().createElement(d,null,"Base URL: ",r().createElement(d,{code:!0,copyable:!0},C)),r().createElement(d,{type:"secondary"},"Use this as the base URL in any OpenAI-compatible client (Cursor, Continue.dev, n8n, etc.)")),style:{marginBottom:16}}),r().createElement(l.Alert,{type:"warning",showIcon:!0,message:"Authentication",description:r().createElement(d,null,"Clients must include a NocoBase API key as a Bearer token:",r().createElement("br",null),r().createElement(d,{code:!0},"Authorization: Bearer ","<your-nocobase-api-key>"),r().createElement("br",null),r().createElement(d,{type:"secondary"},"API keys can be created in Settings → API keys."))})),r().createElement(l.Card,{title:"Configuration"},r().createElement(l.Form,{form:i,layout:"vertical",onFinish:M,initialValues:{mode:"llm"}},r().createElement(l.Form.Item,{name:"mode",label:"API Mode",tooltip:"LLM Proxy: Direct access to the LLM model. Agent: Full AI Employee with tools, knowledge base, and agent capabilities."},r().createElement(l.Radio.Group,null,r().createElement(l.Radio.Button,{value:"llm"},"LLM Proxy"),r().createElement(l.Radio.Button,{value:"agent"},"AI Employee Agent"))),r().createElement(l.Form.Item,{name:"defaultAiEmployee",label:"Default AI Employee",tooltip:"The AI Employee whose system prompt will be injected into chat completions (when client doesn't provide a system message)"},r().createElement(l.Select,{allowClear:!0,placeholder:"Select an AI Employee (optional)",options:A.map(function(e){return{label:"".concat(e.nickname," (").concat(e.username,")"),value:e.username}})})),r().createElement(l.Form.Item,{name:"defaultLlmService",label:"Default LLM Service",tooltip:"The LLM service used when clients send only a model name (e.g. 'gpt-4o') without a service prefix. This is the main service that powers the API.",rules:[{required:!0,message:"Please select a default LLM service"}]},r().createElement(l.Select,{allowClear:!0,placeholder:"Select an LLM Service",options:I.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),r().createElement(l.Form.Item,{name:"enabledLlmServices",label:"Enabled LLM Services",tooltip:"Only these services will be exposed via the API. Leave empty to expose all enabled services."},r().createElement(l.Select,{mode:"multiple",allowClear:!0,placeholder:"All enabled services (default)",options:I.map(function(e){return{label:"".concat(e.title," (").concat(e.provider,")"),value:e.name}})})),r().createElement(l.Form.Item,{name:"rateLimitPerMinute",label:"Rate Limit (requests/minute)",tooltip:"Maximum API requests per user per minute. Set 0 for unlimited."},r().createElement(l.InputNumber,{min:0,max:1e4,style:{width:200}})),r().createElement(l.Form.Item,null,r().createElement(l.Button,{type:"primary",htmlType:"submit",loading:b},"Save Configuration")))),r().createElement(l.Card,{title:"Quick Start",style:{marginTop:24}},r().createElement(p,{level:5},"cURL Example"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},"curl ".concat(C,'/chat/completions \\\n -H "Authorization: Bearer <your-api-key>" \\\n -H "Content-Type: application/json" \\\n -d \'{\n "model": "<service-name>/<model-id>",\n "messages": [\n {"role": "user", "content": "Hello!"}\n ]\n }\''))),r().createElement(p,{level:5},"Python (OpenAI SDK)"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},'from openai import OpenAI\n\nclient = OpenAI(\n base_url="'.concat(C,'",\n api_key="<your-nocobase-api-key>"\n)\n\nresponse = client.chat.completions.create(\n model="<service-name>/<model-id>",\n messages=[{"role": "user", "content": "Hello!"}]\n)'))),r().createElement(p,{level:5},"List Available Models"),r().createElement(f,null,r().createElement("pre",{style:{background:"#1a1a2e",color:"#e0e0e0",padding:16,borderRadius:8,fontSize:13,overflow:"auto"}},"curl ".concat(C,'/models \\\n -H "Authorization: Bearer <your-api-key>"')))))}t.default=y}}]);
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export declare function AiApiConfigPage(): React.JSX.Element;
3
+ export default AiApiConfigPage;
@@ -0,0 +1 @@
1
+ export { default } from './plugin';
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("@nocobase/client"),require("react"),require("antd")):"function"==typeof define&&define.amd?define("plugin-ai-api",["@nocobase/client","react","antd"],t):"object"==typeof exports?exports["plugin-ai-api"]=t(require("@nocobase/client"),require("react"),require("antd")):e["plugin-ai-api"]=t(e["@nocobase/client"],e.react,e.antd)}(self,function(e,t,n){return function(){var r,o,i,u,a,c,f={581:function(e){e.exports=function(e,t){return"undefined"!=typeof __deoptimization_sideEffect__&&__deoptimization_sideEffect__(e,t),t}},772:function(t){"use strict";t.exports=e},721:function(e){"use strict";e.exports=n},156:function(e){"use strict";e.exports=t}},l={};function p(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={exports:{}};return f[e](n,n.exports,p),n.exports}p.m=f,p.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return p.d(t,{a:t}),t},p.d=function(e,t){for(var n in t)p.o(t,n)&&!p.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},p.f={},p.e=function(e){return Promise.all(Object.keys(p.f).reduce(function(t,n){return p.f[n](e,t),t},[]))},p.u=function(e){return"824c2f7487ee05fd.js"},p.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),p.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="plugin-ai-api:",p.l=function(e,t,n,i){if(r[e])return void r[e].push(t);if(void 0!==n)for(var u,a,c=document.getElementsByTagName("script"),f=0;f<c.length;f++){var l=c[f];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){u=l;break}}u||(a=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,p.nc&&u.setAttribute("nonce",p.nc),u.setAttribute("data-webpack",o+n),u.src=e),r[e]=[t];var s=function(t,n){u.onerror=u.onload=null,clearTimeout(d);var o=r[e];if(delete r[e],u.parentNode&&u.parentNode.removeChild(u),o&&o.forEach(function(e){return e(n)}),t)return t(n)},d=setTimeout(s.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=s.bind(null,u.onerror),u.onload=s.bind(null,u.onload),a&&document.head.appendChild(u)},p.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(i=window.__webpack_public_path__||"/").endsWith("/")||(i+="/"),p.p=i+"static/plugins/plugin-ai-api/dist/client/",u={909:0},p.f.j=function(e,t){var n=p.o(u,e)?u[e]:void 0;if(0!==n)if(n)t.push(n[2]);else{var r=new Promise(function(t,r){n=u[e]=[t,r]});t.push(n[2]=r);var o=p.p+p.u(e),i=Error();p.l(o,function(t){if(p.o(u,e)&&(0!==(n=u[e])&&(u[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",i.name="ChunkLoadError",i.type=r,i.request=o,n[1](i)}},"chunk-"+e,e)}},a=function(e,t){var n,r,o=t[0],i=t[1],a=t[2],c=0;if(o.some(function(e){return 0!==u[e]})){for(n in i)p.o(i,n)&&(p.m[n]=i[n]);a&&a(p)}for(e&&e(t);c<o.length;c++)r=o[c],p.o(u,r)&&u[r]&&u[r][0](),u[r]=0},(c=self.webpackChunkplugin_ai_api=self.webpackChunkplugin_ai_api||[]).forEach(a.bind(null,0)),c.push=a.bind(null,c.push.bind(c));var s={};return!function(){"use strict";p.r(s),p.d(s,{default:function(){return d}});var e=p(772),t=p(156),n=p.n(t),r=p(581);function o(e,t,n,r,o,i,u){try{var a=e[i](u),c=a.value}catch(e){n(e);return}a.done?t(c):Promise.resolve(c).then(r,o)}function i(e,t,n){return(i=f()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&a(o,n.prototype),o}).apply(null,arguments)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function a(e,t){return(a=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function c(e){var t="function"==typeof Map?new Map:void 0;return(c=function(e){if(null===e||-1===Function.toString.call(e).indexOf("[native code]"))return e;if("function"!=typeof e)throw TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,n)}function n(){return i(e,arguments,u(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),a(n,e)})(e)}function f(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(f=function(){return!!e})()}var l=n().lazy(function(){return r("imported_c1deu3_component",p.e("503").then(p.bind(p,968)))}),d=function(e){var t;if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");function n(){var e,t,r,o,i;if(!(this instanceof n))throw TypeError("Cannot call a class as a function");return t=n,r=arguments,t=u(t),o=e=function(e,t){var n;if(t&&("object"==((n=t)&&"undefined"!=typeof Symbol&&n.constructor===Symbol?"symbol":typeof n)||"function"==typeof t))return t;if(void 0===e)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(this,f()?Reflect.construct(t,r||[],u(this).constructor):t.apply(this,r)),i=function(e){return e},"t"in o?Object.defineProperty(o,"t",{value:i,enumerable:!0,configurable:!0,writable:!0}):o.t=i,e}return n.prototype=Object.create(e&&e.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),e&&a(n,e),t=[{key:"load",value:function(){var e,t=this;return(e=function(){return function(e,t){var n,r,o,i,u={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){var c=[i,a];if(n)throw TypeError("Generator is already executing.");for(;u;)try{if(n=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return u.label++,{value:c[1],done:!1};case 5:u.label++,r=c[1],c=[0];continue;case 7:c=u.ops.pop(),u.trys.pop();continue;default:if(!(o=(o=u.trys).length>0&&o[o.length-1])&&(6===c[0]||2===c[0])){u=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){u.label=c[1];break}if(6===c[0]&&u.label<o[1]){u.label=o[1],o=c;break}if(o&&u.label<o[2]){u.label=o[2],u.ops.push(c);break}o[2]&&u.ops.pop(),u.trys.pop();continue}c=t.call(e,u)}catch(e){c=[6,e],r=0}finally{n=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}}}(this,function(e){return t.app.pluginSettingsManager.add("ai-api",{icon:"ApiOutlined",title:t.t("AI API Gateway"),aclSnippet:"pm.plugin-ai-api.configuration"}),t.app.pluginSettingsManager.add("ai-api.config",{title:t.t("Configuration"),Component:l}),[2]})},function(){var t=this,n=arguments;return new Promise(function(r,i){var u=e.apply(t,n);function a(e){o(u,r,i,a,c,"next",e)}function c(e){o(u,r,i,a,c,"throw",e)}a(void 0)})})()}}],function(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(n.prototype,t),n}(c(e.Plugin))}(),s}()});
@@ -0,0 +1,10 @@
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
+ export declare function useT(): (str: string) => any;
10
+ export declare function tExpr(key: string): any;
@@ -0,0 +1,10 @@
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
+ declare const _default: Record<string, ModelConstructor>;
10
+ export default _default;
@@ -0,0 +1,6 @@
1
+ import { Plugin } from '@nocobase/client';
2
+ export declare class PluginAiApiClient extends Plugin {
3
+ load(): Promise<void>;
4
+ t: (key: string) => string;
5
+ }
6
+ export default PluginAiApiClient;
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ "react": "18.3.1",
3
+ "antd": "5.24.2",
4
+ "@nocobase/client": "2.0.20",
5
+ "@nocobase/flow-engine": "2.0.20",
6
+ "@nocobase/server": "2.0.20",
7
+ "@nocobase/database": "2.0.20",
8
+ "@nocobase/actions": "2.0.20",
9
+ "@nocobase/resourcer": "2.0.20",
10
+ "@nocobase/plugin-ai": "2.0.20"
11
+ };
@@ -0,0 +1,2 @@
1
+ export * from './server';
2
+ export { default } from './server';
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var src_exports = {};
30
+ __export(src_exports, {
31
+ default: () => import_server.default
32
+ });
33
+ module.exports = __toCommonJS(src_exports);
34
+ __reExport(src_exports, require("./server"), module.exports);
35
+ var import_server = __toESM(require("./server"));
36
+ // Annotate the CommonJS export names for ESM import in node:
37
+ 0 && (module.exports = {
38
+ ...require("./server")
39
+ });
@@ -0,0 +1,10 @@
1
+ {
2
+ "AI API Gateway": "AI API Gateway",
3
+ "Configuration": "Configuration",
4
+ "Default AI Employee": "Default AI Employee",
5
+ "Enabled LLM Services": "Enabled LLM Services",
6
+ "Rate Limit": "Rate Limit",
7
+ "Save Configuration": "Save Configuration",
8
+ "Configuration saved": "Configuration saved",
9
+ "Failed to save configuration": "Failed to save configuration"
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "AI API Gateway": "AI API 网关",
3
+ "Configuration": "配置",
4
+ "Default AI Employee": "默认 AI 员工",
5
+ "Enabled LLM Services": "已启用 LLM 服务",
6
+ "Rate Limit": "速率限制",
7
+ "Save Configuration": "保存配置",
8
+ "Configuration saved": "配置已保存",
9
+ "Failed to save configuration": "保存配置失败"
10
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,63 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var ai_api_config_exports = {};
19
+ __export(ai_api_config_exports, {
20
+ default: () => ai_api_config_default
21
+ });
22
+ module.exports = __toCommonJS(ai_api_config_exports);
23
+ var import_database = require("@nocobase/database");
24
+ var ai_api_config_default = (0, import_database.defineCollection)({
25
+ name: "aiApiConfig",
26
+ autoGenId: true,
27
+ fields: [
28
+ {
29
+ name: "mode",
30
+ type: "string",
31
+ defaultValue: "llm",
32
+ comment: "API mode: 'llm' = direct LLM proxy, 'agent' = full AI Employee agent with tools/RAG"
33
+ },
34
+ {
35
+ name: "defaultAiEmployee",
36
+ type: "string",
37
+ comment: "Username of the default AI Employee for system prompt injection"
38
+ },
39
+ {
40
+ name: "defaultLlmService",
41
+ type: "string",
42
+ comment: "Name (UID) of the default LLM service. Clients can send just modelId without service prefix."
43
+ },
44
+ {
45
+ name: "enabledLlmServices",
46
+ type: "json",
47
+ defaultValue: [],
48
+ comment: "Array of llmService names to expose. Empty = expose all enabled services"
49
+ },
50
+ {
51
+ name: "rateLimitPerMinute",
52
+ type: "integer",
53
+ defaultValue: 60,
54
+ comment: "Max requests per user per minute"
55
+ },
56
+ {
57
+ name: "options",
58
+ type: "jsonb",
59
+ defaultValue: {},
60
+ comment: "Reserved for future extensibility"
61
+ }
62
+ ]
63
+ });
@@ -0,0 +1 @@
1
+ export { default } from './plugin';
@@ -0,0 +1,33 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var server_exports = {};
29
+ __export(server_exports, {
30
+ default: () => import_plugin.default
31
+ });
32
+ module.exports = __toCommonJS(server_exports);
33
+ var import_plugin = __toESM(require("./plugin"));
@@ -0,0 +1,18 @@
1
+ import { RateLimiter } from '../utils/rate-limiter';
2
+ /**
3
+ * Creates a rate-limiting check function for use in the AI API router.
4
+ *
5
+ * Must be called AFTER authenticateBearer() so ctx.state.currentUser is set.
6
+ * Reads rateLimitPerMinute from aiApiConfig on each request (cheap single-row read,
7
+ * allows config changes to take effect immediately without restart).
8
+ * Falls back to 60 req/min if the config record is missing or the field is 0/null.
9
+ *
10
+ * Returns false (and writes the 429 response) when the rate limit is exceeded.
11
+ * Returns true when the request is allowed.
12
+ *
13
+ * Sets OpenAI-compatible rate limit response headers on every request:
14
+ * X-RateLimit-Limit: <limit>
15
+ * X-RateLimit-Remaining: <remaining> (on 429: 0)
16
+ * Retry-After: <seconds> (on 429 only)
17
+ */
18
+ export declare function createRateLimitMiddleware(limiter: RateLimiter): (ctx: Context) => Promise<boolean>;
@@ -0,0 +1,61 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var rate_limit_exports = {};
19
+ __export(rate_limit_exports, {
20
+ createRateLimitMiddleware: () => createRateLimitMiddleware
21
+ });
22
+ module.exports = __toCommonJS(rate_limit_exports);
23
+ var import_openai_format = require("../utils/openai-format");
24
+ function createRateLimitMiddleware(limiter) {
25
+ return async (ctx) => {
26
+ var _a, _b, _c;
27
+ const userId = (_a = ctx.state.currentUser) == null ? void 0 : _a.id;
28
+ if (userId === void 0 || userId === null) return true;
29
+ let limit = 60;
30
+ try {
31
+ const config = await ctx.db.getRepository("aiApiConfig").findOne();
32
+ const configLimit = config == null ? void 0 : config.rateLimitPerMinute;
33
+ if (configLimit && configLimit > 0) {
34
+ limit = configLimit;
35
+ }
36
+ } catch (configErr) {
37
+ (_c = (_b = ctx.app) == null ? void 0 : _b.logger) == null ? void 0 : _c.warn("[ai-api] Rate limit config read failed, using default (60/min)", configErr);
38
+ }
39
+ const result = limiter.check(userId, limit);
40
+ if (!result.allowed) {
41
+ const retryAfterSec = Math.ceil(result.retryAfterMs / 1e3);
42
+ ctx.set("Retry-After", String(retryAfterSec));
43
+ ctx.set("X-RateLimit-Limit", String(limit));
44
+ ctx.set("X-RateLimit-Remaining", "0");
45
+ ctx.status = 429;
46
+ ctx.body = (0, import_openai_format.toOpenAIError)(
47
+ 429,
48
+ `Rate limit exceeded. You have used all ${limit} requests allowed per minute. Please wait ${retryAfterSec} second${retryAfterSec !== 1 ? "s" : ""} before retrying.`,
49
+ "requests",
50
+ "rate_limit_exceeded"
51
+ );
52
+ return false;
53
+ }
54
+ ctx.set("X-RateLimit-Limit", String(limit));
55
+ return true;
56
+ };
57
+ }
58
+ // Annotate the CommonJS export names for ESM import in node:
59
+ 0 && (module.exports = {
60
+ createRateLimitMiddleware
61
+ });
@@ -0,0 +1,18 @@
1
+ import { Plugin } from '@nocobase/server';
2
+ import { RateLimiter } from './utils/rate-limiter';
3
+ export declare class PluginAiApiServer extends Plugin {
4
+ /**
5
+ * Singleton rate limiter — lives for the entire plugin lifetime, shared across all requests.
6
+ * Uses a 1-minute sliding window to enforce rateLimitPerMinute from aiApiConfig.
7
+ */
8
+ rateLimiter: RateLimiter;
9
+ private gcInterval;
10
+ afterAdd(): Promise<void>;
11
+ beforeLoad(): Promise<void>;
12
+ load(): Promise<void>;
13
+ install(): Promise<void>;
14
+ afterEnable(): Promise<void>;
15
+ afterDisable(): Promise<void>;
16
+ remove(): Promise<void>;
17
+ }
18
+ export default PluginAiApiServer;
@@ -0,0 +1,87 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var plugin_exports = {};
29
+ __export(plugin_exports, {
30
+ PluginAiApiServer: () => PluginAiApiServer,
31
+ default: () => plugin_default
32
+ });
33
+ module.exports = __toCommonJS(plugin_exports);
34
+ var import_server = require("@nocobase/server");
35
+ var import_router = require("./routes/router");
36
+ var import_ai_api_config = __toESM(require("./resource/ai-api-config"));
37
+ var import_rate_limiter = require("./utils/rate-limiter");
38
+ class PluginAiApiServer extends import_server.Plugin {
39
+ /**
40
+ * Singleton rate limiter — lives for the entire plugin lifetime, shared across all requests.
41
+ * Uses a 1-minute sliding window to enforce rateLimitPerMinute from aiApiConfig.
42
+ */
43
+ rateLimiter = new import_rate_limiter.RateLimiter(6e4);
44
+ gcInterval = null;
45
+ async afterAdd() {
46
+ }
47
+ async beforeLoad() {
48
+ }
49
+ async load() {
50
+ this.app.use((0, import_router.createAiLlmRouter)(this), { before: "resourcer" });
51
+ this.app.resourceManager.define(import_ai_api_config.default);
52
+ this.app.acl.registerSnippet({
53
+ name: `pm.${this.name}.configuration`,
54
+ actions: ["aiApiConfig:*"]
55
+ });
56
+ this.gcInterval = setInterval(() => this.rateLimiter.gc(), 5 * 60 * 1e3);
57
+ this.gcInterval.unref();
58
+ }
59
+ async install() {
60
+ const existing = await this.db.getRepository("aiApiConfig").findOne();
61
+ if (!existing) {
62
+ await this.db.getRepository("aiApiConfig").create({
63
+ values: {
64
+ defaultAiEmployee: "",
65
+ enabledLlmServices: [],
66
+ rateLimitPerMinute: 60
67
+ }
68
+ });
69
+ }
70
+ }
71
+ async afterEnable() {
72
+ }
73
+ async afterDisable() {
74
+ }
75
+ async remove() {
76
+ if (this.gcInterval) {
77
+ clearInterval(this.gcInterval);
78
+ this.gcInterval = null;
79
+ }
80
+ this.rateLimiter.clear();
81
+ }
82
+ }
83
+ var plugin_default = PluginAiApiServer;
84
+ // Annotate the CommonJS export names for ESM import in node:
85
+ 0 && (module.exports = {
86
+ PluginAiApiServer
87
+ });
@@ -0,0 +1,10 @@
1
+ import { ResourceOptions } from '@nocobase/resourcer';
2
+ /**
3
+ * Resource for managing AI API configuration via NocoBase admin UI.
4
+ * Singleton config pattern (same as aiSettings in plugin-ai).
5
+ *
6
+ * Uses custom action name 'save' instead of 'update' because NocoBase's
7
+ * built-in middleware requires filter/filterByTk for the standard 'update' action.
8
+ */
9
+ declare const aiApiConfigResource: ResourceOptions;
10
+ export default aiApiConfigResource;
@@ -0,0 +1,73 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var ai_api_config_exports = {};
19
+ __export(ai_api_config_exports, {
20
+ default: () => ai_api_config_default
21
+ });
22
+ module.exports = __toCommonJS(ai_api_config_exports);
23
+ const aiApiConfigResource = {
24
+ name: "aiApiConfig",
25
+ actions: {
26
+ async get(ctx, next) {
27
+ let config = await ctx.db.getRepository("aiApiConfig").findOne();
28
+ if (!config) {
29
+ config = await ctx.db.getRepository("aiApiConfig").create({
30
+ values: {
31
+ mode: "llm",
32
+ defaultAiEmployee: "",
33
+ defaultLlmService: "",
34
+ enabledLlmServices: [],
35
+ rateLimitPerMinute: 60,
36
+ options: {}
37
+ }
38
+ });
39
+ }
40
+ ctx.body = config;
41
+ await next();
42
+ },
43
+ async save(ctx, next) {
44
+ const values = ctx.action.params.values || {};
45
+ const repo = ctx.db.getRepository("aiApiConfig");
46
+ let config = await repo.findOne();
47
+ if (!config) {
48
+ config = await repo.create({
49
+ values: {
50
+ mode: values.mode ?? "llm",
51
+ defaultAiEmployee: values.defaultAiEmployee ?? "",
52
+ defaultLlmService: values.defaultLlmService ?? "",
53
+ enabledLlmServices: values.enabledLlmServices ?? [],
54
+ rateLimitPerMinute: values.rateLimitPerMinute ?? 60,
55
+ options: values.options ?? {}
56
+ }
57
+ });
58
+ } else {
59
+ const updateData = {};
60
+ if (values.mode !== void 0) updateData.mode = values.mode;
61
+ if (values.defaultAiEmployee !== void 0) updateData.defaultAiEmployee = values.defaultAiEmployee;
62
+ if (values.defaultLlmService !== void 0) updateData.defaultLlmService = values.defaultLlmService;
63
+ if (values.enabledLlmServices !== void 0) updateData.enabledLlmServices = values.enabledLlmServices;
64
+ if (values.rateLimitPerMinute !== void 0) updateData.rateLimitPerMinute = values.rateLimitPerMinute;
65
+ if (values.options !== void 0) updateData.options = values.options;
66
+ await config.update(updateData);
67
+ }
68
+ ctx.body = config;
69
+ await next();
70
+ }
71
+ }
72
+ };
73
+ var ai_api_config_default = aiApiConfigResource;
@@ -0,0 +1,34 @@
1
+ import { Context } from '@nocobase/actions';
2
+ import type PluginAiApiServer from '../plugin';
3
+ /**
4
+ * POST /api/ai-llm/v1/chat/completions (agent mode)
5
+ *
6
+ * Runs the full AI Employee pipeline by directly instantiating AIEmployee
7
+ * from plugin-ai. This provides TRUE real-time streaming — no buffering.
8
+ *
9
+ * ## Architecture (vs old approach)
10
+ *
11
+ * OLD (fake streaming, HTTP loopback):
12
+ * Client → ai-api → HTTP to localhost → aiConversations:sendMessages
13
+ * ← buffers entire response ←
14
+ * Client ← 20-char fake chunks ← re-emit
15
+ *
16
+ * NEW (true streaming, direct instantiation):
17
+ * Client → ai-api → AIEmployee(ctx) ← writes directly to ctx.res
18
+ * We intercept ctx.res.write to translate NocoBase SSE → OpenAI SSE
19
+ * Client ← real-time OpenAI SSE chunks ← write() intercept
20
+ *
21
+ * ## NocoBase → OpenAI SSE translation
22
+ *
23
+ * NocoBase emits: `data: {"type":"content","body":"chunk text"}\n\n`
24
+ * We emit: `data: {"choices":[{"delta":{"content":"chunk text"},...}]}\n\n`
25
+ *
26
+ * Other NocoBase event types (tool_calls, stream_start, etc.) are silently
27
+ * ignored — they are NocoBase-internal events not part of the OpenAI protocol.
28
+ *
29
+ * ## Known limitation
30
+ * Token usage is always { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
31
+ * in agent mode. ResponseMetadataCollector is private inside AIEmployee with no
32
+ * public accessor. Modifying plugin-ai is out of scope for this plugin.
33
+ */
34
+ export declare function handleAgentCompletions(ctx: Context, plugin: PluginAiApiServer): Promise<void>;