@loopthink/n8n-nodes-loopthink 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/credentials/LoopthinkRunnerApi.credentials.d.ts +8 -0
- package/dist/credentials/LoopthinkRunnerApi.credentials.js +53 -0
- package/dist/credentials/LoopthinkRunnerApi.credentials.js.map +1 -0
- package/dist/credentials/LoopthinkTargetApi.credentials.d.ts +7 -0
- package/dist/credentials/LoopthinkTargetApi.credentials.js +46 -0
- package/dist/credentials/LoopthinkTargetApi.credentials.js.map +1 -0
- package/dist/nodes/Loopthink/Loopthink.node.d.ts +5 -0
- package/dist/nodes/Loopthink/Loopthink.node.js +87 -0
- package/dist/nodes/Loopthink/Loopthink.node.js.map +1 -0
- package/dist/nodes/Loopthink/execute.operation.d.ts +3 -0
- package/dist/nodes/Loopthink/execute.operation.js +89 -0
- package/dist/nodes/Loopthink/execute.operation.js.map +1 -0
- package/dist/nodes/Loopthink/loopthink-mark.svg +1 -0
- package/dist/nodes/Loopthink/prepare.operation.d.ts +3 -0
- package/dist/nodes/Loopthink/prepare.operation.js +144 -0
- package/dist/nodes/Loopthink/prepare.operation.js.map +1 -0
- package/dist/nodes/Loopthink/query.d.ts +29 -0
- package/dist/nodes/Loopthink/query.js +63 -0
- package/dist/nodes/Loopthink/query.js.map +1 -0
- package/dist/nodes/Loopthink/result.operation.d.ts +3 -0
- package/dist/nodes/Loopthink/result.operation.js +137 -0
- package/dist/nodes/Loopthink/result.operation.js.map +1 -0
- package/dist/nodes/LoopthinkRunner/LoopthinkRunner.node.d.ts +5 -0
- package/dist/nodes/LoopthinkRunner/LoopthinkRunner.node.js +143 -0
- package/dist/nodes/LoopthinkRunner/LoopthinkRunner.node.js.map +1 -0
- package/dist/nodes/LoopthinkRunner/loopthink-mark.svg +1 -0
- package/dist/nodes/LoopthinkRunner/masking.d.ts +10 -0
- package/dist/nodes/LoopthinkRunner/masking.js +95 -0
- package/dist/nodes/LoopthinkRunner/masking.js.map +1 -0
- package/dist/nodes/LoopthinkRunner/secrets.d.ts +7 -0
- package/dist/nodes/LoopthinkRunner/secrets.js +63 -0
- package/dist/nodes/LoopthinkRunner/secrets.js.map +1 -0
- package/dist/nodes/LoopthinkRunner/socket.d.ts +13 -0
- package/dist/nodes/LoopthinkRunner/socket.js +94 -0
- package/dist/nodes/LoopthinkRunner/socket.js.map +1 -0
- package/dist/nodes/LoopthinkRunner/workflowTools.d.ts +26 -0
- package/dist/nodes/LoopthinkRunner/workflowTools.js +27 -0
- package/dist/nodes/LoopthinkRunner/workflowTools.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 loopthink
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# @loopthink/n8n-nodes-loopthink
|
|
2
|
+
|
|
3
|
+
Run [loopthink](https://www.loopthink.ai) MCP tools from inside your own network.
|
|
4
|
+
|
|
5
|
+
The node reaches out to loopthink, claims tool calls, executes them against your
|
|
6
|
+
internal HTTP APIs, masks the results locally and sends them back. **Nothing has
|
|
7
|
+
to reach your n8n from the internet**, which is the whole point: it exists for
|
|
8
|
+
environments where the Docker runner cannot be deployed because inbound traffic
|
|
9
|
+
is not an option.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Claude ──► loopthink ──► queue ──┐
|
|
13
|
+
│ n8n polls (outbound only)
|
|
14
|
+
▼
|
|
15
|
+
[loopthink Runner] ──► your internal API
|
|
16
|
+
│
|
|
17
|
+
masked result ──► loopthink ──► Claude
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Two kinds of tool, one shape
|
|
21
|
+
|
|
22
|
+
**HTTP tools** are calls loopthink resolved in full: method, URL, headers, body.
|
|
23
|
+
**Workflow tools** are calls it cannot resolve, because the source has no address
|
|
24
|
+
(an n8n Data Table, a Sheet, a database, any node-only integration), so it sends
|
|
25
|
+
the validated arguments instead.
|
|
26
|
+
|
|
27
|
+
Both leave the Runner on the same output. A Switch on `{{$json.tool}}` decides
|
|
28
|
+
what happens: a workflow tool goes to the branch that answers it, an HTTP tool to
|
|
29
|
+
a branch with **loopthink → Execute Request**, and every branch ends in
|
|
30
|
+
**loopthink → Send Result**. Give the Switch a fallback, or a tool nobody answers
|
|
31
|
+
leaves the caller waiting out its timeout.
|
|
32
|
+
|
|
33
|
+
One branch covers every HTTP tool of a server, because the call is already
|
|
34
|
+
resolved: point Execute Request at `{{ $json.request }}` and it runs whichever
|
|
35
|
+
one arrived.
|
|
36
|
+
|
|
37
|
+
See [examples/](examples) for three working workflows: over an n8n Data Table,
|
|
38
|
+
over Postgres, and over an HTTP API.
|
|
39
|
+
|
|
40
|
+
## Two nodes
|
|
41
|
+
|
|
42
|
+
**loopthink Runner** is the trigger: it claims work and executes the HTTP tools.
|
|
43
|
+
**loopthink** is what the workflow does with the rest: *Build Index Page* and
|
|
44
|
+
*Send Result*.
|
|
45
|
+
|
|
46
|
+
### One transport setting, not two nodes
|
|
47
|
+
|
|
48
|
+
| | **Polling** | **WebSocket** |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| How work arrives | asked for every few seconds | pushed the moment it is queued |
|
|
51
|
+
| Latency added | half the poll interval | none |
|
|
52
|
+
| Needs | plain HTTPS | a network that allows WebSocket upgrades |
|
|
53
|
+
| Cost to loopthink | ~$6 per runner/month | ~$0.04 |
|
|
54
|
+
|
|
55
|
+
Both serve the same queue, so switching loses nothing: work a socket does not
|
|
56
|
+
take stays queued.
|
|
57
|
+
|
|
58
|
+
**Start with WebSocket.** If it cannot connect, the log says so after three
|
|
59
|
+
attempts, that is the answer, and Polling is the fallback. Corporate proxies and
|
|
60
|
+
TLS inspection are the usual reason an upgrade fails.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
Settings → Community Nodes → Install → `@loopthink/n8n-nodes-loopthink`
|
|
65
|
+
|
|
66
|
+
Self-hosted n8n only, as community nodes generally are.
|
|
67
|
+
|
|
68
|
+
## Set up
|
|
69
|
+
|
|
70
|
+
1. In loopthink, open your MCP server → **Runners** → **Add pull runner**.
|
|
71
|
+
2. Copy the block it shows: workspace ID, group ID, secret, queue URL. The secret
|
|
72
|
+
is displayed once and is not recoverable afterwards.
|
|
73
|
+
3. In n8n, create a **loopthink Runner API** credential (the node's
|
|
74
|
+
**Authentication** slot) and paste the four values. Saving it runs a
|
|
75
|
+
connection check against the queue, so a wrong value shows up right away.
|
|
76
|
+
4. Add a runner node to a workflow and **activate** it (see the table above for which).
|
|
77
|
+
|
|
78
|
+
That last step matters: while you are only running *Test workflow* in the editor,
|
|
79
|
+
the node listens for a short window and then stops. It polls continuously only
|
|
80
|
+
once the workflow is active.
|
|
81
|
+
|
|
82
|
+
### Secrets for your own systems
|
|
83
|
+
|
|
84
|
+
In loopthink you configure the *shape* of a request, which header and which URL,
|
|
85
|
+
and write `{{secret.NAME}}` where a value belongs:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Header X-API-Key: {{secret.CRM_API_KEY}}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Then add a **loopthink Target Secrets** credential in n8n (the node's **Secrets**
|
|
92
|
+
slot) with a `CRM_API_KEY` entry. The runner fills the placeholder in on the way out. loopthink sends the
|
|
93
|
+
request to make, never the key to make it with. The secret is never stored there
|
|
94
|
+
and never travels.
|
|
95
|
+
|
|
96
|
+
Two behaviours worth knowing:
|
|
97
|
+
|
|
98
|
+
- **A missing secret refuses the call.** The literal placeholder is never sent;
|
|
99
|
+
it would earn a 401 and leave `{{secret.CRM_API_KEY}}` in the target's access
|
|
100
|
+
log. The error names what is missing.
|
|
101
|
+
- **Execution records show the unresolved form.** A substituted URL can contain a
|
|
102
|
+
secret, and n8n stores execution data.
|
|
103
|
+
|
|
104
|
+
A placeholder in a **URL** ends up in the target's access log and in any proxy in
|
|
105
|
+
between. Sometimes an API leaves no choice, but prefer a header where you have one.
|
|
106
|
+
|
|
107
|
+
## How it behaves
|
|
108
|
+
|
|
109
|
+
- **Polling, not scheduling.** The loop lives inside the node, so no execution is
|
|
110
|
+
created while there is nothing to do. A Schedule trigger at one second would
|
|
111
|
+
produce ~2.6 million executions a month, over 99% of them finding nothing.
|
|
112
|
+
- **Poll interval is latency.** It is the delay added to every tool call. 1–5
|
|
113
|
+
seconds is the sensible range; every poll is also a request loopthink pays for.
|
|
114
|
+
- **Bursts are handled without waiting.** After a call is answered the node checks
|
|
115
|
+
again immediately rather than sleeping out the interval.
|
|
116
|
+
- **Executions are your audit trail.** One per tool call, with the tool name, the
|
|
117
|
+
URL, the status and how many masking rules applied. Turn it off with
|
|
118
|
+
*Emit Results* if you would rather keep the list clean.
|
|
119
|
+
- **Transient failures do not stop it.** A failed poll is logged and retried;
|
|
120
|
+
stopping would leave the runner silently dead until someone noticed.
|
|
121
|
+
|
|
122
|
+
## The loopthink node
|
|
123
|
+
|
|
124
|
+
**Send Result** masks the answer and sends it back. Masking is not a setting on
|
|
125
|
+
it: the only way to answer a call is through this operation, so an unmasked
|
|
126
|
+
result is not something a workflow can send by forgetting a step. Its output
|
|
127
|
+
carries the masked payload as `sent`, so what left the network is something you
|
|
128
|
+
can read rather than take on trust.
|
|
129
|
+
|
|
130
|
+
Answering a listing tool, set it to **Capped List**. The branch reads one row
|
|
131
|
+
more than the limit; if that extra row arrives there was more, and the answer
|
|
132
|
+
says `truncated: true` without the extra row in it. That is the whole paging
|
|
133
|
+
mechanism, and it needs no total, which the Data Table node does not hand out
|
|
134
|
+
anyway.
|
|
135
|
+
|
|
136
|
+
There is no cursor. A model told `truncated` narrows its filters, or continues
|
|
137
|
+
past the last id it was given by passing `id_after`. Both are ordinary filters it
|
|
138
|
+
already understands, so nothing opaque travels back and forth and the model can
|
|
139
|
+
also bisect a large range instead of only walking forward.
|
|
140
|
+
|
|
141
|
+
**Execute Request** issues an HTTP tool's call and fills in `{{secret.NAME}}` from
|
|
142
|
+
the **Secrets** credential on the way out. That substitution is the reason this
|
|
143
|
+
is not a plain HTTP Request node: handed the placeholder, a standard node sends
|
|
144
|
+
it verbatim and it lands in the target's access log.
|
|
145
|
+
|
|
146
|
+
**Prepare Query** is only for the Data Table node, and only because that node
|
|
147
|
+
cannot take its conditions from an expression. A SQL statement or a URL is one
|
|
148
|
+
string, so a call that left a filter out simply produces a shorter query. A Data
|
|
149
|
+
Table node's conditions are rows in the editor, fixed when the workflow is built,
|
|
150
|
+
and the list as a whole cannot be computed: handed a string, n8n walks it
|
|
151
|
+
character by character and quietly produces one empty condition per character.
|
|
152
|
+
|
|
153
|
+
So every row has to hold a value on every call. Prepare Query produces them: a
|
|
154
|
+
bound so far outside the data that the comparison is free, and a wildcard for an
|
|
155
|
+
optional match. One key, one plain value:
|
|
156
|
+
|
|
157
|
+
| | Column | Comparison | Value |
|
|
158
|
+
|---|---|---|---|
|
|
159
|
+
| range, lower | `createdAt` | Or Later | `{{ $json.q.createdAt_min }}` |
|
|
160
|
+
| range, upper | `createdAt` | Or Earlier | `{{ $json.q.createdAt_max }}` |
|
|
161
|
+
| match | `status` | Contains | `{{ $json.q.status }}` |
|
|
162
|
+
| read on | `id` | Greater Than | `{{ $json.q.id_min }}` |
|
|
163
|
+
|
|
164
|
+
The comparison is chosen once from the dropdown and never changes, so it is not
|
|
165
|
+
in the data. Set **Limit** to `{{ $json.q.fetch }}`, which is one more than the
|
|
166
|
+
limit, and **Order** to `{{ $json.q.order }}`.
|
|
167
|
+
|
|
168
|
+
A range on `id` is what lets a model read on, and it is configured exactly like
|
|
169
|
+
any other range. Compare it with **Greater Than** rather than Or Later, so that
|
|
170
|
+
`id_after` means what it says and the boundary row is not returned twice.
|
|
171
|
+
|
|
172
|
+

|
|
173
|
+

|
|
174
|
+
|
|
175
|
+
Paging is by **cursor**, not offset, so the source does the work: `id < c` with a
|
|
176
|
+
Limit is one comparison it expresses natively and it hands back only the page.
|
|
177
|
+
See [examples/](examples) for three sources worked through.
|
|
178
|
+
|
|
179
|
+
## Masking
|
|
180
|
+
|
|
181
|
+
Masking runs **here**, before anything travels back. Only masked data ever passes
|
|
182
|
+
through the loopthink cloud.
|
|
183
|
+
|
|
184
|
+
The rules are not configured in n8n. They arrive with each request, so a rule you
|
|
185
|
+
add in loopthink takes effect on the very next tool call without anyone touching
|
|
186
|
+
this workflow.
|
|
187
|
+
|
|
188
|
+
One thing worth knowing: a rule with an entity **scope** only applies when the
|
|
189
|
+
target entity is known, which for HTTP tools it usually is not. Configure masking
|
|
190
|
+
for HTTP tools **without** a scope, or it will silently do nothing.
|
|
191
|
+
|
|
192
|
+
## Scope
|
|
193
|
+
|
|
194
|
+
HTTP tools are executed by the node; anything else is a workflow tool your own
|
|
195
|
+
branch answers. Both transports do both, so switching between them changes
|
|
196
|
+
nothing but latency and cost. HubSpot and Salesforce importers are not executed
|
|
197
|
+
here.
|
|
198
|
+
|
|
199
|
+
## Develop
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npm install
|
|
203
|
+
npm test
|
|
204
|
+
npm run build
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
A local rig with n8n, an echo service and a seeded Postgres:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
npm run docker:up # http://127.0.0.1:5680
|
|
211
|
+
npm run docker:logs
|
|
212
|
+
npm run docker:down # also drops the volumes, which is what makes Postgres re-seed
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The package is mounted into the container, so `npm run build` is picked up. Node
|
|
216
|
+
*descriptions* are read at startup, though: after adding or renaming a parameter,
|
|
217
|
+
restart n8n or the old description keeps being served and the new parameter
|
|
218
|
+
silently reads as its default.
|
|
219
|
+
|
|
220
|
+
### Local test rig
|
|
221
|
+
|
|
222
|
+
A compose file brings up n8n with this node already loaded, plus an echo service
|
|
223
|
+
to point tool calls at, so you can exercise a full round trip without a real
|
|
224
|
+
internal system:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm run docker:up
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
n8n comes up on <http://127.0.0.1:5680> (override with `LOOPTHINK_N8N_PORT`). From inside the workflow the echo service
|
|
231
|
+
is reachable at `http://echo:8080`. It returns whatever you send it, which makes
|
|
232
|
+
it easy to watch masking work on fields you choose.
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm run docker:restart # rebuild + reload after a code change
|
|
236
|
+
npm run docker:logs # follow n8n's log
|
|
237
|
+
npm run docker:down # stop and remove the volume
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`npm run dev` watches TypeScript and icons; with `N8N_DEV_RELOAD` set in the
|
|
241
|
+
compose file, n8n picks the rebuilt files up without a container restart. The
|
|
242
|
+
browser still needs a refresh to fetch the new node definition.
|
|
243
|
+
|
|
244
|
+
`docker:restart` remains for the cases dev reload does not cover (a changed
|
|
245
|
+
credential class, or anything in docker-compose.yml). It force-recreates rather
|
|
246
|
+
than restarts, because `docker compose restart` would not re-resolve the mount.
|
|
247
|
+
|
|
248
|
+
The whole package is mounted, not just `./dist`: the build begins with
|
|
249
|
+
`rimraf dist`, and a bind mount pointing at a directory that gets deleted leaves
|
|
250
|
+
the container holding a dangling inode. Every later rebuild would then be
|
|
251
|
+
invisible to it, silently, with the node still running whatever loaded at startup.
|
|
252
|
+
|
|
253
|
+
Custom nodes are loaded from `/custom` rather than `~/.n8n/custom` on purpose:
|
|
254
|
+
that path lives inside n8n's data volume, and the two mounts would otherwise
|
|
255
|
+
shadow each other depending on mount order.
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class LoopthinkRunnerApi implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
documentationUrl: string;
|
|
6
|
+
properties: INodeProperties[];
|
|
7
|
+
test: ICredentialTestRequest;
|
|
8
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LoopthinkRunnerApi = void 0;
|
|
4
|
+
class LoopthinkRunnerApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'loopthinkRunnerApi';
|
|
7
|
+
this.displayName = 'Loopthink Runner API';
|
|
8
|
+
this.documentationUrl = 'https://www.loopthink.ai/docs/n8n';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Queue URL',
|
|
12
|
+
name: 'queueUrl',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: 'https://api.eu.loopthink.ai/mcp',
|
|
15
|
+
description: 'The service holding the work queue: the loopthink platform, or a gateway runner inside your own network. Shown together with the secret when the runner is created.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
displayName: 'Workspace ID',
|
|
19
|
+
name: 'workspaceId',
|
|
20
|
+
type: 'string',
|
|
21
|
+
default: '',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
displayName: 'Group ID',
|
|
26
|
+
name: 'groupId',
|
|
27
|
+
type: 'string',
|
|
28
|
+
default: '',
|
|
29
|
+
required: true,
|
|
30
|
+
description: 'The MCP server (group) this runner executes tools for',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
displayName: 'Secret',
|
|
34
|
+
name: 'secret',
|
|
35
|
+
type: 'string',
|
|
36
|
+
typeOptions: { password: true },
|
|
37
|
+
default: '',
|
|
38
|
+
required: true,
|
|
39
|
+
description: 'Shown once when the runner is created and never again. Lost it? Issue a new one there; the old one keeps working for 24 hours.',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
this.test = {
|
|
43
|
+
request: {
|
|
44
|
+
baseURL: '={{$credentials.queueUrl}}',
|
|
45
|
+
url: '=/group/{{$credentials.workspaceId}}/{{$credentials.groupId}}/runner/ping',
|
|
46
|
+
method: 'GET',
|
|
47
|
+
headers: { Authorization: '=Bearer {{$credentials.secret}}' },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.LoopthinkRunnerApi = LoopthinkRunnerApi;
|
|
53
|
+
//# sourceMappingURL=LoopthinkRunnerApi.credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoopthinkRunnerApi.credentials.js","sourceRoot":"","sources":["../../credentials/LoopthinkRunnerApi.credentials.ts"],"names":[],"mappings":";;;AAEA,MAAa,kBAAkB;IAA/B;QACC,SAAI,GAAG,oBAAoB,CAAC;QAE5B,gBAAW,GAAG,sBAAsB,CAAC;QAMrC,qBAAgB,GAAG,mCAAmC,CAAC;QAEvD,eAAU,GAAsB;YAC/B;gBAIC,WAAW,EAAE,WAAW;gBACxB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,iCAAiC;gBAC1C,WAAW,EACV,qKAAqK;aACtK;YACD;gBACC,WAAW,EAAE,cAAc;gBAC3B,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;aACd;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE,uDAAuD;aACpE;YACD;gBAIC,WAAW,EAAE,QAAQ;gBACrB,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,WAAW,EACV,gIAAgI;aACjI;SACD,CAAC;QAMF,SAAI,GAA2B;YAC9B,OAAO,EAAE;gBACR,OAAO,EAAE,4BAA4B;gBACrC,GAAG,EAAE,2EAA2E;gBAChF,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,EAAE,aAAa,EAAE,iCAAiC,EAAE;aAC7D;SACD,CAAC;IACH,CAAC;CAAA;AAjED,gDAiEC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LoopthinkTargetApi = void 0;
|
|
4
|
+
class LoopthinkTargetApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'loopthinkTargetApi';
|
|
7
|
+
this.displayName = 'Loopthink Target Secrets API';
|
|
8
|
+
this.documentationUrl = 'https://www.loopthink.ai/docs/n8n';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Secrets',
|
|
12
|
+
name: 'secrets',
|
|
13
|
+
placeholder: 'Add secret',
|
|
14
|
+
type: 'fixedCollection',
|
|
15
|
+
typeOptions: { multipleValues: true },
|
|
16
|
+
default: {},
|
|
17
|
+
description: 'Each entry matches a {{secret.NAME}} placeholder configured in loopthink. A call whose placeholder is missing here is refused rather than sent, because an unresolved placeholder would reach the target system and end up in its logs.',
|
|
18
|
+
options: [
|
|
19
|
+
{
|
|
20
|
+
name: 'secret',
|
|
21
|
+
displayName: 'Secret',
|
|
22
|
+
values: [
|
|
23
|
+
{
|
|
24
|
+
displayName: 'Name',
|
|
25
|
+
name: 'name',
|
|
26
|
+
type: 'string',
|
|
27
|
+
default: '',
|
|
28
|
+
placeholder: 'CRM_API_KEY',
|
|
29
|
+
description: 'The NAME in {{secret.NAME}}, exactly as written in loopthink',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
displayName: 'Value',
|
|
33
|
+
name: 'value',
|
|
34
|
+
type: 'string',
|
|
35
|
+
typeOptions: { password: true },
|
|
36
|
+
default: '',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.LoopthinkTargetApi = LoopthinkTargetApi;
|
|
46
|
+
//# sourceMappingURL=LoopthinkTargetApi.credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LoopthinkTargetApi.credentials.js","sourceRoot":"","sources":["../../credentials/LoopthinkTargetApi.credentials.ts"],"names":[],"mappings":";;;AAaA,MAAa,kBAAkB;IAA/B;QACC,SAAI,GAAG,oBAAoB,CAAC;QAE5B,gBAAW,GAAG,8BAA8B,CAAC;QAM7C,qBAAgB,GAAG,mCAAmC,CAAC;QAEvD,eAAU,GAAsB;YAC/B;gBACC,WAAW,EAAE,SAAS;gBACtB,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,iBAAiB;gBACvB,WAAW,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE;gBACrC,OAAO,EAAE,EAAE;gBACX,WAAW,EACV,yOAAyO;gBAC1O,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,QAAQ;wBACrB,MAAM,EAAE;4BACP;gCACC,WAAW,EAAE,MAAM;gCACnB,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,QAAQ;gCACd,OAAO,EAAE,EAAE;gCACX,WAAW,EAAE,aAAa;gCAC1B,WAAW,EAAE,8DAA8D;6BAC3E;4BACD;gCACC,WAAW,EAAE,OAAO;gCACpB,IAAI,EAAE,OAAO;gCACb,IAAI,EAAE,QAAQ;gCACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gCAC/B,OAAO,EAAE,EAAE;6BACX;yBACD;qBACD;iBACD;aACD;SACD,CAAC;IACH,CAAC;CAAA;AA9CD,gDA8CC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class Loopthink implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Loopthink = void 0;
|
|
4
|
+
const execute_operation_1 = require("./execute.operation");
|
|
5
|
+
const prepare_operation_1 = require("./prepare.operation");
|
|
6
|
+
const result_operation_1 = require("./result.operation");
|
|
7
|
+
class Loopthink {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.description = {
|
|
10
|
+
displayName: 'loopthink',
|
|
11
|
+
name: 'loopthink',
|
|
12
|
+
icon: 'file:loopthink-mark.svg',
|
|
13
|
+
group: ['transform'],
|
|
14
|
+
version: 1,
|
|
15
|
+
subtitle: '={{$parameter["operation"]}}',
|
|
16
|
+
description: 'Shape and answer a loopthink tool call',
|
|
17
|
+
defaults: { name: 'loopthink' },
|
|
18
|
+
inputs: ['main'],
|
|
19
|
+
outputs: ['main'],
|
|
20
|
+
credentials: [
|
|
21
|
+
{
|
|
22
|
+
name: 'loopthinkRunnerApi',
|
|
23
|
+
required: true,
|
|
24
|
+
displayName: 'Authentication',
|
|
25
|
+
displayOptions: { show: { operation: ['result'] } },
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'loopthinkTargetApi',
|
|
29
|
+
required: false,
|
|
30
|
+
displayName: 'Secrets',
|
|
31
|
+
displayOptions: { show: { operation: ['execute'] } },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
properties: [
|
|
35
|
+
{
|
|
36
|
+
displayName: 'Operation',
|
|
37
|
+
name: 'operation',
|
|
38
|
+
type: 'options',
|
|
39
|
+
noDataExpression: true,
|
|
40
|
+
default: 'result',
|
|
41
|
+
options: [
|
|
42
|
+
{
|
|
43
|
+
name: 'Send Result',
|
|
44
|
+
value: 'result',
|
|
45
|
+
description: 'Mask the answer and send it back to loopthink',
|
|
46
|
+
action: 'Send the result back to loopthink',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Execute Request',
|
|
50
|
+
value: 'execute',
|
|
51
|
+
description: 'Issue the HTTP call loopthink resolved, filling in your secrets',
|
|
52
|
+
action: 'Execute the resolved HTTP request',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'Prepare Query',
|
|
56
|
+
value: 'prepare',
|
|
57
|
+
description: "Turn the model's optional parameters into values a Data Table node can filter on",
|
|
58
|
+
action: 'Prepare a data table query',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
...result_operation_1.resultFields.map((field) => ({
|
|
63
|
+
...field,
|
|
64
|
+
displayOptions: { ...field.displayOptions, show: { ...field.displayOptions?.show, operation: ['result'] } },
|
|
65
|
+
})),
|
|
66
|
+
...execute_operation_1.executeFields.map((field) => ({
|
|
67
|
+
...field,
|
|
68
|
+
displayOptions: { ...field.displayOptions, show: { ...field.displayOptions?.show, operation: ['execute'] } },
|
|
69
|
+
})),
|
|
70
|
+
...prepare_operation_1.prepareFields.map((field) => ({
|
|
71
|
+
...field,
|
|
72
|
+
displayOptions: { ...field.displayOptions, show: { ...field.displayOptions?.show, operation: ['prepare'] } },
|
|
73
|
+
})),
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async execute() {
|
|
78
|
+
const operation = this.getNodeParameter('operation', 0);
|
|
79
|
+
if (operation === 'execute')
|
|
80
|
+
return execute_operation_1.executeRequest.call(this);
|
|
81
|
+
if (operation === 'prepare')
|
|
82
|
+
return prepare_operation_1.executePrepare.call(this);
|
|
83
|
+
return result_operation_1.executeResult.call(this);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.Loopthink = Loopthink;
|
|
87
|
+
//# sourceMappingURL=Loopthink.node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Loopthink.node.js","sourceRoot":"","sources":["../../../nodes/Loopthink/Loopthink.node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeFields = void 0;
|
|
4
|
+
exports.executeRequest = executeRequest;
|
|
5
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
|
+
const secrets_1 = require("../LoopthinkRunner/secrets");
|
|
7
|
+
exports.executeFields = [
|
|
8
|
+
{
|
|
9
|
+
displayName: 'Request',
|
|
10
|
+
name: 'httpRequest',
|
|
11
|
+
type: 'json',
|
|
12
|
+
default: "={{ $json.request }}",
|
|
13
|
+
description: 'The resolved call from the Runner. Leave this as it is unless the branch reshapes the item first.',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
displayName: 'Timeout (Seconds)',
|
|
17
|
+
name: 'requestTimeout',
|
|
18
|
+
type: 'number',
|
|
19
|
+
typeOptions: { minValue: 1, maxValue: 120 },
|
|
20
|
+
default: 30,
|
|
21
|
+
description: 'How long to wait for the internal API before giving up',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
displayName: 'Keys for your own systems stay in this n8n. Write <code>{{secret.NAME}}</code> in loopthink where a value belongs and add a matching entry under <b>Secrets</b>. A call whose placeholder has no entry is refused rather than sent, because the literal placeholder would reach the target and sit in its log.',
|
|
25
|
+
name: 'executeNotice',
|
|
26
|
+
type: 'notice',
|
|
27
|
+
default: '',
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
function parseRequest(value) {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(value);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return (value ?? {});
|
|
40
|
+
}
|
|
41
|
+
async function executeRequest() {
|
|
42
|
+
const timeout = this.getNodeParameter('requestTimeout', 0, 30) * 1000;
|
|
43
|
+
let secrets = {};
|
|
44
|
+
try {
|
|
45
|
+
secrets = (0, secrets_1.secretsFromCredential)(await this.getCredentials('loopthinkTargetApi'));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
}
|
|
49
|
+
const out = [];
|
|
50
|
+
for (let i = 0; i < this.getInputData().length; i += 1) {
|
|
51
|
+
const request = parseRequest(this.getNodeParameter('httpRequest', i));
|
|
52
|
+
if (!request.url) {
|
|
53
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No url in the Request parameter', {
|
|
54
|
+
description: 'This operation runs the HTTP call loopthink resolved. A workflow tool has no url, so route those to their own branch instead.',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const resolved = (0, secrets_1.substituteSecrets)(request, secrets);
|
|
59
|
+
const url = new URL(resolved.url);
|
|
60
|
+
Object.entries(resolved.query || {}).forEach(([key, value]) => url.searchParams.set(key, String(value)));
|
|
61
|
+
const response = await this.helpers.httpRequest({
|
|
62
|
+
method: (resolved.method || 'GET'),
|
|
63
|
+
url: url.toString(),
|
|
64
|
+
headers: resolved.headers || {},
|
|
65
|
+
body: resolved.body ?? undefined,
|
|
66
|
+
json: true,
|
|
67
|
+
timeout,
|
|
68
|
+
returnFullResponse: true,
|
|
69
|
+
ignoreHttpStatusErrors: true,
|
|
70
|
+
});
|
|
71
|
+
out.push({
|
|
72
|
+
json: {
|
|
73
|
+
status: response.statusCode,
|
|
74
|
+
data: response.body,
|
|
75
|
+
url: request.url,
|
|
76
|
+
method: request.method ?? 'GET',
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof secrets_1.MissingSecretError) {
|
|
82
|
+
this.logger.warn(`loopthink: ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
out.push({ json: { error: error.message, url: request.url } });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return [out];
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=execute.operation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.operation.js","sourceRoot":"","sources":["../../../nodes/Loopthink/execute.operation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 256 256" width="256" height="256"><image width="256" height="256" xlink:href="data:image/png;base64,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"/></svg>
|