plugin-custom-llm 1.1.1 → 1.2.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.
- package/README.md +28 -1
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.js +0 -9
- package/dist/client/llm-providers/custom-llm/ModelSettings.d.ts +2 -0
- package/dist/client/llm-providers/custom-llm/ProviderSettings.d.ts +2 -0
- package/dist/client/llm-providers/custom-llm/index.d.ts +2 -0
- package/dist/client/locale.d.ts +2 -0
- package/dist/client/models/index.d.ts +10 -0
- package/dist/client/plugin.d.ts +5 -0
- package/dist/externalVersion.js +9 -16
- package/dist/index.d.ts +2 -0
- package/dist/index.js +0 -9
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +0 -9
- package/dist/server/llm-providers/custom-llm.d.ts +54 -0
- package/dist/server/llm-providers/custom-llm.js +220 -35
- package/dist/server/plugin.d.ts +12 -0
- package/dist/server/plugin.js +0 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ NocoBase plugin for integrating external LLM providers that support OpenAI-compa
|
|
|
7
7
|
- **OpenAI-compatible**: Works with any LLM provider exposing `/chat/completions` endpoint
|
|
8
8
|
- **Auto content detection**: Handles both string and array content blocks (`[{type: 'text', text: '...'}]`)
|
|
9
9
|
- **Response mapping**: Transform non-standard API responses to OpenAI format via JSON config (supports streaming SSE and JSON)
|
|
10
|
-
- **Reasoning content**: Display thinking/reasoning from DeepSeek-compatible providers
|
|
10
|
+
- **Reasoning content**: Display thinking/reasoning from DeepSeek-compatible providers (multi-path detection)
|
|
11
|
+
- **Stream keepalive**: Prevent proxy/gateway timeouts during long model thinking phases
|
|
12
|
+
- **Tool calling support**: Gemini-compatible tool schema fixing (Zod + JSON Schema)
|
|
11
13
|
- **Configurable**: JSON config editors for request and response customization
|
|
12
14
|
- **Locale support**: English, Vietnamese, Chinese
|
|
13
15
|
|
|
@@ -23,6 +25,11 @@ Upload `plugin-custom-llm-x.x.x.tgz` via NocoBase Plugin Manager UI, then enable
|
|
|
23
25
|
|---|---|
|
|
24
26
|
| **Base URL** | LLM endpoint URL, e.g. `https://your-llm-server.com/v1` |
|
|
25
27
|
| **API Key** | Authentication key |
|
|
28
|
+
| **Disable Streaming** | Disable streaming for models that return empty stream values |
|
|
29
|
+
| **Stream Keep Alive** | Enable keepalive to prevent timeouts during long thinking phases |
|
|
30
|
+
| **Keep Alive Interval** | Interval in ms between keepalive signals (default: 5000) |
|
|
31
|
+
| **Keep Alive Content** | Visual indicator text during keepalive (default: `...`) |
|
|
32
|
+
| **Timeout** | Custom timeout in ms for slow-responding models |
|
|
26
33
|
| **Request config (JSON)** | Optional. Extra request configuration |
|
|
27
34
|
| **Response config (JSON)** | Optional. Response parsing and mapping configuration |
|
|
28
35
|
|
|
@@ -72,6 +79,26 @@ Upload `plugin-custom-llm-x.x.x.tgz` via NocoBase Plugin Manager UI, then enable
|
|
|
72
79
|
|
|
73
80
|
Standard OpenAI-compatible parameters: temperature, max tokens, top P, frequency/presence penalty, response format, timeout, max retries.
|
|
74
81
|
|
|
82
|
+
## Changelog
|
|
83
|
+
|
|
84
|
+
### v1.2.0
|
|
85
|
+
|
|
86
|
+
- **Fix**: Keepalive no longer interferes with tool call sequences (prevents tool call corruption)
|
|
87
|
+
- **Fix**: Gemini-compatible tool schema fixing — handles Zod schemas via dual-phase approach (pre/post conversion)
|
|
88
|
+
- **Fix**: Keepalive content no longer contaminates saved messages in DB
|
|
89
|
+
- **Fix**: Response metadata extraction with long ID sanitization (>128 chars truncated)
|
|
90
|
+
- **Fix**: Multi-path reasoning content detection (`additional_kwargs` + `kwargs.additional_kwargs`)
|
|
91
|
+
- **Fix**: Improved error recovery in keepalive consumer (immediate error propagation)
|
|
92
|
+
|
|
93
|
+
### v1.1.1
|
|
94
|
+
|
|
95
|
+
- Stream keepalive proxy for long thinking phases
|
|
96
|
+
- Response mapping for non-standard LLM APIs
|
|
97
|
+
|
|
98
|
+
### v1.0.0
|
|
99
|
+
|
|
100
|
+
- Initial release with OpenAI-compatible LLM provider support
|
|
101
|
+
|
|
75
102
|
## License
|
|
76
103
|
|
|
77
104
|
Apache-2.0
|
package/dist/client/index.js
CHANGED
|
@@ -1,10 +1 @@
|
|
|
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
1
|
!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}()});
|
|
@@ -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;
|
package/dist/externalVersion.js
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
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
1
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.0.
|
|
12
|
-
"@nocobase/plugin-ai": "2.0.
|
|
2
|
+
"@nocobase/client": "2.0.20",
|
|
3
|
+
"@nocobase/plugin-ai": "2.0.20",
|
|
13
4
|
"react-i18next": "11.18.6",
|
|
14
|
-
"@nocobase/server": "2.0.
|
|
15
|
-
"@nocobase/flow-engine": "2.0.
|
|
16
|
-
"@nocobase/database": "2.0.
|
|
17
|
-
"
|
|
18
|
-
"@nocobase/
|
|
5
|
+
"@nocobase/server": "2.0.20",
|
|
6
|
+
"@nocobase/flow-engine": "2.0.20",
|
|
7
|
+
"@nocobase/database": "2.0.20",
|
|
8
|
+
"axios": "1.14.0",
|
|
9
|
+
"@nocobase/actions": "2.0.20",
|
|
10
|
+
"react": "18.3.1",
|
|
11
|
+
"@nocobase/utils": "2.0.20",
|
|
19
12
|
"antd": "5.24.2"
|
|
20
13
|
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
var __create = Object.create;
|
|
11
2
|
var __defProp = Object.defineProperty;
|
|
12
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './plugin';
|
package/dist/server/index.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
var __create = Object.create;
|
|
11
2
|
var __defProp = Object.defineProperty;
|
|
12
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { LLMProvider, LLMProviderMeta } from '@nocobase/plugin-ai';
|
|
2
|
+
import { Model } from '@nocobase/database';
|
|
3
|
+
import { Context } from '@nocobase/actions';
|
|
4
|
+
import type { ParsedAttachmentResult } from '@nocobase/plugin-ai';
|
|
5
|
+
export declare class CustomLLMProvider extends LLMProvider {
|
|
6
|
+
get baseURL(): any;
|
|
7
|
+
private get requestConfig();
|
|
8
|
+
private get responseConfig();
|
|
9
|
+
createModel(): any;
|
|
10
|
+
parseResponseChunk(chunk: any): string | null;
|
|
11
|
+
parseResponseMessage(message: Model): {
|
|
12
|
+
key: Model;
|
|
13
|
+
content: Record<string, any>;
|
|
14
|
+
role: Model;
|
|
15
|
+
};
|
|
16
|
+
parseReasoningContent(chunk: any): {
|
|
17
|
+
status: string;
|
|
18
|
+
content: string;
|
|
19
|
+
} | null;
|
|
20
|
+
/**
|
|
21
|
+
* Extract response metadata from LLM output for post-save enrichment.
|
|
22
|
+
* Sanitizes overly long message IDs from Gemini or other providers.
|
|
23
|
+
*/
|
|
24
|
+
parseResponseMetadata(output: any): any;
|
|
25
|
+
parseResponseError(err: any): any;
|
|
26
|
+
/**
|
|
27
|
+
* Self-contained file reading that correctly handles the APP_PUBLIC_PATH prefix.
|
|
28
|
+
*
|
|
29
|
+
* plugin-ai's encodeLocalFile does path.join(cwd, url) without stripping
|
|
30
|
+
* APP_PUBLIC_PATH, so when the app is deployed under a sub-path (e.g. /my-app)
|
|
31
|
+
* the resolved path becomes '{cwd}/my-app/storage/uploads/…' which does not exist.
|
|
32
|
+
* We cannot fix that in plugin-ai (core), so we re-implement file reading here
|
|
33
|
+
* with the prefix stripped before the cwd join.
|
|
34
|
+
*/
|
|
35
|
+
private readFileAsBase64;
|
|
36
|
+
/**
|
|
37
|
+
* Override parseAttachment to convert all attachments into formats that
|
|
38
|
+
* generic OpenAI-compatible endpoints actually support:
|
|
39
|
+
*
|
|
40
|
+
* - Images → image_url block with base64 data URI (vision models)
|
|
41
|
+
* - Text files → text block with decoded UTF-8 content
|
|
42
|
+
* - Binary → text block with base64 data URI (multi-modal or fallback)
|
|
43
|
+
*
|
|
44
|
+
* The base-class implementation returns a LangChain ContentBlock.Multimodal.File
|
|
45
|
+
* (`type: 'file'`) for non-image attachments. LangChain serialises this as the
|
|
46
|
+
* newer OpenAI Files API format which most custom/local endpoints do NOT understand,
|
|
47
|
+
* causing file content to be silently dropped.
|
|
48
|
+
*
|
|
49
|
+
* This method is entirely self-contained — it does not call super — so it is
|
|
50
|
+
* safe to use without modifying plugin-ai core.
|
|
51
|
+
*/
|
|
52
|
+
parseAttachment(ctx: Context, attachment: any): Promise<ParsedAttachmentResult>;
|
|
53
|
+
}
|
|
54
|
+
export declare const customLLMProviderOptions: LLMProviderMeta;
|
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
var __create = Object.create;
|
|
11
2
|
var __defProp = Object.defineProperty;
|
|
12
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -42,6 +33,8 @@ __export(custom_llm_exports, {
|
|
|
42
33
|
module.exports = __toCommonJS(custom_llm_exports);
|
|
43
34
|
var import_plugin_ai = require("@nocobase/plugin-ai");
|
|
44
35
|
var import_node_path = __toESM(require("node:path"));
|
|
36
|
+
var import_promises = __toESM(require("node:fs/promises"));
|
|
37
|
+
var import_axios = __toESM(require("axios"));
|
|
45
38
|
const KEEPALIVE_PREFIX = "\u200B\u200B\u200B";
|
|
46
39
|
function requireFromApp(moduleName) {
|
|
47
40
|
const appNodeModules = process.env.NODE_MODULES_PATH || import_node_path.default.join(process.cwd(), "node_modules");
|
|
@@ -100,11 +93,41 @@ function extractTextContent(content, contentPath) {
|
|
|
100
93
|
}
|
|
101
94
|
return "";
|
|
102
95
|
}
|
|
103
|
-
function
|
|
96
|
+
function isTextMimetype(mimetype) {
|
|
97
|
+
if (!mimetype) return false;
|
|
98
|
+
if (mimetype.startsWith("text/")) return true;
|
|
99
|
+
const TEXT_APPLICATION_TYPES = /* @__PURE__ */ new Set([
|
|
100
|
+
"application/json",
|
|
101
|
+
"application/xml",
|
|
102
|
+
"application/xhtml+xml",
|
|
103
|
+
"application/atom+xml",
|
|
104
|
+
"application/rss+xml",
|
|
105
|
+
"application/csv",
|
|
106
|
+
"application/javascript",
|
|
107
|
+
"application/typescript",
|
|
108
|
+
"application/x-javascript",
|
|
109
|
+
"application/x-typescript",
|
|
110
|
+
"application/x-yaml",
|
|
111
|
+
"application/yaml",
|
|
112
|
+
"application/x-json",
|
|
113
|
+
"application/geo+json",
|
|
114
|
+
"application/ld+json",
|
|
115
|
+
"application/manifest+json",
|
|
116
|
+
"application/graphql",
|
|
117
|
+
"application/x-www-form-urlencoded",
|
|
118
|
+
"application/toml",
|
|
119
|
+
"application/x-sh",
|
|
120
|
+
"application/x-shellscript",
|
|
121
|
+
"application/sql"
|
|
122
|
+
]);
|
|
123
|
+
return TEXT_APPLICATION_TYPES.has(mimetype);
|
|
124
|
+
}
|
|
125
|
+
function safeParseJSON(str, fieldName) {
|
|
104
126
|
if (!str || typeof str !== "string") return {};
|
|
105
127
|
try {
|
|
106
128
|
return JSON.parse(str);
|
|
107
|
-
} catch {
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.warn(`[CustomLLM] Failed to parse ${fieldName || "JSON config"}: ${e.message}`);
|
|
108
131
|
return {};
|
|
109
132
|
}
|
|
110
133
|
}
|
|
@@ -170,13 +193,13 @@ function createMappingFetch(responseMapping) {
|
|
|
170
193
|
|
|
171
194
|
`));
|
|
172
195
|
} else {
|
|
173
|
-
controller.enqueue(encoder.encode(line + "\n"));
|
|
196
|
+
controller.enqueue(encoder.encode(line + "\n\n"));
|
|
174
197
|
}
|
|
175
198
|
} catch {
|
|
176
|
-
controller.enqueue(encoder.encode(line + "\n"));
|
|
199
|
+
controller.enqueue(encoder.encode(line + "\n\n"));
|
|
177
200
|
}
|
|
178
201
|
} else {
|
|
179
|
-
controller.enqueue(encoder.encode(line + "\n"));
|
|
202
|
+
controller.enqueue(encoder.encode(line + "\n\n"));
|
|
180
203
|
}
|
|
181
204
|
}
|
|
182
205
|
}
|
|
@@ -236,9 +259,16 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
236
259
|
let streamDone = false;
|
|
237
260
|
let streamError = null;
|
|
238
261
|
let notifyReady = null;
|
|
262
|
+
let hasToolCallChunks = false;
|
|
263
|
+
let hasErrored = false;
|
|
239
264
|
const consumer = (async () => {
|
|
265
|
+
var _a, _b;
|
|
240
266
|
try {
|
|
241
267
|
for await (const chunk of baseIterator) {
|
|
268
|
+
const msg = chunk == null ? void 0 : chunk.message;
|
|
269
|
+
if (((_a = msg == null ? void 0 : msg.tool_call_chunks) == null ? void 0 : _a.length) || ((_b = msg == null ? void 0 : msg.tool_calls) == null ? void 0 : _b.length)) {
|
|
270
|
+
hasToolCallChunks = true;
|
|
271
|
+
}
|
|
242
272
|
buffer.push(chunk);
|
|
243
273
|
if (notifyReady) {
|
|
244
274
|
notifyReady();
|
|
@@ -247,6 +277,11 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
247
277
|
}
|
|
248
278
|
} catch (err) {
|
|
249
279
|
streamError = err;
|
|
280
|
+
hasErrored = true;
|
|
281
|
+
if (notifyReady) {
|
|
282
|
+
notifyReady();
|
|
283
|
+
notifyReady = null;
|
|
284
|
+
}
|
|
250
285
|
} finally {
|
|
251
286
|
streamDone = true;
|
|
252
287
|
if (notifyReady) {
|
|
@@ -260,6 +295,7 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
260
295
|
while (buffer.length > 0) {
|
|
261
296
|
yield buffer.shift();
|
|
262
297
|
}
|
|
298
|
+
hasToolCallChunks = false;
|
|
263
299
|
if (streamDone) break;
|
|
264
300
|
const waitForChunk = new Promise((resolve) => {
|
|
265
301
|
notifyReady = resolve;
|
|
@@ -273,9 +309,14 @@ function wrapWithStreamKeepAlive(model, options) {
|
|
|
273
309
|
]);
|
|
274
310
|
if (timer) clearTimeout(timer);
|
|
275
311
|
if (result === "timeout" && !streamDone && buffer.length === 0) {
|
|
312
|
+
if (streamError || hasErrored) break;
|
|
313
|
+
if (hasToolCallChunks) continue;
|
|
276
314
|
const keepAliveChunk = new ChatGenerationChunk({
|
|
277
|
-
message: new AIMessageChunk({
|
|
278
|
-
|
|
315
|
+
message: new AIMessageChunk({
|
|
316
|
+
content: KEEPALIVE_PREFIX,
|
|
317
|
+
additional_kwargs: { __keepalive: true }
|
|
318
|
+
}),
|
|
319
|
+
text: KEEPALIVE_PREFIX
|
|
279
320
|
});
|
|
280
321
|
yield keepAliveChunk;
|
|
281
322
|
}
|
|
@@ -296,37 +337,51 @@ function fixEmptyToolProperties(model) {
|
|
|
296
337
|
var _a;
|
|
297
338
|
const originalBind = (_a = model.bindTools) == null ? void 0 : _a.bind(model);
|
|
298
339
|
if (!originalBind) return model;
|
|
340
|
+
const PLACEHOLDER_PROP = {
|
|
341
|
+
_placeholder: { type: "string", description: "No parameters required" }
|
|
342
|
+
};
|
|
343
|
+
function fixPropertiesInSchema(schema) {
|
|
344
|
+
if (!schema || typeof schema !== "object") return;
|
|
345
|
+
if (schema.properties && typeof schema.properties === "object" && Object.keys(schema.properties).length === 0) {
|
|
346
|
+
schema.properties = { ...PLACEHOLDER_PROP };
|
|
347
|
+
}
|
|
348
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
349
|
+
if (Array.isArray(schema[key])) {
|
|
350
|
+
schema[key].forEach((sub) => fixPropertiesInSchema(sub));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
299
354
|
model.bindTools = function(tools, kwargs) {
|
|
355
|
+
var _a2;
|
|
300
356
|
const fixedTools = tools.map((tool) => {
|
|
301
|
-
var
|
|
357
|
+
var _a3, _b;
|
|
302
358
|
if (!tool || typeof tool !== "object") return tool;
|
|
359
|
+
if (typeof ((_a3 = tool.schema) == null ? void 0 : _a3.safeParse) === "function") {
|
|
360
|
+
return tool;
|
|
361
|
+
}
|
|
303
362
|
const schema = tool.schema;
|
|
304
|
-
if (schema && typeof schema === "object") {
|
|
305
|
-
const props = schema.properties
|
|
363
|
+
if (schema && typeof schema === "object" && !schema.safeParse) {
|
|
364
|
+
const props = schema.properties;
|
|
306
365
|
if (props && typeof props === "object" && Object.keys(props).length === 0) {
|
|
307
366
|
return {
|
|
308
367
|
...tool,
|
|
309
368
|
schema: {
|
|
310
369
|
...schema,
|
|
311
|
-
properties: {
|
|
312
|
-
_placeholder: { type: "string", description: "No parameters required" }
|
|
313
|
-
}
|
|
370
|
+
properties: { ...PLACEHOLDER_PROP }
|
|
314
371
|
}
|
|
315
372
|
};
|
|
316
373
|
}
|
|
317
374
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (typeof
|
|
375
|
+
const funcParams = (_b = tool.function) == null ? void 0 : _b.parameters;
|
|
376
|
+
if (funcParams == null ? void 0 : funcParams.properties) {
|
|
377
|
+
if (typeof funcParams.properties === "object" && Object.keys(funcParams.properties).length === 0) {
|
|
321
378
|
return {
|
|
322
379
|
...tool,
|
|
323
380
|
function: {
|
|
324
381
|
...tool.function,
|
|
325
382
|
parameters: {
|
|
326
|
-
...
|
|
327
|
-
properties: {
|
|
328
|
-
_placeholder: { type: "string", description: "No parameters required" }
|
|
329
|
-
}
|
|
383
|
+
...funcParams,
|
|
384
|
+
properties: { ...PLACEHOLDER_PROP }
|
|
330
385
|
}
|
|
331
386
|
}
|
|
332
387
|
};
|
|
@@ -334,7 +389,22 @@ function fixEmptyToolProperties(model) {
|
|
|
334
389
|
}
|
|
335
390
|
return tool;
|
|
336
391
|
});
|
|
337
|
-
|
|
392
|
+
const result = originalBind(fixedTools, kwargs);
|
|
393
|
+
try {
|
|
394
|
+
const config = (result == null ? void 0 : result.kwargs) ?? (result == null ? void 0 : result.defaultOptions);
|
|
395
|
+
if ((config == null ? void 0 : config.tools) && Array.isArray(config.tools)) {
|
|
396
|
+
for (const tool of config.tools) {
|
|
397
|
+
if ((_a2 = tool == null ? void 0 : tool.function) == null ? void 0 : _a2.parameters) {
|
|
398
|
+
fixPropertiesInSchema(tool.function.parameters);
|
|
399
|
+
}
|
|
400
|
+
if (tool == null ? void 0 : tool.parameters) {
|
|
401
|
+
fixPropertiesInSchema(tool.parameters);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
return result;
|
|
338
408
|
};
|
|
339
409
|
return model;
|
|
340
410
|
}
|
|
@@ -344,11 +414,11 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
344
414
|
}
|
|
345
415
|
get requestConfig() {
|
|
346
416
|
var _a;
|
|
347
|
-
return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.requestConfig);
|
|
417
|
+
return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.requestConfig, "requestConfig");
|
|
348
418
|
}
|
|
349
419
|
get responseConfig() {
|
|
350
420
|
var _a;
|
|
351
|
-
return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.responseConfig);
|
|
421
|
+
return safeParseJSON((_a = this.serviceOptions) == null ? void 0 : _a.responseConfig, "responseConfig");
|
|
352
422
|
}
|
|
353
423
|
createModel() {
|
|
354
424
|
var _a;
|
|
@@ -404,11 +474,12 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
404
474
|
const resConfig = this.responseConfig;
|
|
405
475
|
const text = extractTextContent(chunk, resConfig.contentPath);
|
|
406
476
|
if (isKeepAlive(text)) {
|
|
407
|
-
return
|
|
477
|
+
return KEEPALIVE_PREFIX;
|
|
408
478
|
}
|
|
409
479
|
return stripToolCallTags(text);
|
|
410
480
|
}
|
|
411
481
|
parseResponseMessage(message) {
|
|
482
|
+
var _a, _b;
|
|
412
483
|
const { content: rawContent, messageId, metadata, role, toolCalls, attachments, workContext } = message;
|
|
413
484
|
const content = {
|
|
414
485
|
...rawContent ?? {},
|
|
@@ -429,6 +500,10 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
429
500
|
content.content = content.content.replace(new RegExp(escapedPrefix + ".*?(?=" + escapedPrefix + "|$)", "g"), "");
|
|
430
501
|
content.content = stripToolCallTags(content.content);
|
|
431
502
|
}
|
|
503
|
+
if (((_b = (_a = content.metadata) == null ? void 0 : _a.additional_kwargs) == null ? void 0 : _b.__keepalive) !== void 0) {
|
|
504
|
+
const { __keepalive, ...cleanKwargs } = content.metadata.additional_kwargs;
|
|
505
|
+
content.metadata = { ...content.metadata, additional_kwargs: cleanKwargs };
|
|
506
|
+
}
|
|
432
507
|
return {
|
|
433
508
|
key: messageId,
|
|
434
509
|
content,
|
|
@@ -436,18 +511,128 @@ class CustomLLMProvider extends import_plugin_ai.LLMProvider {
|
|
|
436
511
|
};
|
|
437
512
|
}
|
|
438
513
|
parseReasoningContent(chunk) {
|
|
439
|
-
var _a;
|
|
514
|
+
var _a, _b, _c;
|
|
440
515
|
const resConfig = this.responseConfig;
|
|
441
516
|
const reasoningKey = resConfig.reasoningKey || "reasoning_content";
|
|
442
|
-
const reasoning = (_a = chunk == null ? void 0 : chunk.additional_kwargs) == null ? void 0 : _a[reasoningKey];
|
|
517
|
+
const reasoning = ((_a = chunk == null ? void 0 : chunk.additional_kwargs) == null ? void 0 : _a[reasoningKey]) ?? ((_c = (_b = chunk == null ? void 0 : chunk.kwargs) == null ? void 0 : _b.additional_kwargs) == null ? void 0 : _c[reasoningKey]);
|
|
443
518
|
if (reasoning && typeof reasoning === "string") {
|
|
444
519
|
return { status: "streaming", content: reasoning };
|
|
445
520
|
}
|
|
446
521
|
return null;
|
|
447
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Extract response metadata from LLM output for post-save enrichment.
|
|
525
|
+
* Sanitizes overly long message IDs from Gemini or other providers.
|
|
526
|
+
*/
|
|
527
|
+
parseResponseMetadata(output) {
|
|
528
|
+
var _a, _b;
|
|
529
|
+
try {
|
|
530
|
+
const generation = (_b = (_a = output == null ? void 0 : output.generations) == null ? void 0 : _a[0]) == null ? void 0 : _b[0];
|
|
531
|
+
if (!generation) return [null, null];
|
|
532
|
+
const message = generation.message;
|
|
533
|
+
let id = message == null ? void 0 : message.id;
|
|
534
|
+
if (!id) return [null, null];
|
|
535
|
+
if (typeof id === "string" && id.length > 128) {
|
|
536
|
+
id = id.substring(0, 128);
|
|
537
|
+
}
|
|
538
|
+
const metadata = {};
|
|
539
|
+
if (message == null ? void 0 : message.response_metadata) {
|
|
540
|
+
metadata.finish_reason = message.response_metadata.finish_reason;
|
|
541
|
+
metadata.system_fingerprint = message.response_metadata.system_fingerprint;
|
|
542
|
+
}
|
|
543
|
+
if (message == null ? void 0 : message.usage_metadata) {
|
|
544
|
+
metadata.usage_metadata = message.usage_metadata;
|
|
545
|
+
}
|
|
546
|
+
return Object.keys(metadata).length > 0 ? [id, metadata] : [null, null];
|
|
547
|
+
} catch {
|
|
548
|
+
return [null, null];
|
|
549
|
+
}
|
|
550
|
+
}
|
|
448
551
|
parseResponseError(err) {
|
|
449
552
|
return (err == null ? void 0 : err.message) ?? "Unexpected LLM service error";
|
|
450
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Self-contained file reading that correctly handles the APP_PUBLIC_PATH prefix.
|
|
556
|
+
*
|
|
557
|
+
* plugin-ai's encodeLocalFile does path.join(cwd, url) without stripping
|
|
558
|
+
* APP_PUBLIC_PATH, so when the app is deployed under a sub-path (e.g. /my-app)
|
|
559
|
+
* the resolved path becomes '{cwd}/my-app/storage/uploads/…' which does not exist.
|
|
560
|
+
* We cannot fix that in plugin-ai (core), so we re-implement file reading here
|
|
561
|
+
* with the prefix stripped before the cwd join.
|
|
562
|
+
*/
|
|
563
|
+
async readFileAsBase64(ctx, attachment) {
|
|
564
|
+
const fileManager = this.app.pm.get("file-manager");
|
|
565
|
+
const rawUrl = await fileManager.getFileURL(attachment);
|
|
566
|
+
const url = decodeURIComponent(rawUrl);
|
|
567
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
568
|
+
const referer = ctx.get("referer") || "";
|
|
569
|
+
const ua = ctx.get("user-agent") || "";
|
|
570
|
+
const response = await import_axios.default.get(url, {
|
|
571
|
+
responseType: "arraybuffer",
|
|
572
|
+
// Default 30s timeout to prevent slow file servers from blocking indefinitely
|
|
573
|
+
timeout: 3e4,
|
|
574
|
+
headers: { referer, "User-Agent": ua }
|
|
575
|
+
});
|
|
576
|
+
return Buffer.from(response.data).toString("base64");
|
|
577
|
+
}
|
|
578
|
+
let localPath = url;
|
|
579
|
+
const appPublicPath = (process.env.APP_PUBLIC_PATH || "/").replace(/\/+$/, "");
|
|
580
|
+
if (appPublicPath && localPath.startsWith(appPublicPath + "/")) {
|
|
581
|
+
localPath = localPath.slice(appPublicPath.length);
|
|
582
|
+
}
|
|
583
|
+
const storageRoot = import_node_path.default.resolve(process.cwd());
|
|
584
|
+
const absPath = import_node_path.default.resolve(storageRoot, localPath.replace(/^\//, ""));
|
|
585
|
+
if (!absPath.startsWith(storageRoot + import_node_path.default.sep) && absPath !== storageRoot) {
|
|
586
|
+
throw new Error(`Attachment path escapes storage root: ${localPath}`);
|
|
587
|
+
}
|
|
588
|
+
const data = await import_promises.default.readFile(absPath);
|
|
589
|
+
return Buffer.from(data).toString("base64");
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Override parseAttachment to convert all attachments into formats that
|
|
593
|
+
* generic OpenAI-compatible endpoints actually support:
|
|
594
|
+
*
|
|
595
|
+
* - Images → image_url block with base64 data URI (vision models)
|
|
596
|
+
* - Text files → text block with decoded UTF-8 content
|
|
597
|
+
* - Binary → text block with base64 data URI (multi-modal or fallback)
|
|
598
|
+
*
|
|
599
|
+
* The base-class implementation returns a LangChain ContentBlock.Multimodal.File
|
|
600
|
+
* (`type: 'file'`) for non-image attachments. LangChain serialises this as the
|
|
601
|
+
* newer OpenAI Files API format which most custom/local endpoints do NOT understand,
|
|
602
|
+
* causing file content to be silently dropped.
|
|
603
|
+
*
|
|
604
|
+
* This method is entirely self-contained — it does not call super — so it is
|
|
605
|
+
* safe to use without modifying plugin-ai core.
|
|
606
|
+
*/
|
|
607
|
+
async parseAttachment(ctx, attachment) {
|
|
608
|
+
const mimetype = attachment.mimetype || "application/octet-stream";
|
|
609
|
+
const filename = attachment.filename || attachment.name || "file";
|
|
610
|
+
const data = await this.readFileAsBase64(ctx, attachment);
|
|
611
|
+
if (mimetype.startsWith("image/")) {
|
|
612
|
+
return {
|
|
613
|
+
placement: "contentBlocks",
|
|
614
|
+
content: {
|
|
615
|
+
type: "image_url",
|
|
616
|
+
image_url: { url: `data:${mimetype};base64,${data}` }
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
let textContent;
|
|
621
|
+
if (isTextMimetype(mimetype)) {
|
|
622
|
+
const decoded = Buffer.from(data, "base64").toString("utf-8");
|
|
623
|
+
textContent = `<attachment filename="${filename}" type="${mimetype}">
|
|
624
|
+
${decoded}
|
|
625
|
+
</attachment>`;
|
|
626
|
+
} else {
|
|
627
|
+
textContent = `<attachment filename="${filename}" type="${mimetype}">
|
|
628
|
+
data:${mimetype};base64,${data}
|
|
629
|
+
</attachment>`;
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
placement: "contentBlocks",
|
|
633
|
+
content: { type: "text", text: textContent }
|
|
634
|
+
};
|
|
635
|
+
}
|
|
451
636
|
}
|
|
452
637
|
const customLLMProviderOptions = {
|
|
453
638
|
title: "Custom LLM (OpenAI Compatible)",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/server';
|
|
2
|
+
export declare class PluginCustomLLMServer extends Plugin {
|
|
3
|
+
afterAdd(): Promise<void>;
|
|
4
|
+
beforeLoad(): Promise<void>;
|
|
5
|
+
load(): Promise<void>;
|
|
6
|
+
install(): Promise<void>;
|
|
7
|
+
afterEnable(): Promise<void>;
|
|
8
|
+
afterDisable(): Promise<void>;
|
|
9
|
+
remove(): Promise<void>;
|
|
10
|
+
private get aiPlugin();
|
|
11
|
+
}
|
|
12
|
+
export default PluginCustomLLMServer;
|
package/dist/server/plugin.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
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.
|
|
6
|
+
"version": "1.2.1",
|
|
7
7
|
"main": "dist/server/index.js",
|
|
8
8
|
"nocobase": {
|
|
9
9
|
"supportedVersions": [
|