plugin-custom-llm 1.0.1 → 1.1.1

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.
@@ -7,4 +7,4 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):"function"==typeof define&&define.amd?define("plugin-custom-llm",["react","@nocobase/plugin-ai/client","@nocobase/client","@nocobase/utils/client","antd","react-i18next"],t):"object"==typeof exports?exports["plugin-custom-llm"]=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):e["plugin-custom-llm"]=t(e.react,e["@nocobase/plugin-ai/client"],e["@nocobase/client"],e["@nocobase/utils/client"],e.antd,e["react-i18next"])}(self,function(e,t,n,o,r,i){return function(){"use strict";var u={772:function(e){e.exports=n},645:function(e){e.exports=t},584:function(e){e.exports=o},721:function(e){e.exports=r},156:function(t){t.exports=e},238:function(e){e.exports=i}},c={};function a(e){var t=c[e];if(void 0!==t)return t.exports;var n=c[e]={exports:{}};return u[e](n,n.exports,a),n.exports}a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,{a:t}),t},a.d=function(e,t){for(var n in t)a.o(t,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var l={};return!function(){a.r(l),a.d(l,{PluginCustomLLMClient:function(){return h},default:function(){return S}});var e=a(772),t=a(156),n=a.n(t),o=a(584),r=a(238),i="@nocobase/plugin-custom-llm",u=a(721),c=a(645),p=function(){var t=(0,r.useTranslation)(i,{nsMode:"fallback"}).t;return n().createElement("div",{style:{marginBottom:24}},n().createElement(u.Collapse,{bordered:!1,size:"small",items:[{key:"options",label:t("Options"),forceRender:!0,children:n().createElement(e.SchemaComponent,{schema:{type:"void",name:"custom-llm",properties:{temperature:{title:(0,o.tval)("Temperature",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:.7,"x-component-props":{step:.1,min:0,max:2}},maxCompletionTokens:{title:(0,o.tval)("Max completion tokens",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:-1},topP:{title:(0,o.tval)("Top P",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1,"x-component-props":{step:.1,min:0,max:1}},frequencyPenalty:{title:(0,o.tval)("Frequency penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},presencePenalty:{title:(0,o.tval)("Presence penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},responseFormat:{title:(0,o.tval)("Response format",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Select",enum:[{label:t("Text"),value:"text"},{label:t("JSON"),value:"json_object"}],default:"text"},timeout:{title:(0,o.tval)("Timeout (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:6e4},maxRetries:{title:(0,o.tval)("Max retries",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1}}}})}]}))},s={components:{ProviderSettingsForm:function(){return n().createElement(e.SchemaComponent,{schema:{type:"void",properties:{baseURL:{title:(0,o.tval)("Base URL",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"TextAreaWithGlobalScope","x-component-props":{placeholder:"https://your-llm-server.com/v1"}},apiKey:{title:(0,o.tval)("API Key",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"TextAreaWithGlobalScope"},requestConfig:{title:(0,o.tval)("Request config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({extraHeaders:{},extraBody:{},modelKwargs:{}},null,2),rows:6,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Request config description",{ns:i})},responseConfig:{title:(0,o.tval)("Response config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({contentPath:"auto",reasoningKey:"reasoning_content",responseMapping:{content:"message.response"}},null,2),rows:8,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Response config description",{ns:i})}}}})},ModelSettingsForm:function(){return n().createElement(e.SchemaComponent,{components:{Options:p,ModelSelect:c.ModelSelect},schema:{type:"void",properties:{model:{title:(0,o.tval)("Model",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"ModelSelect"},options:{type:"void","x-component":"Options"}}}})}}};function f(e,t,n,o,r,i,u){try{var c=e[i](u),a=c.value}catch(e){n(e);return}c.done?t(a):Promise.resolve(a).then(o,r)}function m(e){return function(){var t=this,n=arguments;return new Promise(function(o,r){var i=e.apply(t,n);function u(e){f(i,o,r,u,c,"next",e)}function c(e){f(i,o,r,u,c,"throw",e)}u(void 0)})}}function d(e,t,n){return(d=v()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var r=new(Function.bind.apply(e,o));return n&&b(r,n.prototype),r}).apply(null,arguments)}function y(e){return(y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function b(e,t){return(b=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function x(e){var t="function"==typeof Map?new Map:void 0;return(x=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 d(e,arguments,y(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),b(n,e)})(e)}function v(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(v=function(){return!!e})()}function g(e,t){var n,o,r,i,u={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){var a=[i,c];if(n)throw TypeError("Generator is already executing.");for(;u;)try{if(n=1,o&&(r=2&a[0]?o.return:a[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,a[1])).done)return r;switch(o=0,r&&(a=[2&a[0],r.value]),a[0]){case 0:case 1:r=a;break;case 4:return u.label++,{value:a[1],done:!1};case 5:u.label++,o=a[1],a=[0];continue;case 7:a=u.ops.pop(),u.trys.pop();continue;default:if(!(r=(r=u.trys).length>0&&r[r.length-1])&&(6===a[0]||2===a[0])){u=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]<r[3])){u.label=a[1];break}if(6===a[0]&&u.label<r[1]){u.label=r[1],r=a;break}if(r&&u.label<r[2]){u.label=r[2],u.ops.push(a);break}r[2]&&u.ops.pop(),u.trys.pop();continue}a=t.call(e,u)}catch(e){a=[6,e],o=0}finally{n=r=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}}}var h=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;if(!(this instanceof n))throw TypeError("Cannot call a class as a function");return e=n,t=arguments,e=y(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,v()?Reflect.construct(e,t||[],y(this).constructor):e.apply(this,t))}return n.prototype=Object.create(e&&e.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),e&&b(n,e),t=[{key:"afterAdd",value:function(){return m(function(){return g(this,function(e){return[2]})})()}},{key:"beforeLoad",value:function(){return m(function(){return g(this,function(e){return[2]})})()}},{key:"load",value:function(){var e=this;return m(function(){return g(this,function(t){return e.aiPlugin.aiManager.registerLLMProvider("custom-llm",s),[2]})})()}},{key:"aiPlugin",get:function(){return this.app.pm.get("ai")}}],function(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}(n.prototype,t),n}(x(e.Plugin)),S=h}(),l}()});
10
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):"function"==typeof define&&define.amd?define("plugin-custom-llm",["react","@nocobase/plugin-ai/client","@nocobase/client","@nocobase/utils/client","antd","react-i18next"],t):"object"==typeof exports?exports["plugin-custom-llm"]=t(require("react"),require("@nocobase/plugin-ai/client"),require("@nocobase/client"),require("@nocobase/utils/client"),require("antd"),require("react-i18next")):e["plugin-custom-llm"]=t(e.react,e["@nocobase/plugin-ai/client"],e["@nocobase/client"],e["@nocobase/utils/client"],e.antd,e["react-i18next"])}(self,function(e,t,n,o,r,i){return function(){"use strict";var a={772:function(e){e.exports=n},645:function(e){e.exports=t},584:function(e){e.exports=o},721:function(e){e.exports=r},156:function(t){t.exports=e},238:function(e){e.exports=i}},c={};function u(e){var t=c[e];if(void 0!==t)return t.exports;var n=c[e]={exports:{}};return a[e](n,n.exports,u),n.exports}u.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return u.d(t,{a:t}),t},u.d=function(e,t){for(var n in t)u.o(t,n)&&!u.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},u.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},u.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var l={};return!function(){u.r(l),u.d(l,{PluginCustomLLMClient:function(){return g},default:function(){return S}});var e=u(772),t=u(156),n=u.n(t),o=u(584),r=u(238),i="@nocobase/plugin-custom-llm",a=u(721),c=u(645),p=function(){var t=(0,r.useTranslation)(i,{nsMode:"fallback"}).t;return n().createElement("div",{style:{marginBottom:24}},n().createElement(a.Collapse,{bordered:!1,size:"small",items:[{key:"options",label:t("Options"),forceRender:!0,children:n().createElement(e.SchemaComponent,{schema:{type:"void",name:"custom-llm",properties:{temperature:{title:(0,o.tval)("Temperature",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:.7,"x-component-props":{step:.1,min:0,max:2}},maxCompletionTokens:{title:(0,o.tval)("Max completion tokens",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:-1},topP:{title:(0,o.tval)("Top P",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1,"x-component-props":{step:.1,min:0,max:1}},frequencyPenalty:{title:(0,o.tval)("Frequency penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},presencePenalty:{title:(0,o.tval)("Presence penalty",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:0,"x-component-props":{step:.1,min:-2,max:2}},responseFormat:{title:(0,o.tval)("Response format",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Select",enum:[{label:t("Text"),value:"text"},{label:t("JSON"),value:"json_object"}],default:"text"},timeout:{title:(0,o.tval)("Timeout (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:6e4},maxRetries:{title:(0,o.tval)("Max retries",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber",default:1}}}})}]}))},s={components:{ProviderSettingsForm:function(){return n().createElement(e.SchemaComponent,{schema:{type:"void",properties:{apiKey:{title:(0,o.tval)("API Key",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"TextAreaWithGlobalScope"},disableStream:{title:(0,o.tval)("Disable streaming",{ns:i}),type:"boolean","x-decorator":"FormItem","x-component":"Checkbox","x-content":(0,o.tval)("Disable streaming description",{ns:i})},streamKeepAlive:{title:(0,o.tval)("Stream keepalive",{ns:i}),type:"boolean","x-decorator":"FormItem","x-component":"Checkbox","x-content":(0,o.tval)("Stream keepalive description",{ns:i})},keepAliveIntervalMs:{title:(0,o.tval)("Keepalive interval (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{placeholder:"5000",min:1e3,step:1e3,style:{width:"100%"}},description:(0,o.tval)("Keepalive interval description",{ns:i})},keepAliveContent:{title:(0,o.tval)("Keepalive content",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input","x-component-props":{placeholder:"..."},description:(0,o.tval)("Keepalive content description",{ns:i})},timeout:{title:(0,o.tval)("Timeout (ms)",{ns:i}),type:"number","x-decorator":"FormItem","x-component":"InputNumber","x-component-props":{placeholder:"120000",min:0,step:1e3,style:{width:"100%"}},description:(0,o.tval)("Timeout description",{ns:i})},requestConfig:{title:(0,o.tval)("Request config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({extraHeaders:{},extraBody:{},modelKwargs:{}},null,2),rows:6,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Request config description",{ns:i})},responseConfig:{title:(0,o.tval)("Response config (JSON)",{ns:i}),type:"string","x-decorator":"FormItem","x-component":"Input.TextArea","x-component-props":{placeholder:JSON.stringify({contentPath:"auto",reasoningKey:"reasoning_content",responseMapping:{content:"message.response"}},null,2),rows:8,style:{fontFamily:"monospace",fontSize:12}},description:(0,o.tval)("Response config description",{ns:i})}}}})},ModelSettingsForm:function(){return n().createElement(e.SchemaComponent,{components:{Options:p,ModelSelect:c.ModelSelect},schema:{type:"void",properties:{model:{title:(0,o.tval)("Model",{ns:i}),type:"string",required:!0,"x-decorator":"FormItem","x-component":"ModelSelect"},options:{type:"void","x-component":"Options"}}}})}}};function m(e,t,n,o,r,i,a){try{var c=e[i](a),u=c.value}catch(e){n(e);return}c.done?t(u):Promise.resolve(u).then(o,r)}function f(e){return function(){var t=this,n=arguments;return new Promise(function(o,r){var i=e.apply(t,n);function a(e){m(i,o,r,a,c,"next",e)}function c(e){m(i,o,r,a,c,"throw",e)}a(void 0)})}}function d(e,t,n){return(d=x()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var r=new(Function.bind.apply(e,o));return n&&b(r,n.prototype),r}).apply(null,arguments)}function y(e){return(y=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function b(e,t){return(b=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function v(e){var t="function"==typeof Map?new Map:void 0;return(v=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 d(e,arguments,y(this).constructor)}return n.prototype=Object.create(e.prototype,{constructor:{value:n,enumerable:!1,writable:!0,configurable:!0}}),b(n,e)})(e)}function x(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(e){}return(x=function(){return!!e})()}function h(e,t){var n,o,r,i,a={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){var u=[i,c];if(n)throw TypeError("Generator is already executing.");for(;a;)try{if(n=1,o&&(r=2&u[0]?o.return:u[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,u[1])).done)return r;switch(o=0,r&&(u=[2&u[0],r.value]),u[0]){case 0:case 1:r=u;break;case 4:return a.label++,{value:u[1],done:!1};case 5:a.label++,o=u[1],u=[0];continue;case 7:u=a.ops.pop(),a.trys.pop();continue;default:if(!(r=(r=a.trys).length>0&&r[r.length-1])&&(6===u[0]||2===u[0])){a=0;continue}if(3===u[0]&&(!r||u[1]>r[0]&&u[1]<r[3])){a.label=u[1];break}if(6===u[0]&&a.label<r[1]){a.label=r[1],r=u;break}if(r&&a.label<r[2]){a.label=r[2],a.ops.push(u);break}r[2]&&a.ops.pop(),a.trys.pop();continue}u=t.call(e,a)}catch(e){u=[6,e],o=0}finally{n=r=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}}}var g=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;if(!(this instanceof n))throw TypeError("Cannot call a class as a function");return e=n,t=arguments,e=y(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,x()?Reflect.construct(e,t||[],y(this).constructor):e.apply(this,t))}return n.prototype=Object.create(e&&e.prototype,{constructor:{value:n,writable:!0,configurable:!0}}),e&&b(n,e),t=[{key:"afterAdd",value:function(){return f(function(){return h(this,function(e){return[2]})})()}},{key:"beforeLoad",value:function(){return f(function(){return h(this,function(e){return[2]})})()}},{key:"load",value:function(){var e=this;return f(function(){return h(this,function(t){return e.aiPlugin.aiManager.registerLLMProvider("custom-llm",s),[2]})})()}},{key:"aiPlugin",get:function(){return this.app.pm.get("ai")}}],function(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}(n.prototype,t),n}(v(e.Plugin)),S=g}(),l}()});
@@ -12,7 +12,16 @@
12
12
  "Text": "Text",
13
13
  "JSON": "JSON",
14
14
  "Timeout (ms)": "Timeout (ms)",
15
+ "Timeout description": "Request timeout in milliseconds. Increase this for models with long thinking/processing phases. Default: 120000 (2 minutes).",
15
16
  "Max retries": "Max retries",
17
+ "Disable streaming": "Disable streaming",
18
+ "Disable streaming description": "Use non-streaming mode. Enable this for models that have a long \"thinking\" phase before responding, which can cause empty stream values and processing to terminate early.",
19
+ "Stream keepalive": "Stream keepalive",
20
+ "Stream keepalive description": "Keep stream alive during model thinking. Injects placeholder content when no data arrives within the keepalive interval. Works only when streaming is enabled.",
21
+ "Keepalive interval (ms)": "Keepalive interval (ms)",
22
+ "Keepalive interval description": "Interval in milliseconds between keepalive signals. Default: 5000 (5 seconds).",
23
+ "Keepalive content": "Keepalive content",
24
+ "Keepalive content description": "Placeholder text used as keepalive signal (invisible to the user). Default: '...'",
16
25
  "Request config (JSON)": "Request config (JSON)",
17
26
  "Request config description": "Extra configuration for LLM requests. Supported keys: extraHeaders (custom HTTP headers), extraBody (extra request body fields), modelKwargs (LangChain model kwargs).",
18
27
  "Response config (JSON)": "Response config (JSON)",
@@ -12,7 +12,16 @@
12
12
  "Text": "Văn bản",
13
13
  "JSON": "JSON",
14
14
  "Timeout (ms)": "Thời gian chờ (ms)",
15
+ "Timeout description": "Thời gian chờ request tính bằng mili giây. Tăng giá trị này cho các model có giai đoạn thinking/xử lý dài. Mặc định: 120000 (2 phút).",
15
16
  "Max retries": "Số lần thử lại tối đa",
17
+ "Disable streaming": "Tắt streaming",
18
+ "Disable streaming description": "Sử dụng chế độ non-streaming. Bật tính năng này cho các model có giai đoạn \"thinking\" dài trước khi trả lời, gây ra stream rỗng và xử lý bị ngắt sớm.",
19
+ "Stream keepalive": "Giữ kết nối stream",
20
+ "Stream keepalive description": "Giữ stream hoạt động khi model đang thinking. Gửi nội dung giữ kết nối khi không có dữ liệu trong khoảng thời gian đã cấu hình. Chỉ hoạt động khi streaming được bật.",
21
+ "Keepalive interval (ms)": "Khoảng thời gian keepalive (ms)",
22
+ "Keepalive interval description": "Khoảng thời gian giữa các tín hiệu keepalive, tính bằng mili giây. Mặc định: 5000 (5 giây).",
23
+ "Keepalive content": "Nội dung keepalive",
24
+ "Keepalive content description": "Nội dung giữ kết nối (không hiển thị cho người dùng). Mặc định: '...'",
16
25
  "Request config (JSON)": "Cấu hình request (JSON)",
17
26
  "Request config description": "Cấu hình bổ sung cho request LLM. Các key hỗ trợ: extraHeaders (HTTP headers tùy chỉnh), extraBody (thêm trường vào request body), modelKwargs (tham số model LangChain).",
18
27
  "Response config (JSON)": "Cấu hình response (JSON)",
@@ -42,6 +42,7 @@ __export(custom_llm_exports, {
42
42
  module.exports = __toCommonJS(custom_llm_exports);
43
43
  var import_plugin_ai = require("@nocobase/plugin-ai");
44
44
  var import_node_path = __toESM(require("node:path"));
45
+ const KEEPALIVE_PREFIX = "\u200B\u200B\u200B";
45
46
  function requireFromApp(moduleName) {
46
47
  const appNodeModules = process.env.NODE_MODULES_PATH || import_node_path.default.join(process.cwd(), "node_modules");
47
48
  const resolved = require.resolve(moduleName, { paths: [appNodeModules] });
@@ -55,6 +56,22 @@ function getChatOpenAI() {
55
56
  }
56
57
  return _ChatOpenAI;
57
58
  }
59
+ let _ChatGenerationChunk = null;
60
+ function getChatGenerationChunk() {
61
+ if (!_ChatGenerationChunk) {
62
+ const mod = requireFromApp("@langchain/core/outputs");
63
+ _ChatGenerationChunk = mod.ChatGenerationChunk;
64
+ }
65
+ return _ChatGenerationChunk;
66
+ }
67
+ let _AIMessageChunk = null;
68
+ function getAIMessageChunk() {
69
+ if (!_AIMessageChunk) {
70
+ const mod = requireFromApp("@langchain/core/messages");
71
+ _AIMessageChunk = mod.AIMessageChunk;
72
+ }
73
+ return _AIMessageChunk;
74
+ }
58
75
  function stripToolCallTags(content) {
59
76
  if (typeof content !== "string") {
60
77
  return content;
@@ -207,6 +224,120 @@ function createMappingFetch(responseMapping) {
207
224
  return response;
208
225
  };
209
226
  }
227
+ function wrapWithStreamKeepAlive(model, options) {
228
+ const streamMethodName = typeof model._streamResponseChunks === "function" ? "_streamResponseChunks" : "_stream";
229
+ const originalStream = model[streamMethodName].bind(model);
230
+ const { intervalMs, keepAliveContent } = options;
231
+ model[streamMethodName] = async function* (messages, opts, runManager) {
232
+ const ChatGenerationChunk = getChatGenerationChunk();
233
+ const AIMessageChunk = getAIMessageChunk();
234
+ const baseIterator = originalStream(messages, opts, runManager);
235
+ const buffer = [];
236
+ let streamDone = false;
237
+ let streamError = null;
238
+ let notifyReady = null;
239
+ const consumer = (async () => {
240
+ try {
241
+ for await (const chunk of baseIterator) {
242
+ buffer.push(chunk);
243
+ if (notifyReady) {
244
+ notifyReady();
245
+ notifyReady = null;
246
+ }
247
+ }
248
+ } catch (err) {
249
+ streamError = err;
250
+ } finally {
251
+ streamDone = true;
252
+ if (notifyReady) {
253
+ notifyReady();
254
+ notifyReady = null;
255
+ }
256
+ }
257
+ })();
258
+ try {
259
+ while (!streamDone || buffer.length > 0) {
260
+ while (buffer.length > 0) {
261
+ yield buffer.shift();
262
+ }
263
+ if (streamDone) break;
264
+ const waitForChunk = new Promise((resolve) => {
265
+ notifyReady = resolve;
266
+ });
267
+ let timer = null;
268
+ const result = await Promise.race([
269
+ waitForChunk.then(() => "chunk"),
270
+ new Promise((resolve) => {
271
+ timer = setTimeout(() => resolve("timeout"), intervalMs);
272
+ })
273
+ ]);
274
+ if (timer) clearTimeout(timer);
275
+ if (result === "timeout" && !streamDone && buffer.length === 0) {
276
+ const keepAliveChunk = new ChatGenerationChunk({
277
+ message: new AIMessageChunk({ content: KEEPALIVE_PREFIX + keepAliveContent }),
278
+ text: KEEPALIVE_PREFIX + keepAliveContent
279
+ });
280
+ yield keepAliveChunk;
281
+ }
282
+ }
283
+ if (streamError) {
284
+ throw streamError;
285
+ }
286
+ } finally {
287
+ await consumer;
288
+ }
289
+ };
290
+ return model;
291
+ }
292
+ function isKeepAlive(text) {
293
+ return typeof text === "string" && text.startsWith(KEEPALIVE_PREFIX);
294
+ }
295
+ function fixEmptyToolProperties(model) {
296
+ var _a;
297
+ const originalBind = (_a = model.bindTools) == null ? void 0 : _a.bind(model);
298
+ if (!originalBind) return model;
299
+ model.bindTools = function(tools, kwargs) {
300
+ const fixedTools = tools.map((tool) => {
301
+ var _a2, _b;
302
+ if (!tool || typeof tool !== "object") return tool;
303
+ const schema = tool.schema;
304
+ if (schema && typeof schema === "object") {
305
+ const props = schema.properties || (schema == null ? void 0 : schema.shape);
306
+ if (props && typeof props === "object" && Object.keys(props).length === 0) {
307
+ return {
308
+ ...tool,
309
+ schema: {
310
+ ...schema,
311
+ properties: {
312
+ _placeholder: { type: "string", description: "No parameters required" }
313
+ }
314
+ }
315
+ };
316
+ }
317
+ }
318
+ if ((_b = (_a2 = tool.function) == null ? void 0 : _a2.parameters) == null ? void 0 : _b.properties) {
319
+ const params = tool.function.parameters;
320
+ if (typeof params.properties === "object" && Object.keys(params.properties).length === 0) {
321
+ return {
322
+ ...tool,
323
+ function: {
324
+ ...tool.function,
325
+ parameters: {
326
+ ...params,
327
+ properties: {
328
+ _placeholder: { type: "string", description: "No parameters required" }
329
+ }
330
+ }
331
+ }
332
+ };
333
+ }
334
+ }
335
+ return tool;
336
+ });
337
+ return originalBind(fixedTools, kwargs);
338
+ };
339
+ return model;
340
+ }
210
341
  class CustomLLMProvider extends import_plugin_ai.LLMProvider {
211
342
  get baseURL() {
212
343
  return null;
@@ -220,7 +351,9 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
220
351
  return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.responseConfig);
221
352
  }
222
353
  createModel() {
223
- const { baseURL, apiKey } = this.serviceOptions || {};
354
+ var _a;
355
+ const { apiKey, disableStream, timeout, streamKeepAlive, keepAliveIntervalMs, keepAliveContent } = this.serviceOptions || {};
356
+ const baseURL = (_a = this.serviceOptions) == null ? void 0 : _a.baseURL;
224
357
  const { responseFormat } = this.modelOptions || {};
225
358
  const reqConfig = this.requestConfig;
226
359
  const resConfig = this.responseConfig;
@@ -244,17 +377,35 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
244
377
  },
245
378
  verbose: false
246
379
  };
380
+ if (disableStream) {
381
+ config.streaming = false;
382
+ }
383
+ if (timeout && Number(timeout) > 0) {
384
+ config.timeout = Number(timeout);
385
+ config.configuration.timeout = Number(timeout);
386
+ }
247
387
  if (reqConfig.extraHeaders && typeof reqConfig.extraHeaders === "object") {
248
388
  config.configuration.defaultHeaders = reqConfig.extraHeaders;
249
389
  }
250
390
  if (resConfig.responseMapping) {
251
391
  config.configuration.fetch = createMappingFetch(resConfig.responseMapping);
252
392
  }
253
- return new ChatOpenAI(config);
393
+ let model = new ChatOpenAI(config);
394
+ model = fixEmptyToolProperties(model);
395
+ if (streamKeepAlive && !disableStream) {
396
+ return wrapWithStreamKeepAlive(model, {
397
+ intervalMs: Number(keepAliveIntervalMs) || 5e3,
398
+ keepAliveContent: keepAliveContent || "..."
399
+ });
400
+ }
401
+ return model;
254
402
  }
255
403
  parseResponseChunk(chunk) {
256
404
  const resConfig = this.responseConfig;
257
405
  const text = extractTextContent(chunk, resConfig.contentPath);
406
+ if (isKeepAlive(text)) {
407
+ return null;
408
+ }
258
409
  return stripToolCallTags(text);
259
410
  }
260
411
  parseResponseMessage(message) {
@@ -274,6 +425,8 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
274
425
  content.content = textBlocks.map((block) => block.text).join("") || "";
275
426
  }
276
427
  if (typeof content.content === "string") {
428
+ const escapedPrefix = KEEPALIVE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
429
+ content.content = content.content.replace(new RegExp(escapedPrefix + ".*?(?=" + escapedPrefix + "|$)", "g"), "");
277
430
  content.content = stripToolCallTags(content.content);
278
431
  }
279
432
  return {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "displayName": "AI LLM: Custom (OpenAI Compatible)",
4
4
  "displayName.zh-CN": "AI LLM:自定义(OpenAI 兼容)",
5
5
  "description": "OpenAI-compatible LLM provider with auto response format detection for external LLM services.",
6
- "version": "1.0.1",
6
+ "version": "1.1.1",
7
7
  "main": "dist/server/index.js",
8
8
  "nocobase": {
9
9
  "supportedVersions": [
Binary file
Binary file