node-red-contrib-ollama 0.5.3 → 0.6.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/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
+ ## [0.6.1] - 2026-09-13
8
+
9
+ ### Fixed
10
+
11
+ - `ollama-create` node configuration fields (model, from, quantize, template, license, system, parameters, messages, adapters) were never actually used — only `msg.payload` overrides worked. Switched to `RED.util.evaluateNodeProperty` and fixed the node's `typedInput` setup. Also removed dead `this.editor` references that threw on every save/cancel, made `parameters` a proper JSON field, and made `quantize` optional ([#32](https://github.com/jakubburkiewicz/node-red-contrib-ollama/pull/32), thanks @gorenje).
12
+
13
+ ## [0.6.0] - 2026-09-13
14
+
15
+ ### Added
16
+
17
+ - `ollama-chat`, `ollama-generate`, `ollama-pull` and `ollama-push` now emit one output message per streamed chunk when `stream` is `true`, instead of a single message with an unusable raw iterator as `payload` ([#17](https://github.com/jakubburkiewicz/node-red-contrib-ollama/issues/17)). Non-streaming behavior (the default) is unchanged.
18
+
19
+ ## [0.5.5] - 2026-09-13
20
+
21
+ ### Fixed
22
+
23
+ - The "Embeddings" node in the example flow using a stale node type (`ollama-embeddings` instead of `ollama-embed`) and a payload shaped for `ollama-generate` instead of `ollama-embed` ([#18](https://github.com/jakubburkiewicz/node-red-contrib-ollama/issues/18)).
24
+
25
+ ## [0.5.4] - 2026-09-13
26
+
27
+ ### Fixed
28
+
29
+ - `msg.payload.host` being silently ignored on all nodes when the node's Server field is set to "none", instead of being used to reach the Ollama instance directly ([#28](https://github.com/jakubburkiewicz/node-red-contrib-ollama/issues/28), [#20](https://github.com/jakubburkiewicz/node-red-contrib-ollama/issues/20)).
30
+
7
31
  ## [0.5.3] - 2026-09-13
8
32
 
9
33
  ### Fixed
@@ -254,7 +254,7 @@
254
254
  },
255
255
  {
256
256
  "id": "66e2fb897f54a093",
257
- "type": "ollama-embeddings",
257
+ "type": "ollama-embed",
258
258
  "z": "3bccaf6e46d3dbf5",
259
259
  "name": "Embeddings",
260
260
  "x": 450,
@@ -280,7 +280,7 @@
280
280
  "once": false,
281
281
  "onceDelay": 0.1,
282
282
  "topic": "",
283
- "payload": "{\"model\":\"llama2\",\"prompt\":\"Why is the sky blue?\"}",
283
+ "payload": "{\"model\":\"llama2\",\"input\":\"Why is the sky blue?\"}",
284
284
  "payloadType": "json",
285
285
  "x": 270,
286
286
  "y": 320,
@@ -10,7 +10,7 @@ module.exports = function( RED ) {
10
10
 
11
11
  // Construct host URL
12
12
  let host = null
13
- if ( !host && server ) {
13
+ if ( server ) {
14
14
  if ( server.useCloud ) {
15
15
  // For Ollama Cloud, always use the configured host (https://ollama.com)
16
16
  // NEVER allow msg.payload.host override when using API key authentication
@@ -30,6 +30,9 @@ module.exports = function( RED ) {
30
30
  }
31
31
  }
32
32
  }
33
+ } else {
34
+ // No server configured - allow msg.payload.host directly
35
+ host = msg?.payload?.host || null
33
36
  }
34
37
 
35
38
  // Ollama Cloud configuration
@@ -15,7 +15,7 @@ module.exports = function( RED ) {
15
15
 
16
16
  // Construct host URL
17
17
  let host = null
18
- if ( !host && server ) {
18
+ if ( server ) {
19
19
  if ( server.useCloud ) {
20
20
  // For Ollama Cloud, always use the configured host (https://ollama.com)
21
21
  // NEVER allow msg.payload.host override when using API key authentication
@@ -35,6 +35,9 @@ module.exports = function( RED ) {
35
35
  }
36
36
  }
37
37
  }
38
+ } else {
39
+ // No server configured - allow msg.payload.host directly
40
+ host = msg?.payload?.host || null
38
41
  }
39
42
 
40
43
  // Ollama Cloud configuration
@@ -114,9 +117,17 @@ module.exports = function( RED ) {
114
117
  tools,
115
118
  options
116
119
  } )
117
- .then( response => {
118
- msg.payload = response
119
- node.send( msg )
120
+ .then( async response => {
121
+ if ( stream ) {
122
+ for await ( const part of response ) {
123
+ const chunkMsg = RED.util.cloneMessage( msg )
124
+ chunkMsg.payload = part
125
+ node.send( chunkMsg )
126
+ }
127
+ } else {
128
+ msg.payload = response
129
+ node.send( msg )
130
+ }
120
131
  } )
121
132
  .catch( error => {
122
133
  node.error( error )
@@ -13,7 +13,7 @@ module.exports = function( RED ) {
13
13
 
14
14
  // Construct host URL
15
15
  let host = null
16
- if ( !host && server ) {
16
+ if ( server ) {
17
17
  if ( server.useCloud ) {
18
18
  // For Ollama Cloud, always use the configured host (https://ollama.com)
19
19
  // NEVER allow msg.payload.host override when using API key authentication
@@ -33,6 +33,9 @@ module.exports = function( RED ) {
33
33
  }
34
34
  }
35
35
  }
36
+ } else {
37
+ // No server configured - allow msg.payload.host directly
38
+ host = msg?.payload?.host || null
36
39
  }
37
40
 
38
41
  // Ollama Cloud configuration
@@ -3,92 +3,76 @@
3
3
  category: 'AI',
4
4
  color: 'rgb(186 230 253)',
5
5
  defaults: {
6
- name: { value: 'Ollama Create' },
6
+ name: { value: '' },
7
7
  server: { value: '', type: 'ollama-config-server', required: false },
8
- model: { value: '', type: 'typedInput', required: true },
9
- from: { value: '', type: 'typedInput', required: true },
10
- stream: { value: false, type: 'boolean', required: false },
11
- quantize: { value: '', type: 'typedInput', required: true },
12
- template: { value: '', type: 'typedInput', required: false },
13
- license: { value: '', type: 'typedInput', required: false },
14
- system: { value: '', type: 'typedInput', required: false },
15
- parameters: { value: '', type: 'typedInput', required: false },
16
- messages: { value: '', type: 'typedInput', required: false },
17
- adapters: { value: '', type: 'typedInput', required: false }
8
+ stream: { value: false, required: false },
9
+
10
+ model: { value: '', required: true },
11
+ modelType: { value: "str" },
12
+
13
+ from: { value: '', required: true },
14
+ fromType: { value: "str" },
15
+
16
+ quantize: { value: '', required: false },
17
+ quantizeType: { value: "str" },
18
+
19
+ template: { value: '', required: false },
20
+ templateType: { value: "str" },
21
+
22
+ license: { value: '', required: false },
23
+ licenseType: { value: "str" },
24
+
25
+ system: { value: '', required: false },
26
+ systemType: { value: "str" },
27
+
28
+ parameters: { value: '{}', required: false },
29
+ parametersType: { value: "json" },
30
+
31
+ messages: { value: '', required: false },
32
+ messagesType: { value: "str" },
33
+
34
+ adapters: { value: '', required: false },
35
+ adaptersType: { value: "str" }
18
36
  },
19
37
  inputs: 1,
20
38
  outputs: 1,
21
39
  icon: 'ollama.png',
22
40
  label: function() {
23
- return this.name || 'ollama-create';
41
+ return this.name || (this.modelType === 'str' && this.model) || 'ollama-create';
24
42
  },
43
+
25
44
  oneditprepare: function() {
26
- if( !this.modelType ) {
27
- this.modelType = 'str'
28
- }
29
-
30
- $( '#node-input-model' ).typedInput( {
31
- type: 'str',
32
- types: [ 'str', 'msg', 'flow', 'global' ],
33
- typeField: '#node-input-modelType'
34
- } )
35
-
36
- $( '#node-input-from' ).typedInput( {
37
- type: 'str',
38
- types: [ 'str', 'msg', 'flow', 'global' ],
39
- typeField: '#node-input-fromType'
40
- } )
41
-
42
- $( '#node-input-quantize' ).typedInput( {
43
- type: 'str',
44
- types: [ 'str', 'msg', 'flow', 'global' ],
45
- typeField: '#node-input-quantizeType'
46
- } )
47
-
48
- $( '#node-input-template' ).typedInput( {
49
- type: 'str',
50
- types: [ 'str', 'msg', 'flow', 'global' ],
51
- typeField: '#node-input-templateType'
52
- } )
53
-
54
- $( '#node-input-license' ).typedInput( {
55
- type: 'str',
56
- types: [ 'str', 'msg', 'flow', 'global' ],
57
- typeField: '#node-input-licenseType'
58
- } )
59
-
60
- $( '#node-input-system' ).typedInput( {
61
- type: 'str',
62
- types: [ 'str', 'msg', 'flow', 'global' ],
63
- typeField: '#node-input-systemType'
64
- } )
65
-
66
- $( '#node-input-parameters' ).typedInput( {
67
- type: 'str',
68
- types: [ 'str', 'msg', 'flow', 'global' ],
69
- typeField: '#node-input-parametersType'
70
- } )
71
-
72
- $( '#node-input-messages' ).typedInput( {
73
- type: 'str',
74
- types: [ 'str', 'msg', 'flow', 'global' ],
75
- typeField: '#node-input-messagesType'
76
- } )
77
-
78
- $( '#node-input-adapters' ).typedInput( {
79
- type: 'str',
80
- types: [ 'str', 'msg', 'flow', 'global' ],
81
- typeField: '#node-input-adaptersType'
82
- } )
45
+ [
46
+ "model",
47
+ "from",
48
+ "quantize",
49
+ "template",
50
+ "license",
51
+ "system",
52
+ "messages",
53
+ "adapters"
54
+ ].forEach( parameter => {
55
+ $( `#node-input-${parameter}` ).typedInput( {
56
+ types: [ 'str', 'msg', 'flow', 'global' ],
57
+ typeField: `#node-input-${parameter}Type`
58
+ })
59
+ });
60
+
61
+ // not 'str' but 'json'
62
+ [
63
+ "parameters"
64
+ ].forEach( parameter => {
65
+ $( `#node-input-${parameter}` ).typedInput( {
66
+ types: [ 'json', 'msg', 'flow', 'global' ],
67
+ typeField: `#node-input-${parameter}Type`
68
+ })
69
+ });
83
70
  },
71
+
84
72
  oneditsave: function() {
85
- this.modelfile = this.editor.getValue()
86
- this.editor.destroy()
87
- delete this.editor
88
73
  },
74
+
89
75
  oneditcancel: function() {
90
- this.editor.destroy()
91
- delete this.editor
92
76
  }
93
77
  } )
94
78
  </script>
@@ -117,7 +101,7 @@
117
101
  </div>
118
102
 
119
103
  <div class="form-row">
120
- <label for="node-input-stream"><i class="fa fa-stream"></i> Stream</label>
104
+ <label for="node-input-stream"><i class="fa fa-umbrella"></i> Stream</label>
121
105
  <input type="checkbox" id="node-input-stream" />
122
106
  </div>
123
107
 
@@ -128,7 +112,7 @@
128
112
  </div>
129
113
 
130
114
  <div class="form-row">
131
- <label for="node-input-template"><i class="fa fa-file-code"></i> Template</label>
115
+ <label for="node-input-template"><i class="fa fa-file-code-o"></i> Template</label>
132
116
  <input type="text" id="node-input-template" />
133
117
  <input type="hidden" id="node-input-templateType" />
134
118
  </div>
@@ -4,25 +4,15 @@ module.exports = function( RED ) {
4
4
  function OllamaCreateNode( config ) {
5
5
  RED.nodes.createNode( this, config )
6
6
 
7
- this.modelType = config.modelType || 'str'
8
- this.fromType = config.from || 'str'
9
- this.stream = config.stream || false
10
- this.quantize = config.quantize || 'str'
11
- this.template = config.template || null
12
- this.license = config.license || null
13
- this.system = config.system || null
14
- this.parameters = config.parameters || 'str'
15
- this.messages = config.messages || 'str'
16
- this.adapters = config.adapters || 'str'
17
- this.pathType = config.pathType || 'str'
18
-
19
7
  const node = this
8
+ const cfg = config
9
+
20
10
  node.on( 'input', async function( msg ) {
21
11
  const server = RED.nodes.getNode( config.server )
22
12
 
23
13
  // Construct host URL
24
14
  let host = null
25
- if ( !host && server ) {
15
+ if ( server ) {
26
16
  if ( server.useCloud ) {
27
17
  // For Ollama Cloud, always use the configured host (https://ollama.com)
28
18
  // NEVER allow msg.payload.host override when using API key authentication
@@ -42,6 +32,9 @@ module.exports = function( RED ) {
42
32
  }
43
33
  }
44
34
  }
35
+ } else {
36
+ // No server configured - allow msg.payload.host directly
37
+ host = msg?.payload?.host || null
45
38
  }
46
39
 
47
40
  // Ollama Cloud configuration
@@ -52,163 +45,92 @@ module.exports = function( RED ) {
52
45
  }
53
46
  }
54
47
 
55
- const ollama = new Ollama( ollamaConfig )
48
+ const stream = (msg?.payload?.stream !== undefined) ? msg.payload.stream : cfg.stream
56
49
 
57
50
  let model = null
58
51
  if( msg?.payload?.model ) {
59
- model = msg?.payload?.model
60
- } else if( !!config.model ) {
61
- if( node.modelType === 'str' ) {
62
- model = config.model
63
- } else if( node.modelType === 'msg' ) {
64
- model = msg[ config.model ]
65
- } else if( node.modelType === 'flow' ) {
66
- model = node.context().flow.get( config.model )
67
- } else if( node.modelType === 'global' ) {
68
- model = node.context().global.get( config.model )
69
- }
52
+ model = msg.payload.model
53
+ } else {
54
+ model = RED.util.evaluateNodeProperty(cfg.model, cfg.modelType, node, msg)
70
55
  }
71
56
 
72
57
  let from = null
73
58
  if( msg?.payload?.from ) {
74
- from = msg?.payload?.from
59
+ from = msg.payload.from
75
60
  } else if( !!config.from ) {
76
- if( node.fromType === 'str' ) {
77
- from = config.from
78
- } else if( node.fromType === 'msg' ) {
79
- from = msg[ config.from ]
80
- } else if( node.fromType === 'flow' ) {
81
- from = node.context().flow.get( config.from )
82
- } else if( node.fromType === 'global' ) {
83
- from = node.context().global.get( config.from )
84
- }
61
+ from = RED.util.evaluateNodeProperty(cfg.from, cfg.fromType, node, msg)
85
62
  }
86
63
 
87
- const stream = ( msg?.payload?.stream !== undefined ) ? msg?.payload?.stream : node.stream
88
-
89
64
  let quantize = null
90
65
  if( msg?.payload?.quantize ) {
91
- quantize = msg?.payload?.quantize
66
+ quantize = msg.payload.quantize
92
67
  } else if( !!config.quantize ) {
93
- if( node.quantize === 'str' ) {
94
- quantize = config.quantize
95
- } else if( node.quantize === 'msg' ) {
96
- quantize = msg[ config.quantize ]
97
- } else if( node.quantize === 'flow' ) {
98
- quantize = node.context().flow.get( config.quantize )
99
- } else if( node.quantize === 'global' ) {
100
- quantize = node.context().global.get( config.quantize )
101
- }
68
+ quantize = RED.util.evaluateNodeProperty(cfg.quantize, cfg.quantizeType, node, msg)
102
69
  }
103
70
 
104
71
  let template = null
105
72
  if( msg?.payload?.template ) {
106
- template = msg?.payload?.template
73
+ template = msg.payload.template
107
74
  } else if( !!config.template ) {
108
- if( node.template === 'str' ) {
109
- template = config.template
110
- } else if( node.template === 'msg' ) {
111
- template = msg[ config.template ]
112
- } else if( node.template === 'flow' ) {
113
- template = node.context().flow.get( config.template )
114
- } else if( node.template === 'global' ) {
115
- template = node.context().global.get( config.template )
116
- }
75
+ template = RED.util.evaluateNodeProperty(cfg.template, cfg.templateType, node, msg)
117
76
  }
118
77
 
119
78
  let license = null
120
79
  if( msg?.payload?.license ) {
121
- license = msg?.payload?.license
80
+ license = msg.payload.license
122
81
  } else if( !!config.license ) {
123
- if( node.license === 'str' ) {
124
- license = config.license
125
- } else if( node.license === 'msg' ) {
126
- license = msg[ config.license ]
127
- } else if( node.license === 'flow' ) {
128
- license = node.context().flow.get( config.license )
129
- } else if( node.license === 'global' ) {
130
- license = node.context().global.get( config.license )
131
- }
82
+ license = RED.util.evaluateNodeProperty(cfg.license, cfg.licenseType, node, msg)
132
83
  }
133
84
 
134
85
  let system = null
135
86
  if( msg?.payload?.system ) {
136
- system = msg?.payload?.system
87
+ system = msg.payload.system
137
88
  } else if( !!config.system ) {
138
- if( node.system === 'str' ) {
139
- system = config.system
140
- } else if( node.system === 'msg' ) {
141
- system = msg[ config.system ]
142
- } else if( node.system === 'flow' ) {
143
- system = node.context().flow.get( config.system )
144
- } else if( node.system === 'global' ) {
145
- system = node.context().global.get( config.system )
146
- }
89
+ system = RED.util.evaluateNodeProperty(cfg.system, cfg.systemType, node, msg)
147
90
  }
148
91
 
149
92
  let parameters = null
150
93
  if( msg?.payload?.parameters ) {
151
- parameters = msg?.payload?.parameters
94
+ parameters = msg.payload.parameters
152
95
  } else if( !!config.parameters ) {
153
- if( node.parameters === 'str' ) {
154
- parameters = config.parameters
155
- } else if( node.parameters === 'msg' ) {
156
- parameters = msg[ config.parameters ]
157
- } else if( node.parameters === 'flow' ) {
158
- parameters = node.context().flow.get( config.parameters )
159
- } else if( node.parameters === 'global' ) {
160
- parameters = node.context().global.get( config.parameters )
161
- }
96
+ parameters = RED.util.evaluateNodeProperty(cfg.parameters, cfg.parametersType, node, msg)
162
97
  }
163
98
 
164
99
  let messages = null
165
100
  if( msg?.payload?.messages ) {
166
- messages = msg?.payload?.messages
101
+ messages = msg.payload.messages
167
102
  } else if( !!config.messages ) {
168
- if( node.messages === 'str' ) {
169
- messages = config.messages
170
- } else if( node.messages === 'msg' ) {
171
- messages = msg[ config.messages ]
172
- } else if( node.messages === 'flow' ) {
173
- messages = node.context().flow.get( config.messages )
174
- } else if( node.messages === 'global' ) {
175
- messages = node.context().global.get( config.messages )
176
- }
103
+ messages = RED.util.evaluateNodeProperty(cfg.messages, cfg.messagesType, node, msg)
177
104
  }
178
105
 
179
106
  let adapters = null
180
107
  if( msg?.payload?.adapters ) {
181
- adapters = msg?.payload?.adapters
108
+ adapters = msg.payload.adapters
182
109
  } else if( !!config.adapters ) {
183
- if( node.adapters === 'str' ) {
184
- adapters = config.adapters
185
- } else if( node.adapters === 'msg' ) {
186
- adapters = msg[ config.adapters ]
187
- } else if( node.adapters === 'flow' ) {
188
- adapters = node.context().flow.get( config.adapters )
189
- } else if( node.adapters === 'global' ) {
190
- adapters = node.context().global.get( config.adapters )
191
- }
110
+ adapters = RED.util.evaluateNodeProperty(cfg.adapters, cfg.adaptersType, node, msg)
192
111
  }
193
-
194
- const response = await ollama.create( {
195
- model,
196
- from,
197
- stream,
198
- quantize,
199
- template,
200
- license,
201
- system,
202
- parameters,
203
- messages,
204
- adapters
205
- } )
206
- .catch( error => {
207
- node.error( error )
208
- } )
209
-
210
- msg.payload = response
211
- node.send( msg )
112
+
113
+ const ollama = new Ollama(ollamaConfig)
114
+
115
+ ollama.create( {
116
+ model,
117
+ from,
118
+ stream,
119
+ quantize,
120
+ template,
121
+ license,
122
+ system,
123
+ parameters,
124
+ messages,
125
+ adapters
126
+ })
127
+ .then( response => {
128
+ msg.payload = response
129
+ node.send(msg)
130
+ })
131
+ .catch( error => {
132
+ node.error( error )
133
+ })
212
134
  } )
213
135
  }
214
136
 
@@ -12,7 +12,7 @@ module.exports = function( RED ) {
12
12
 
13
13
  // Construct host URL
14
14
  let host = null
15
- if ( !host && server ) {
15
+ if ( server ) {
16
16
  if ( server.useCloud ) {
17
17
  // For Ollama Cloud, always use the configured host (https://ollama.com)
18
18
  // NEVER allow msg.payload.host override when using API key authentication
@@ -32,6 +32,9 @@ module.exports = function( RED ) {
32
32
  }
33
33
  }
34
34
  }
35
+ } else {
36
+ // No server configured - allow msg.payload.host directly
37
+ host = msg?.payload?.host || null
35
38
  }
36
39
 
37
40
  // Ollama Cloud configuration
@@ -15,7 +15,7 @@ module.exports = function( RED ) {
15
15
 
16
16
  // Construct host URL
17
17
  let host = null
18
- if ( !host && server ) {
18
+ if ( server ) {
19
19
  if ( server.useCloud ) {
20
20
  // For Ollama Cloud, always use the configured host (https://ollama.com)
21
21
  // NEVER allow msg.payload.host override when using API key authentication
@@ -35,6 +35,9 @@ module.exports = function( RED ) {
35
35
  }
36
36
  }
37
37
  }
38
+ } else {
39
+ // No server configured - allow msg.payload.host directly
40
+ host = msg?.payload?.host || null
38
41
  }
39
42
 
40
43
  // Ollama Cloud configuration
@@ -21,7 +21,7 @@ module.exports = function( RED ) {
21
21
 
22
22
  // Construct host URL
23
23
  let host = null
24
- if ( !host && server ) {
24
+ if ( server ) {
25
25
  if ( server.useCloud ) {
26
26
  // For Ollama Cloud, always use the configured host (https://ollama.com)
27
27
  // NEVER allow msg.payload.host override when using API key authentication
@@ -41,6 +41,9 @@ module.exports = function( RED ) {
41
41
  }
42
42
  }
43
43
  }
44
+ } else {
45
+ // No server configured - allow msg.payload.host directly
46
+ host = msg?.payload?.host || null
44
47
  }
45
48
 
46
49
  // Ollama Cloud configuration
@@ -183,9 +186,17 @@ module.exports = function( RED ) {
183
186
  keep_alive,
184
187
  options
185
188
  } )
186
- .then( response => {
187
- msg.payload = response
188
- node.send( msg )
189
+ .then( async response => {
190
+ if ( stream ) {
191
+ for await ( const part of response ) {
192
+ const chunkMsg = RED.util.cloneMessage( msg )
193
+ chunkMsg.payload = part
194
+ node.send( chunkMsg )
195
+ }
196
+ } else {
197
+ msg.payload = response
198
+ node.send( msg )
199
+ }
189
200
  } )
190
201
  .catch( error => {
191
202
  node.error( error )
@@ -10,7 +10,7 @@ module.exports = function( RED ) {
10
10
 
11
11
  // Construct host URL
12
12
  let host = null
13
- if ( !host && server ) {
13
+ if ( server ) {
14
14
  if ( server.useCloud ) {
15
15
  // For Ollama Cloud, always use the configured host (https://ollama.com)
16
16
  // NEVER allow msg.payload.host override when using API key authentication
@@ -30,6 +30,9 @@ module.exports = function( RED ) {
30
30
  }
31
31
  }
32
32
  }
33
+ } else {
34
+ // No server configured - allow msg.payload.host directly
35
+ host = msg?.payload?.host || null
33
36
  }
34
37
 
35
38
  // Ollama Cloud configuration
@@ -10,7 +10,7 @@ module.exports = function( RED ) {
10
10
 
11
11
  // Construct host URL
12
12
  let host = null
13
- if ( !host && server ) {
13
+ if ( server ) {
14
14
  if ( server.useCloud ) {
15
15
  // For Ollama Cloud, always use the configured host (https://ollama.com)
16
16
  // NEVER allow msg.payload.host override when using API key authentication
@@ -30,6 +30,9 @@ module.exports = function( RED ) {
30
30
  }
31
31
  }
32
32
  }
33
+ } else {
34
+ // No server configured - allow msg.payload.host directly
35
+ host = msg?.payload?.host || null
33
36
  }
34
37
 
35
38
  // Ollama Cloud configuration
@@ -14,7 +14,7 @@ module.exports = function( RED ) {
14
14
 
15
15
  // Construct host URL
16
16
  let host = null
17
- if ( !host && server ) {
17
+ if ( server ) {
18
18
  if ( server.useCloud ) {
19
19
  // For Ollama Cloud, always use the configured host (https://ollama.com)
20
20
  // NEVER allow msg.payload.host override when using API key authentication
@@ -34,6 +34,9 @@ module.exports = function( RED ) {
34
34
  }
35
35
  }
36
36
  }
37
+ } else {
38
+ // No server configured - allow msg.payload.host directly
39
+ host = msg?.payload?.host || null
37
40
  }
38
41
 
39
42
  // Ollama Cloud configuration
@@ -70,9 +73,17 @@ module.exports = function( RED ) {
70
73
  insecure,
71
74
  stream
72
75
  } )
73
- .then( response => {
74
- msg.payload = response
75
- node.send( msg )
76
+ .then( async response => {
77
+ if ( stream ) {
78
+ for await ( const part of response ) {
79
+ const chunkMsg = RED.util.cloneMessage( msg )
80
+ chunkMsg.payload = part
81
+ node.send( chunkMsg )
82
+ }
83
+ } else {
84
+ msg.payload = response
85
+ node.send( msg )
86
+ }
76
87
  } )
77
88
  .catch( error => {
78
89
  node.error( error )
@@ -14,7 +14,7 @@ module.exports = function( RED ) {
14
14
 
15
15
  // Construct host URL
16
16
  let host = null
17
- if ( !host && server ) {
17
+ if ( server ) {
18
18
  if ( server.useCloud ) {
19
19
  // For Ollama Cloud, always use the configured host (https://ollama.com)
20
20
  // NEVER allow msg.payload.host override when using API key authentication
@@ -34,6 +34,9 @@ module.exports = function( RED ) {
34
34
  }
35
35
  }
36
36
  }
37
+ } else {
38
+ // No server configured - allow msg.payload.host directly
39
+ host = msg?.payload?.host || null
37
40
  }
38
41
 
39
42
  // Ollama Cloud configuration
@@ -70,9 +73,17 @@ module.exports = function( RED ) {
70
73
  insecure,
71
74
  stream
72
75
  } )
73
- .then( response => {
74
- msg.payload = response
75
- node.send( msg )
76
+ .then( async response => {
77
+ if ( stream ) {
78
+ for await ( const part of response ) {
79
+ const chunkMsg = RED.util.cloneMessage( msg )
80
+ chunkMsg.payload = part
81
+ node.send( chunkMsg )
82
+ }
83
+ } else {
84
+ msg.payload = response
85
+ node.send( msg )
86
+ }
76
87
  } )
77
88
  .catch( error => {
78
89
  node.error( error )
@@ -14,7 +14,7 @@ module.exports = function( RED ) {
14
14
 
15
15
  // Construct host URL
16
16
  let host = null
17
- if ( !host && server ) {
17
+ if ( server ) {
18
18
  if ( server.useCloud ) {
19
19
  // For Ollama Cloud, always use the configured host (https://ollama.com)
20
20
  // NEVER allow msg.payload.host override when using API key authentication
@@ -34,6 +34,9 @@ module.exports = function( RED ) {
34
34
  }
35
35
  }
36
36
  }
37
+ } else {
38
+ // No server configured - allow msg.payload.host directly
39
+ host = msg?.payload?.host || null
37
40
  }
38
41
 
39
42
  // Ollama Cloud configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ollama",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "description": "Add AI functionality to your flows! This module includes a set of nodes that enable easy communication with Ollama, enriching your projects with intelligent solutions.",
5
5
  "keywords": [
6
6
  "ai",