iobroker.javascript 9.2.0 → 9.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.
Files changed (43) hide show
  1. package/README.md +18 -9
  2. package/admin/assets/AiChatPanel-Cc1KscBJ.js +315 -0
  3. package/admin/assets/AiInlineProvider-DkgjVSFQ.js +7 -0
  4. package/admin/assets/ScriptEditor-6qrtvwPL.js +2 -0
  5. package/admin/assets/ScriptEditorVanillaMonaco-bJua4sm1.js +3 -0
  6. package/admin/assets/applyCodeEdit-BdRnbz2g.js +1 -0
  7. package/admin/assets/{index-DAkyON1r.js → index-BZxCaQhO.js} +1 -1
  8. package/admin/assets/{index-DW5CwIma.js → index-CYbVtccs.js} +132 -121
  9. package/admin/assets/index-CexrwGGC.js +986 -0
  10. package/admin/assets/{index-BI3JsAz_.js → index-DsW7vBcg.js} +1 -1
  11. package/admin/assets/inlineChatWidget-Cu6rb4Ar.js +2 -0
  12. package/admin/assets/inlineDiffController-DBLpPOe2.js +16 -0
  13. package/admin/assets/{localSharedImportMap-CqvxvyGG.js → localSharedImportMap-BCwqnmn4.js} +1 -1
  14. package/admin/assets/stateHoverProvider-CGChzV5W.js +3 -0
  15. package/admin/assets/{virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-BJ03G6Pu.js → virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-DNrEsMrg.js} +2 -2
  16. package/admin/custom/assets/{index-BVwMgafs.js → index-DNKzDki_.js} +152 -152
  17. package/admin/custom/assets/{index-DsIn9jH_.js → index-NItS-TWp.js} +1 -1
  18. package/admin/{assets/index-hwplVCgv.js → custom/assets/index-sozs10Ij.js} +45 -71
  19. package/admin/custom/assets/{localSharedImportMap-C4qXtGUd.js → localSharedImportMap-BxT0VwTO.js} +1 -1
  20. package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-DFSXB0v2.js → virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-Br3IlnqO.js} +2 -2
  21. package/admin/custom/customComponents.js +1 -1
  22. package/admin/jsonConfig.json +9 -9
  23. package/admin/mf-manifest.json +1 -1
  24. package/admin/remoteEntry.js +1 -1
  25. package/admin/tab.html +2 -2
  26. package/build/lib/aiProviderResolver.js +80 -0
  27. package/build/lib/aiProviderResolver.js.map +1 -0
  28. package/build/lib/anthropicAdapter.js +175 -0
  29. package/build/lib/anthropicAdapter.js.map +1 -0
  30. package/build/lib/sandbox.js +7 -2
  31. package/build/lib/sandbox.js.map +1 -1
  32. package/build/main.js +141 -107
  33. package/build/main.js.map +1 -1
  34. package/build/types.d.ts +1 -0
  35. package/docs/de/README.md +62 -4
  36. package/docs/en/README.md +62 -4
  37. package/io-package.json +68 -1
  38. package/package.json +1 -1
  39. package/admin/assets/AiChatPanel-sgt697A2.js +0 -296
  40. package/admin/assets/AiInlineProvider-Dd7eKxk4.js +0 -7
  41. package/admin/assets/ScriptEditor-CH4qJzqq.js +0 -2
  42. package/admin/assets/ScriptEditorVanillaMonaco-cB_90T0P.js +0 -1
  43. package/admin/custom/assets/index-CVIpIckw.js +0 -960
package/build/main.js CHANGED
@@ -78,6 +78,8 @@ const protectFs_1 = __importDefault(require("./lib/protectFs"));
78
78
  const words_1 = require("./lib/words");
79
79
  const sandbox_1 = require("./lib/sandbox");
80
80
  const nodeModulesManagement_1 = require("./lib/nodeModulesManagement");
81
+ const aiProviderResolver_1 = require("./lib/aiProviderResolver");
82
+ const anthropicAdapter_1 = require("./lib/anthropicAdapter");
81
83
  const eventObj_1 = require("./lib/eventObj");
82
84
  const scheduler_1 = require("./lib/scheduler");
83
85
  const typescriptSettings_1 = require("./lib/typescriptSettings");
@@ -918,14 +920,15 @@ class JavaScript extends adapter_core_1.Adapter {
918
920
  break;
919
921
  }
920
922
  case 'chatCompletion': {
921
- // Proxy chat completion requests to an OpenAI-compatible API endpoint
923
+ // Proxy chat completion requests to an OpenAI-compatible API endpoint.
924
+ // API keys are resolved server-side from encryptedNative config — they never
925
+ // leave the adapter (frontend only sends `provider`, not the key).
922
926
  if (obj.callback) {
923
- const baseUrl = (obj.message?.baseUrl || '').trim();
924
- const apiKey = (obj.message?.apiKey || '').trim();
925
927
  const chatModel = (obj.message?.model || '').trim();
926
928
  const messages = obj.message?.messages;
927
929
  const tools = obj.message?.tools;
928
930
  const provider = (obj.message?.provider || 'openai').trim();
931
+ const { apiKey, baseUrl } = (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider, obj.message?.baseUrl);
929
932
  // Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
930
933
  if (!apiKey &&
931
934
  (provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
@@ -945,15 +948,16 @@ class JavaScript extends adapter_core_1.Adapter {
945
948
  url = 'https://api.anthropic.com/v1/messages';
946
949
  chatHeaders['x-api-key'] = apiKey;
947
950
  chatHeaders['anthropic-version'] = '2023-06-01';
948
- const systemMessages = messages.filter((m) => m.role === 'system');
949
- const nonSystemMessages = messages.filter((m) => m.role !== 'system');
950
- const systemText = systemMessages.map((m) => m.content).join('\n\n');
951
+ // Translate OpenAI-format messages/tools into Anthropic's content-block format.
952
+ const { system: systemText, messages: anthropicMessages } = (0, anthropicAdapter_1.translateMessagesToAnthropic)(messages);
953
+ const anthropicTools = tools?.length ? (0, anthropicAdapter_1.translateToolsToAnthropic)(tools) : [];
951
954
  bodyObj = {
952
955
  model: chatModel,
953
956
  max_tokens: 8192,
954
957
  stream: false,
955
958
  ...(systemText ? { system: systemText } : {}),
956
- messages: nonSystemMessages,
959
+ messages: anthropicMessages,
960
+ ...(anthropicTools.length ? { tools: anthropicTools } : {}),
957
961
  };
958
962
  }
959
963
  else if (provider === 'gemini') {
@@ -995,69 +999,86 @@ class JavaScript extends adapter_core_1.Adapter {
995
999
  }
996
1000
  const isHttps = urlObj.protocol === 'https:';
997
1001
  const requestModule = isHttps ? https : http;
998
- const req = requestModule.request(url, {
999
- method: 'POST',
1000
- headers: chatHeaders,
1001
- timeout: 600000,
1002
- ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1003
- }, res => {
1004
- let data = '';
1005
- res.on('data', (chunk) => {
1006
- data += chunk.toString();
1007
- });
1008
- res.on('end', () => {
1009
- if (res.statusCode === 200) {
1010
- try {
1011
- const parsed = JSON.parse(data);
1012
- const message = provider === 'anthropic' ? null : parsed.choices?.[0]?.message;
1013
- const content = provider === 'anthropic'
1014
- ? parsed.content?.[0]?.text || ''
1015
- : message?.content || '';
1016
- const tool_calls = message?.tool_calls;
1017
- if (!content && !tool_calls?.length) {
1018
- this.sendTo(obj.from, obj.command, { error: 'Empty response from API' }, obj.callback);
1002
+ try {
1003
+ const req = requestModule.request(url, {
1004
+ method: 'POST',
1005
+ headers: chatHeaders,
1006
+ timeout: 600000,
1007
+ ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1008
+ }, res => {
1009
+ let data = '';
1010
+ res.on('data', (chunk) => {
1011
+ data += chunk.toString();
1012
+ });
1013
+ res.on('end', () => {
1014
+ if (res.statusCode === 200) {
1015
+ try {
1016
+ const parsed = JSON.parse(data);
1017
+ let content;
1018
+ let tool_calls;
1019
+ if (provider === 'anthropic') {
1020
+ const translated = (0, anthropicAdapter_1.translateAnthropicResponseToOpenAI)(parsed);
1021
+ content = translated.content;
1022
+ tool_calls = translated.tool_calls;
1023
+ }
1024
+ else {
1025
+ const message = parsed.choices?.[0]?.message;
1026
+ content = message?.content || '';
1027
+ tool_calls = message?.tool_calls;
1028
+ }
1029
+ if (!content && !tool_calls?.length) {
1030
+ this.sendTo(obj.from, obj.command, { error: 'Empty response from API' }, obj.callback);
1031
+ }
1032
+ else {
1033
+ this.sendTo(obj.from, obj.command, {
1034
+ success: true,
1035
+ content,
1036
+ ...(tool_calls ? { tool_calls } : {}),
1037
+ }, obj.callback);
1038
+ }
1019
1039
  }
1020
- else {
1021
- this.sendTo(obj.from, obj.command, { success: true, content, ...(tool_calls ? { tool_calls } : {}) }, obj.callback);
1040
+ catch {
1041
+ this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1022
1042
  }
1023
1043
  }
1024
- catch {
1025
- this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1026
- }
1027
- }
1028
- else {
1029
- let detail = '';
1030
- try {
1031
- const errParsed = JSON.parse(data);
1032
- detail = errParsed.error?.message || data.substring(0, 200);
1033
- }
1034
- catch {
1035
- detail = data.substring(0, 200);
1044
+ else {
1045
+ let detail = '';
1046
+ try {
1047
+ const errParsed = JSON.parse(data);
1048
+ detail = errParsed.error?.message || data.substring(0, 200);
1049
+ }
1050
+ catch {
1051
+ detail = data.substring(0, 200);
1052
+ }
1053
+ this.sendTo(obj.from, obj.command, {
1054
+ error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1055
+ }, obj.callback);
1036
1056
  }
1037
- this.sendTo(obj.from, obj.command, {
1038
- error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1039
- }, obj.callback);
1040
- }
1057
+ });
1041
1058
  });
1042
- });
1043
- req.on('error', (err) => {
1044
- this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1045
- });
1046
- req.on('timeout', () => {
1047
- req.destroy();
1048
- this.sendTo(obj.from, obj.command, { error: 'Connection timeout (600s)' }, obj.callback);
1049
- });
1050
- req.write(bodyBuffer);
1051
- req.end();
1059
+ req.on('error', (err) => {
1060
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1061
+ });
1062
+ req.on('timeout', () => {
1063
+ req.destroy();
1064
+ this.sendTo(obj.from, obj.command, { error: 'Connection timeout (600s)' }, obj.callback);
1065
+ });
1066
+ req.write(bodyBuffer);
1067
+ req.end();
1068
+ }
1069
+ catch (error) {
1070
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${error.toString()}` }, obj.callback);
1071
+ }
1052
1072
  }
1053
1073
  break;
1054
1074
  }
1055
1075
  case 'testApiConnection': {
1056
- // Test connection to an OpenAI-compatible API endpoint
1076
+ // Test connection to an OpenAI-compatible API endpoint.
1077
+ // The settings-dialog Test button sends the current form value as `apiKey`
1078
+ // (so users can test before saving); otherwise we fall back to the stored key.
1057
1079
  if (obj.callback) {
1058
- const baseUrl = (obj.message?.baseUrl || '').trim();
1059
- const apiKey = (obj.message?.apiKey || '').trim();
1060
1080
  const provider = (obj.message?.provider || 'openai').trim();
1081
+ const { apiKey, baseUrl } = (0, aiProviderResolver_1.resolveTestCredentials)(this.config, provider, obj.message?.apiKey, obj.message?.baseUrl);
1061
1082
  // Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
1062
1083
  if (!apiKey &&
1063
1084
  (provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
@@ -1099,59 +1120,72 @@ class JavaScript extends adapter_core_1.Adapter {
1099
1120
  }
1100
1121
  const isHttps = urlObj.protocol === 'https:';
1101
1122
  const requestModule = isHttps ? https : http;
1102
- const req = requestModule.request(url, {
1103
- method: 'GET',
1104
- headers: testHeaders,
1105
- timeout: 10000,
1106
- ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1107
- }, res => {
1108
- let data = '';
1109
- res.on('data', (chunk) => {
1110
- data += chunk.toString();
1111
- });
1112
- res.on('end', () => {
1113
- if (res.statusCode === 200) {
1114
- try {
1115
- const parsed = JSON.parse(data);
1116
- const models = (parsed.data || [])
1117
- .map((m) => m.id.startsWith('models/') ? m.id.substring(7) : m.id)
1118
- .sort();
1119
- this.sendTo(obj.from, obj.command, { success: true, models, count: models.length }, obj.callback);
1123
+ try {
1124
+ const req = requestModule.request(url, {
1125
+ method: 'GET',
1126
+ headers: testHeaders,
1127
+ timeout: 10000,
1128
+ ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1129
+ }, res => {
1130
+ let data = '';
1131
+ res.on('data', (chunk) => {
1132
+ data += chunk.toString();
1133
+ });
1134
+ res.on('end', () => {
1135
+ if (res.statusCode === 200) {
1136
+ try {
1137
+ const parsed = JSON.parse(data);
1138
+ const models = (parsed.data || [])
1139
+ .map((m) => m.id.startsWith('models/') ? m.id.substring(7) : m.id)
1140
+ .sort();
1141
+ this.sendTo(obj.from, obj.command, { success: true, models, count: models.length }, obj.callback);
1142
+ }
1143
+ catch {
1144
+ this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1145
+ }
1120
1146
  }
1121
- catch {
1122
- this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1147
+ else if (res.statusCode === 401) {
1148
+ this.sendTo(obj.from, obj.command, { error: 'Invalid API key (401)' }, obj.callback);
1123
1149
  }
1124
- }
1125
- else if (res.statusCode === 401) {
1126
- this.sendTo(obj.from, obj.command, { error: 'Invalid API key (401)' }, obj.callback);
1127
- }
1128
- else if (res.statusCode === 403) {
1129
- this.sendTo(obj.from, obj.command, { error: 'Access denied (403)' }, obj.callback);
1130
- }
1131
- else {
1132
- // Include response body for debugging
1133
- let detail = '';
1134
- try {
1135
- const errParsed = JSON.parse(data);
1136
- detail = errParsed.error?.message || data.substring(0, 200);
1150
+ else if (res.statusCode === 403) {
1151
+ this.sendTo(obj.from, obj.command, { error: 'Access denied (403)' }, obj.callback);
1137
1152
  }
1138
- catch {
1139
- detail = data.substring(0, 200);
1153
+ else {
1154
+ // Include response body for debugging
1155
+ let detail = '';
1156
+ try {
1157
+ const errParsed = JSON.parse(data);
1158
+ detail = errParsed.error?.message || data.substring(0, 200);
1159
+ }
1160
+ catch {
1161
+ detail = data.substring(0, 200);
1162
+ }
1163
+ this.sendTo(obj.from, obj.command, {
1164
+ error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1165
+ }, obj.callback);
1140
1166
  }
1141
- this.sendTo(obj.from, obj.command, {
1142
- error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1143
- }, obj.callback);
1144
- }
1167
+ });
1145
1168
  });
1146
- });
1147
- req.on('error', (err) => {
1148
- this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1149
- });
1150
- req.on('timeout', () => {
1151
- req.destroy();
1152
- this.sendTo(obj.from, obj.command, { error: 'Connection timeout (10s)' }, obj.callback);
1153
- });
1154
- req.end();
1169
+ req.on('error', (err) => {
1170
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1171
+ });
1172
+ req.on('timeout', () => {
1173
+ req.destroy();
1174
+ this.sendTo(obj.from, obj.command, { error: 'Connection timeout (10s)' }, obj.callback);
1175
+ });
1176
+ req.end();
1177
+ }
1178
+ catch (error) {
1179
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${error.toString()}` }, obj.callback);
1180
+ }
1181
+ }
1182
+ break;
1183
+ }
1184
+ case 'getAvailableAiProviders': {
1185
+ // Reports which AI providers have stored credentials (keys never leave the backend).
1186
+ if (obj.callback) {
1187
+ const providers = (0, aiProviderResolver_1.listAvailableProviders)(this.config);
1188
+ this.sendTo(obj.from, obj.command, { providers }, obj.callback);
1155
1189
  }
1156
1190
  break;
1157
1191
  }