@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +259 -0
  3. package/dist/credentials/LoopthinkRunnerApi.credentials.d.ts +8 -0
  4. package/dist/credentials/LoopthinkRunnerApi.credentials.js +53 -0
  5. package/dist/credentials/LoopthinkRunnerApi.credentials.js.map +1 -0
  6. package/dist/credentials/LoopthinkTargetApi.credentials.d.ts +7 -0
  7. package/dist/credentials/LoopthinkTargetApi.credentials.js +46 -0
  8. package/dist/credentials/LoopthinkTargetApi.credentials.js.map +1 -0
  9. package/dist/nodes/Loopthink/Loopthink.node.d.ts +5 -0
  10. package/dist/nodes/Loopthink/Loopthink.node.js +87 -0
  11. package/dist/nodes/Loopthink/Loopthink.node.js.map +1 -0
  12. package/dist/nodes/Loopthink/execute.operation.d.ts +3 -0
  13. package/dist/nodes/Loopthink/execute.operation.js +89 -0
  14. package/dist/nodes/Loopthink/execute.operation.js.map +1 -0
  15. package/dist/nodes/Loopthink/loopthink-mark.svg +1 -0
  16. package/dist/nodes/Loopthink/prepare.operation.d.ts +3 -0
  17. package/dist/nodes/Loopthink/prepare.operation.js +144 -0
  18. package/dist/nodes/Loopthink/prepare.operation.js.map +1 -0
  19. package/dist/nodes/Loopthink/query.d.ts +29 -0
  20. package/dist/nodes/Loopthink/query.js +63 -0
  21. package/dist/nodes/Loopthink/query.js.map +1 -0
  22. package/dist/nodes/Loopthink/result.operation.d.ts +3 -0
  23. package/dist/nodes/Loopthink/result.operation.js +137 -0
  24. package/dist/nodes/Loopthink/result.operation.js.map +1 -0
  25. package/dist/nodes/LoopthinkRunner/LoopthinkRunner.node.d.ts +5 -0
  26. package/dist/nodes/LoopthinkRunner/LoopthinkRunner.node.js +143 -0
  27. package/dist/nodes/LoopthinkRunner/LoopthinkRunner.node.js.map +1 -0
  28. package/dist/nodes/LoopthinkRunner/loopthink-mark.svg +1 -0
  29. package/dist/nodes/LoopthinkRunner/masking.d.ts +10 -0
  30. package/dist/nodes/LoopthinkRunner/masking.js +95 -0
  31. package/dist/nodes/LoopthinkRunner/masking.js.map +1 -0
  32. package/dist/nodes/LoopthinkRunner/secrets.d.ts +7 -0
  33. package/dist/nodes/LoopthinkRunner/secrets.js +63 -0
  34. package/dist/nodes/LoopthinkRunner/secrets.js.map +1 -0
  35. package/dist/nodes/LoopthinkRunner/socket.d.ts +13 -0
  36. package/dist/nodes/LoopthinkRunner/socket.js +94 -0
  37. package/dist/nodes/LoopthinkRunner/socket.js.map +1 -0
  38. package/dist/nodes/LoopthinkRunner/workflowTools.d.ts +26 -0
  39. package/dist/nodes/LoopthinkRunner/workflowTools.js +27 -0
  40. package/dist/nodes/LoopthinkRunner/workflowTools.js.map +1 -0
  41. 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
+ ![Prepare Query](https://raw.githubusercontent.com/loopthink/n8n-nodes-loopthink/main/docs/prepare-query.png)
173
+ ![The Data Table node reading from it](https://raw.githubusercontent.com/loopthink/n8n-nodes-loopthink/main/docs/read-bookings.png)
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,7 @@
1
+ import type { ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class LoopthinkTargetApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ }
@@ -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,3 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
2
+ export declare const executeFields: INodeProperties[];
3
+ export declare function executeRequest(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
@@ -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>
@@ -0,0 +1,3 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
2
+ export declare const prepareFields: INodeProperties[];
3
+ export declare function executePrepare(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;