@taifoon/n8n-nodes-typesafe 1.5.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,16 @@
1
1
  # TypeSafe for n8n
2
2
 
3
- > **Publish mirror.** The source of this package moved to `taifoon-io/taifoon-agents` (private), directory
4
- > `judge/n8n-typesafe`, on 2026-09-27. This public repository is the publish mirror that npm provenance and the n8n
5
- > community listing point at; it is updated from there.
3
+ **Let your n8n workflow make a decision, and send it to a person when it is not sure.**
6
4
 
7
- Most automations have a moment where someone has to *decide*: is this a refund request, which team
8
- gets this ticket, how urgent is it, is this invoice a duplicate. Today you either write brittle rules
9
- for that, or you ask a chat model and then fight with its prose: parse the answer, handle the day it
10
- says "It depends", pay for a paragraph you throw away.
5
+ - **What:** an n8n node that asks TypeSafe's Jev yes/no (Noul), pick-one (Choice) and rate-it (Score) questions
6
+ about any item, and routes it to **Pass**, **Fail** or **Review**.
7
+ - **Why:** a chat model writes prose you have to parse and guesses when unsure. This node returns a probability for
8
+ every answer, so an uncertain item goes to a person instead of going wrong quietly.
9
+ - **How:** in n8n, **Settings → Community Nodes → Install** `@taifoon/n8n-nodes-typesafe`, add your TypeSafe key, and ask
10
+ "Is this a refund request?".
11
11
 
12
- This node does the deciding and nothing else. You hand it an item and a few questions. It hands back
13
- answers your workflow can branch on directly, with a probability attached, usually in well under a
14
- second and for about two thousandths of a cent.
15
-
16
- It works by calling [TypeSafe's Jev](https://docs.typesafe.ai/introduction), a model built to make
17
- decisions rather than write text. You need a TypeSafe API key, which you get from
18
- [console.typesafe.ai](https://console.typesafe.ai). That is the only account involved: the node talks
19
- to TypeSafe directly, and nobody else, including us, is in the path.
12
+ Use it to route tickets, classify rows, guard an LLM's input or output, or gate an agent's tool call for approval.
13
+ It is not for writing text, summarising, or reasoning about code.
20
14
 
21
15
  ```
22
16
  item ──► TypeSafe ──► Pass confident, and it cleared your thresholds
@@ -24,83 +18,16 @@ item ──► TypeSafe ──► Pass confident, and it cleared your thres
24
18
  └──► Review unsure, or the answer did not validate: send this to a person
25
19
  ```
26
20
 
27
- ## The idea: three systems, each doing the one thing it is good at
28
-
29
- ```
30
- n8n Taifoon TypeSafe
31
- the workflow the coordination layer the judge
32
- ───────────── ────────────────────── ─────────
33
- gathers the item ──► turns your words into ──► answers each question
34
- runs the branches typed questions with a probability
35
- ▲ turns the answers back ◄──
36
- └──────────────── into Pass / Fail / Review,
37
- and into sentences in the
38
- asker's own language
39
- ```
40
-
41
- - **n8n** is where your process already lives: the triggers, the data, the people who get notified.
42
- It is good at moving things and bad at judgment.
43
- - **TypeSafe** is a model that only judges. It cannot write an essay, which is the point: it returns
44
- a number you can threshold, quickly and cheaply, instead of prose you have to interpret.
45
- - **Taifoon's part is the layer between them**, and it is plain code that ships inside this node. Going
46
- in, it compiles what you mean ("is this a refund?") into the three question types the model
47
- understands. Coming out, it compiles the model's probabilities into the only three things a workflow
48
- can act on: go ahead, do not, or ask a human. Every threshold in that step is yours and sits on the
49
- canvas where you can see it. And when a person is waiting on the other end, it says the answers back
50
- as sentences in the language they wrote in.
51
-
52
- Why three outputs and not two: a yes/no forces a confident answer even when the model is guessing, and
53
- that is how automations go wrong silently. **Review** is the honest third option. It is where the
54
- low-confidence cases and the malformed answers go, so a person sees exactly the items that need one.
55
-
56
- ## Who this is for
57
-
58
- - **Support and ops teams** routing tickets, emails and alerts without maintaining a wall of IF nodes.
59
- - **Anyone putting an LLM in a workflow** who wants a cheap, fast guard in front of it or behind it:
60
- is this input safe, is this output on topic, does it contain personal data.
61
- - **Builders of data pipelines** who need to classify, de-duplicate or score thousands of rows and
62
- cannot afford, or wait for, a chat model on each one.
63
- - **People who do not trust a yes/no without a number.** Every answer comes with how sure the model
64
- is, so the uncertain cases go to a human instead of going wrong quietly.
65
-
66
- It is not for writing text, summarising, or reasoning about code. We measured that honestly; see
67
- [What it is good and bad at](#what-it-is-good-and-bad-at).
68
-
69
- ## Three kinds of question
70
-
71
- | You ask | You get back | Think of it as |
72
- |---|---|---|
73
- | **Yes / no** ("Noul") | `p`, the probability the statement is true | an IF with a dial |
74
- | **Pick one** ("Choice") | the option, a probability for every option, and a `confidence` | a Switch that knows when it is guessing |
75
- | **Rate it** ("Score") | a level on a rubric you write, and a `confidence` | a ranking you can threshold |
76
-
77
- Two things make the answers sharper, and both are optional:
78
-
79
- - **Describe the options.** Write `billing = payments and refunds; technical = bugs and outages; other = fits none`.
80
- The descriptions go to the model and are what separates options that sound alike. Add an `other`: a message
81
- that fits nowhere is then answered *other* with confidence, instead of being forced into a team.
82
- - **Say what yes and no mean.** A yes/no question has *Yes Means* and *No Means* fields for where the line is.
83
-
84
- Ask all the questions that might matter in the same node. They are answered at once and independently,
85
- so ten questions cost about the same as one.
86
-
87
21
  ## Install
88
22
 
89
23
  In self-hosted n8n: **Settings → Community Nodes → Install**, then enter `@taifoon/n8n-nodes-typesafe`.
90
24
 
91
- **No key yet?** Pick the **Free Trial** connection: three real answers with no account and no key, on us
92
- (up to 4 questions and 4,000 characters per call). It exists so you can see a real result before signing
93
- up anywhere.
94
-
95
- When you are ready, create a **TypeSafe API** credential and paste your key. The test button makes one tiny real call, so
96
- you find out immediately whether the key works. Running n8n for a team? You can provision the key from a
97
- secrets file so nobody ever sees it: [Supplying keys securely](docs/SECURE_KEYS.md).
25
+ Get a TypeSafe API key from [console.typesafe.ai](https://console.typesafe.ai) and create a
26
+ **TypeSafe API** credential. The test button makes one tiny real call, so you know at once whether the key works.
27
+ The node talks to TypeSafe directly with your key, and nobody else, including us, is in the path.
98
28
 
99
- **Upgrading from 1.1 or earlier?** The credential type was renamed (from `typeSafeApi` to
100
- `taifoonTypeSafeApi`) so it cannot collide with other TypeSafe packages or a future built-in node. After
101
- updating, create the **TypeSafe API** credential again and select it in your TypeSafe nodes. Nothing else
102
- changed. If you pre-fill credentials from a file, use the new name as the key
103
- ([Supplying keys securely](docs/SECURE_KEYS.md)).
29
+ Running n8n for a team? [Supply the key from a secrets file](docs/SECURE_KEYS.md). Upgrading from 1.1 or
30
+ earlier? The credential type was renamed: [Upgrading](docs/UPGRADING.md).
104
31
 
105
32
  ## Try it in two minutes
106
33
 
@@ -117,260 +44,55 @@ changed. If you pre-fill credentials from a file, use the new name as the key
117
44
 
118
45
  Ready-made versions are in [`examples/`](examples).
119
46
 
120
- ## Do not want to write the questions? Describe the job
121
-
122
- The **Translate** operation turns a sentence into questions:
123
-
124
- > *Check if the customer is asking for a refund. Classify the ticket into billing, technical, sales or
125
- > abuse. Rate the urgency from 1 to 5.*
126
-
127
- becomes a yes/no, a pick-one with exactly those four options, and a five-level rating. It runs inside
128
- the node: no network call, no key, no cost, and the same sentence always gives the same result.
129
-
130
- It understands **English, Spanish, German, French, Portuguese, Italian, Polish, Dutch, Russian, Japanese
131
- and Arabic**, detects the language per sentence, and you can mix them in one task. Anything else still
132
- works as a yes/no.
133
-
134
- **Your language is not here? Please add it.** A language is one small word pack in
135
- [`nodes/TaifoonTypeSafe/translate.ts`](nodes/TaifoonTypeSafe/translate.ts): the verbs that mean "pick
136
- one", the words that mean "rate it", how options are introduced and separated, how a scale is written,
137
- and a four-level default rubric. No logic changes. Add the pack, add one test sentence to the self-test,
138
- open a pull request. Native speakers catch what we cannot: we would especially welcome Chinese, Korean,
139
- Hindi, Turkish, Ukrainian, Swedish, Hebrew and Indonesian, and corrections to the eleven we ship.
140
-
141
- It splits a sentence that holds several jobs (*check if it is a refund and rate the urgency*), keeps the levels you
142
- name (*rate the tone as polite, neutral or rude*) and reads *from 5 to 1* as the 1 to 5 scale. When it cannot do what
143
- you wrote it says so in `warnings` rather than substituting quietly: a *0 to 10* scale has eleven steps and a rating
144
- takes at most ten, and *is the customer new or returning?* asked as a yes/no answers whether EITHER holds, not which.
145
-
146
- It is deliberately literal. It will not invent categories you did not name: *"classify this ticket"*
147
- with no list comes back flagged `needs_input`. It suggests thresholds but never applies them for you,
148
- for the reason in the next section.
149
-
150
- ## Answering people in their own language
151
-
152
- Branches are for workflows. When a person is waiting for the answer (a support chat, a Telegram bot, a
153
- trading assistant), `{"noul": 0.97}` is no use to them. Set **Reply Language** on the Ask operation and
154
- the output gains a `reply`:
155
-
156
- ```json
157
- { "lang": "de", "flagHuman": true,
158
- "text": "Prüfe, ob die Volatilität ungewöhnlich hoch ist. Ja (94 % sicher)\n? Bewerte die Dringlichkeit ... Vermutlich 4 (4 von 5), aber unsicher (36 %)\nNicht sicher genug: Ich gebe das an einen Menschen weiter.",
159
- "lines": [{ "id": "...", "question": "...", "answer": "...", "outcome": "review" }], "verdict": "..." }
160
- ```
161
-
162
- - **Match Questions** answers in the language the questions were written in, so one workflow serves
163
- every customer. Or pin a language: English questions, Polish answers.
164
- - It is templates, not a model: free, offline, and the same answers always read the same. The person's
165
- own sentence, options and rubric levels are echoed exactly as they wrote them; only the glue around
166
- them is translated, so nothing is paraphrased and nothing is invented.
167
- - It never rounds doubt away. A coin-flip reads as *hard to say*, not yes. A rating the model is spread
168
- across reads as *probably 4, but not sure*. And when anything went to Review, `flagHuman` is true and the
169
- last line says a person is taking over. Wire that to a person, do not soften it.
170
-
171
- The same eleven languages as Translate, and the same request: a voice is one row of thirteen short
172
- strings in [`translate.ts`](nodes/TaifoonTypeSafe/translate.ts). Native speakers, please correct ours.
173
-
174
- ## Raw output: the model's own numbers, untouched
175
-
176
- Everything above — Routing, Reply, the per-question shaping — is this node interpreting the answer for
177
- you. When you would rather do that yourself, turn on **Raw Output** on the Ask operation. The node then
178
- returns the API's answer *exactly as TypeSafe sent it*, with none of its interpretation:
179
-
180
- ```json
181
- { "model": "jev-latest", "provider": "typesafe", "connection": "direct", "latency_ms": 812,
182
- "usage": { "input_tokens": 545 },
183
- "raw": { "model": "jev-latest",
184
- "answers": {
185
- "is_refund": { "noul": 0.98 },
186
- "urgency": { "score": 3.6, "confidence": 0.41, "probabilities": [ ... ], "legend": [ ... ] },
187
- "which_lane": { "choice": "billing", "confidence": 0.77, "probabilities": { "billing": 0.77, "shipping": 0.19, "other": 0.04 } }
188
- } } }
189
- ```
190
-
191
- - **No Routing, no Reply, no reshaping.** `raw` is the whole `{answers, model, usage}` object the model
192
- returned. The probabilities, confidences and `noul`/`score`/`choice` values are the model's own — this
193
- is the `--raw` form for when you want the raw calibration to feed your own logic, a training set, or a
194
- model that learns from Jev's best cases.
195
- - **Everything flows on the first output.** The fail/review outputs are a Routing feature, and Routing
196
- is skipped in raw mode, so nothing is split off.
197
- - Works on both connections (your key and the free trial). `Fail Closed` still applies before the raw
198
- object is emitted, so a malformed answer still stops the item unless you turn it off.
199
-
200
- ## Grade a job with Jev, and put it on chain (Jev Options, 1.5.0)
201
-
202
- For agent jobs with an escrow and an evaluator seat, the Ask operation has **Jev Options**. All of them are off by
203
- default, and without them the node behaves exactly as 1.4.0.
204
-
205
- - **Ask RUBRIC_v1** adds the four questions of the published rubric: spec_met, unsupported_claim, ending and
206
- cheat_shaped. Jev reads the item, then a section listing the facts your workflow established. The node composes
207
- **complete / reject / needs_review** under THRESHOLDS_v1 and routes them to Pass / Fail / Review.
208
- - **Facts (JSON)** holds the checks you already made, e.g. `{"delivered": true, "checks": {"proof_verifies": true}}`.
209
- A false check rejects on Fail, and Jev is not asked.
210
- - **Record On** (`none`, `devnet`, `base`, `both`) adds the unsigned calls that record the receipt on JevAnswerLog
211
- and JevDecisionLog. On devnet 36927 the calls carry the logs' addresses. In this version the Base calls carry
212
- `to: null`.
213
- - **Evaluator Call**, **Job ID** and **Evaluator Address** add the one unsigned call that ends the job as its
214
- evaluator. The protocols are Virtuals ERC-8183, Virtuals memo-ACP, BitAgent ERC-8183, an assurance hook or the judge
215
- adapter. For needs_review the call is `null`.
216
-
217
- The output carries `jev: { verdict, reasons, receiptHash, decisionDigest, answersDigest, record?, evaluator?, receipt }`.
218
- Nothing is signed or sent: a signer node or your wallet does that. The same code, with its tests against real
219
- transactions, is the standalone package [`@taifoon/jev`](jev/README.md).
220
-
221
- ## n8n verification (Creator Portal) — status
222
-
223
- | Requirement | Status | Proof |
224
- |---|---|---|
225
- | Public source repository | done | this repository, `taifoon-io/n8n-nodes-typesafe` (public) |
226
- | No run-time dependencies; no environment or file-system access in `nodes/` and `credentials/` | done | `ci.yml` checks both on every push |
227
- | Lint with n8n's community-node ruleset | done | `npm run lint` in `ci.yml` and `publish.yml` |
228
- | `author.email` is a real mailbox (n8n sends the ownership token there) | done | `publish.yml` refuses a noreply address |
229
- | npm publish with provenance from GitHub Actions | done | 1.4.0 carries an SLSA provenance attestation ([npm](https://www.npmjs.com/package/@taifoon/n8n-nodes-typesafe)); release run [35778144807](https://github.com/taifoon-io/n8n-nodes-typesafe/actions/runs/35778144807) |
230
- | n8n's community-package scanner passes on the published package | done | the same run's scanner step (success) |
231
- | Submission on [creators.n8n.io](https://creators.n8n.io) | **not submitted** | needs the npm package owner (`taifoon`) to sign in and submit `@taifoon/n8n-nodes-typesafe` |
232
-
233
- ## Basic trading tasks, with gates
234
-
235
- A worked example of the whole loop on something less forgiving than support tickets. These are real:
236
- live 5-minute candles, the real model, run on 2026-09-21. A program computed the facts first
237
- (averages, ranges, volatility ratios, whether the New York morning session is open); each task is
238
- written the way a person would type it, one per language; the gates are plain thresholds in code.
239
-
240
- **A pre-trade entry gate, in English** (NQ, 798 ms, left by **Fail**)
241
-
242
- > Check if price is above its 20-bar average. Check if the last hour's move is larger than usual for this market. Classify the market into trending up, trending down or ranging. Rate how stretched price is from its average from 1 to 5.
243
-
244
- | compiled to | gate |
245
- |---|---|
246
- | noul | `gte` 0.7 |
247
- | noul | `lte` 0.5 |
248
- | choice | `minConfidence` 0.6, `in` trending up |
249
- | score | `max` 2 |
250
-
251
- ```
252
- ✓ Check if price is above its 20-bar average. Yes (99% sure)
253
- ✗ Check if the last hour's move is larger than usual for this market. Yes (94% sure)
254
- ✓ Classify the market into trending up, trending down or ranging. trending up (86% confident)
255
- ✓ Rate how stretched price is from its average from 1 to 5. 2 (2 of 5), 55% confident
256
- At least one check did not pass.
257
- ```
258
-
259
- The second check failed on purpose: the gate wants a calm last hour (`lte 0.5`) and the hour was not calm.
260
- That is a gate doing its job, not the model being wrong.
261
-
262
- **A pre-trade order sanity check, in Japanese** (BTC, 301 ms, left by **Review**)
263
-
264
- > この注文の数量は通常より異常に大きいですか。指値は現在の価格から大きく離れていますか。この注文を次のいずれかに分類してください:通常、要確認、誤発注の疑い。
265
-
266
- | compiled to | gate |
267
- |---|---|
268
- | noul | `lte` 0.3 |
269
- | noul | `lte` 0.3 |
270
- | choice | `minConfidence` 0.6, `in` 通常 |
271
-
272
- ```
273
- ✓ この注文の数量は通常より異常に大きいですか。 いいえ(確信度86%)
274
- ✓ 指値は現在の価格から大きく離れていますか。 いいえ(確信度94%)
275
- ? この注文を次のいずれかに分類してください:通常、要確認、誤発注の疑い。 おそらく通常ですが、確信はありません(46%)
276
- 確信が足りないため、担当者に確認を依頼します。
277
- ```
278
-
279
- Both yes/no checks passed, but the model would not commit to a category, so the order goes to a person.
280
- That is what Review is for. (The order is a sample ticket measured against the real last price.)
281
-
282
- **An exit guard, in German** (BTC, 663 ms, left by **Review**)
283
-
284
- > Prüfe, ob der Kurs unter dem 20-Perioden-Durchschnitt liegt. Prüfe, ob die Volatilität ungewöhnlich hoch ist. Bewerte die Dringlichkeit, eine Long-Position zu verkleinern, von 1 bis 5.
285
-
286
- | compiled to | gate |
287
- |---|---|
288
- | noul | reported, not gated |
289
- | noul | reported, not gated |
290
- | score | `min` 3, `minConfidence` 0.5 |
291
-
292
- ```
293
- Prüfe, ob der Kurs unter dem 20-Perioden-Durchschnitt liegt. Nein (99 % sicher)
294
- Prüfe, ob die Volatilität ungewöhnlich hoch ist. Ja (94 % sicher)
295
- ? Bewerte die Dringlichkeit, eine Long-Position zu verkleinern, von 1 bis 5. Vermutlich 4 (4 von 5), aber unsicher (36 %)
296
- Nicht sicher genug: Ich gebe das an einen Menschen weiter.
297
- ```
298
-
299
- A rating is a centre of mass. Without `minConfidence` this one would have cleared `min 3` while the model
300
- was only about a third sure. We found that in this very run, which is why a rating gate can now ask for
301
- confidence too.
302
-
303
- All eleven languages, with the facts the model was shown: [docs/TRADING_GATES.md](docs/TRADING_GATES.md).
304
- Across the run, 11 of 11 languages were detected, the model's reading of the first fact matched plain code in
305
- 11 of 11, the median call took 295 ms, and all 11 calls together cost 0.000331 USD.
306
-
307
- **What this is not.** These gates describe and guard a state a program has already measured. They do not
308
- forecast. We tested that hard: six pre-registered trials on these same markets, and no model (this one,
309
- Claude, or our own) forecast direction. A model in a trading loop supplies judgment about *now*; it does
310
- not create an edge, and a strategy without one loses faster with a model in it. Keep the arithmetic, the
311
- thresholds and every veto in code.
312
-
313
- ## The one rule about routing
314
-
315
- An item leaves by **Pass** only if *every* question you put in Routing passed. So only route the
316
- questions you actually want to gate on. If you route `urgency` as well, a perfectly good refund ticket
317
- "fails" just because it is not urgent. We made exactly that mistake while building this.
47
+ ## Three kinds of question
318
48
 
319
- | Question | Routing keys | What happens |
49
+ | You ask | You get back | Think of it as |
320
50
  |---|---|---|
321
- | Yes / no | `gte`, `lte` on `p` | pass or fail |
322
- | Pick one | `minConfidence`, and `in` for the options you accept | below the confidence → **Review** |
323
- | Rate it | `min`, `max` on the level, and optionally `minConfidence` | pass or fail; below the confidence → **Review** |
324
-
325
- A mistake in Routing never reads as a pass. A rule that names a question you did not ask (a typo, a renamed ID), a
326
- yes/no rule with no threshold, or a confidence bar on an answer that carries no confidence all send the item to
327
- **Review**, with the reason in `decisions`.
328
-
329
- Two details: a rating is a **zero-based level number** (0 is your first level; every answer includes a
330
- `legend`), and an answer that does not validate always goes to Review. Nothing fails open.
331
-
332
- ## What it is good and bad at
333
-
334
- We benchmarked it against Claude Sonnet, and the results are mixed in a useful way:
335
-
336
- - **Answering a real system's yes/no checks:** it matched or beat Sonnet on every decision.
337
- - **Forecasting next-day rain from two days of weather:** a tie (81% against 78%), and neither clearly
338
- beat the naive "same as today".
339
- - **Judging claims about small Python functions:** Sonnet got 100%, this got 83%. It is not a code
340
- reasoner.
51
+ | **Yes / no** ("Noul") | `p`, the probability the statement is true | an IF with a dial |
52
+ | **Pick one** ("Choice") | the option, a probability for every option, and a `confidence` | a Switch that knows when it is guessing |
53
+ | **Rate it** ("Score") | a level on a rubric you write, and a `confidence` | a ranking you can threshold |
341
54
 
342
- In all three it was about **ten times faster and a thousand times cheaper**. Its raw probabilities were
343
- the less well calibrated of the two, which is the practical reason to **fit your thresholds on a few
344
- dozen of your own labelled items** before trusting them.
55
+ Describe the options, and add an `other`, to sharpen a Choice. Ask every question that might matter in one node:
56
+ they are answered at once and independently, so ten cost about the same as one.
345
57
 
346
- ## Habits that keep it reliable
58
+ ## What you must know
347
59
 
60
+ - **Route only what you gate on.** An item leaves by **Pass** only if *every* routed question passed. Route
61
+ `urgency` too, and a good refund ticket "fails" for not being urgent.
62
+ - **Nothing fails open.** A Routing mistake, or an answer that does not validate, sends the item to **Review**.
63
+ - **A rating is a zero-based level number.** 0 is your first level; every answer includes a `legend`.
64
+ - **Fit thresholds on your own items.** Label a few dozen before you trust them. The raw probabilities are
65
+ not calibrated on your data.
348
66
  - **Calculate first, then ask.** Do arithmetic in a Code node. Ask the model for judgment, never a sum.
349
- - **One thing per question.** If a question weighs several factors, split it and combine the answers
350
- yourself, with weights you can see.
351
- - **Keep thresholds on the canvas,** where they can be reviewed and changed, not inside a prompt.
352
- - **Leave Fail Closed on.** A malformed answer stops the item instead of flowing on as an empty value.
353
- - **Remember what n8n stores.** By default n8n keeps every execution's data. If your items contain
354
- personal data, set `EXECUTIONS_DATA_SAVE_ON_SUCCESS=none` on your instance. This node never writes
355
- your key into an execution record, including when a request fails.
67
+ - **Leave Fail Closed on.** A malformed answer then stops the item instead of flowing on as an empty value.
68
+
69
+ Cost: about 0.6 to 0.8 s and 450 input tokens for three questions. TypeSafe charges 0.042 USD per million input
70
+ tokens and nothing for output: roughly 0.00002 USD per item. Input is text or JSON; no images or audio.
356
71
 
357
- ## Cost and limits
72
+ ## More operations and options
358
73
 
359
- About 0.6 to 0.8 s and 450 input tokens for three questions. TypeSafe charges 0.042 USD per million
360
- input tokens and nothing for output: roughly 0.00002 USD per item. Rate limits and overload responses
361
- are retried with backoff. Input is text or JSON; no images or audio.
74
+ - **Translate** turns a sentence into questions, in eleven languages, offline and free.
75
+ [How Translate works](docs/TRANSLATION.md)
76
+ - **Reply Language** says the answers back as sentences in the asker's language. **Raw Output** returns
77
+ TypeSafe's answer untouched. [Reply and Raw Output](docs/OUTPUTS.md)
78
+ - **Jev Options** grade an agent job under RUBRIC_v1 and return unsigned on-chain calls.
79
+ [Jev Options](docs/JEV_OPTIONS.md), and the standalone package [`@taifoon/jev`](https://github.com/taifoon-io/jev).
362
80
 
363
- ## More
81
+ ## Documentation
364
82
 
365
- [TypeSafe over MCP, and an approval gate for agent tool calls](docs/MCP.md) ·
366
- [Supplying keys securely](docs/SECURE_KEYS.md) · [Workflow patterns](docs/WORKFLOWS.md) ·
367
- [How Translate works, rule by rule](docs/TRANSLATION.md) · [Trading gates in eleven languages](docs/TRADING_GATES.md) · [Key policy and rotation](docs/KEY_POLICY.md)
83
+ - [How it works, and who it is for](docs/HOW_IT_WORKS.md)
84
+ - [Routing, reliability, cost and benchmark](docs/RELIABILITY.md)
85
+ - [Workflow patterns](docs/WORKFLOWS.md)
86
+ - [TypeSafe over MCP, and an approval gate for agent tool calls](docs/MCP.md)
87
+ - [Basic trading tasks, with gates](docs/TRADING_EXAMPLES.md) and [in eleven languages](docs/TRADING_GATES.md)
88
+ - [Supplying keys securely](docs/SECURE_KEYS.md) · [Key policy and rotation](docs/KEY_POLICY.md)
368
89
 
369
90
  This package integrates one service: TypeSafe. It is published from GitHub Actions with an npm provenance
370
91
  statement, and every release must pass n8n's community-package scanner.
371
92
 
372
-
373
93
  ## Licence
374
94
 
95
+ Independent project. Jev and TypeSafe are products of TypeSafe AI, Inc., which does not endorse this package.
96
+
375
97
  MIT. An independent community node by [Taifoon](https://github.com/taifoon-io). TypeSafe and Jev are
376
98
  trademarks of TypeSafe AI; this project is not affiliated with or endorsed by TypeSafe AI.
@@ -15,16 +15,7 @@ function safeError(error) {
15
15
  const said = typeof e.response?.data === 'object' && e.response?.data !== null ? JSON.stringify(e.response.data).slice(0, 300) : String(e.description ?? e.message ?? '').slice(0, 300);
16
16
  return { message: `Request failed (${status})`, description: said.replace(REDACT, '$1[redacted]'), httpCode: String(status) };
17
17
  }
18
- function nextStep(status, connection) {
19
- if (connection === 'trial') {
20
- if (status === 402)
21
- return 'The free calls for this server are used up. Create a TypeSafe API credential with your own key from console.typesafe.ai and switch Connection to "Direct to TypeSafe".';
22
- if (status === 413 || status === 400)
23
- return 'The free trial takes up to 4 questions and 4,000 characters per call. Send less, or use your own key.';
24
- if (status === 429)
25
- return 'The free trial is busy. Wait a minute, or use your own key.';
26
- return undefined;
27
- }
18
+ function nextStep(status) {
28
19
  if (status === 401 || status === 403)
29
20
  return 'TypeSafe rejected the key. Check the TypeSafe API credential: the key may have been rotated or revoked at console.typesafe.ai.';
30
21
  if (status === 402)
@@ -62,10 +53,6 @@ async function withBackoff(node, call) {
62
53
  }
63
54
  }
64
55
  }
65
- const TRIAL_URL = 'https://typesafe.taifoon.dev/v1/trial';
66
- async function askTrial(ctx, body) {
67
- return (await ctx.helpers.httpRequest({ method: 'POST', url: TRIAL_URL, body, json: true, timeout: 60000 }));
68
- }
69
56
  const BRANCH_OF = { complete: 'pass', reject: 'fail', needs_review: 'review' };
70
57
  const VERDICT_OF = { pass: 'complete', fail: 'reject', review: 'needs_review' };
71
58
  function jevFacts(raw) {
@@ -108,9 +95,10 @@ class TaifoonTypeSafe {
108
95
  ] },
109
96
  { displayName: 'Connection', name: 'connection', type: 'options', default: 'direct', displayOptions: { show: { operation: ['ask'] } },
110
97
  options: [
111
- { name: 'Direct to TypeSafe', value: 'direct', description: 'Your own TypeSafe key. No other account needed.' },
112
- { name: 'Free Trial (3 Calls, No Key)', value: 'trial', description: 'Three real answers with no account and no key, paid for by Taifoon. Up to 4 questions and 4,000 characters per call.' },
98
+ { name: 'Direct to TypeSafe', value: 'direct', description: 'Your own TypeSafe key from console.typesafe.ai. No other account needed.' },
113
99
  ] },
100
+ { displayName: 'Model', name: 'model', type: 'string', default: 'jev-1.13.0', displayOptions: { show: { operation: ['ask'] } },
101
+ description: 'The TypeSafe model to ask. Pinned by default so the same item gets the same answers over time; enter jev-latest to follow TypeSafe\'s newest model.' },
114
102
  { displayName: 'Item to Judge', name: 'state', type: 'json', default: '={{ JSON.stringify($json) }}', required: true, displayOptions: { show: { operation: ['ask'] } },
115
103
  description: 'Any JSON or text. The default sends the whole incoming item. Do the arithmetic upstream: the model judges, it does not calculate.' },
116
104
  { displayName: 'Questions', name: 'questions', type: 'fixedCollection', typeOptions: { multipleValues: true }, default: {}, placeholder: 'Add Question', displayOptions: { show: { operation: ['ask'] } },
@@ -213,7 +201,7 @@ class TaifoonTypeSafe {
213
201
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Add at least one question', { itemIndex: i });
214
202
  const facts = jevOn ? jevFacts(jev.factsJson) : null;
215
203
  const subject = { chainId: Number(jev.chainId ?? 0), ref: String(jev.subject || `n8n-item-${i}`) };
216
- const input = rubricOn ? (0, receipt_1.inputFor)((0, receipt_1.packOf)(state), facts, connection === 'trial' ? 4000 : undefined) : null;
204
+ const input = rubricOn ? (0, receipt_1.inputFor)((0, receipt_1.packOf)(state), facts) : null;
217
205
  if (rubricOn && rubric_1.RUBRIC_v1.compose(facts, null).forced === 'hard_fail') {
218
206
  const receipt = (0, receipt_1.buildReceipt)({ rubric: rubric_1.RUBRIC_v1, subject, state: input.state, jevState: input.jevState, facts: facts, answers: null, model: null, connection: 'none', caller: 'n8n' });
219
207
  fail.push({ pairedItem: { item: i }, json: { branch: 'fail', jev: await jevOutput(jev, receipt) } });
@@ -238,7 +226,7 @@ class TaifoonTypeSafe {
238
226
  const base = String(cred.baseUrl || 'https://api.typesafe.ai').replace(/\/$/, '');
239
227
  if (!/^https:\/\/[^\s/]+/i.test(base))
240
228
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'The Base URL in the TypeSafe API credential must start with https://', { itemIndex: i });
241
- const req = { method: 'POST', url: `${base}/v1/systemone`, body: { model: 'jev-latest', state, questions }, json: true, timeout: 60000 };
229
+ const req = { method: 'POST', url: `${base}/v1/systemone`, body: { model: String(this.getNodeParameter('model', i, 'jev-1.13.0') || 'jev-1.13.0'), state, questions }, json: true, timeout: 60000 };
242
230
  const started = Date.now();
243
231
  const res = (await withBackoff(this.getNode(), () => this.helpers.httpRequestWithAuthentication.call(this, 'taifoonTypeSafeApi', req)));
244
232
  rawRes = res;
@@ -252,15 +240,10 @@ class TaifoonTypeSafe {
252
240
  return { id: q.id, kind: q.kind, schema_ok: typeof a.choice === 'string', value: a.choice ?? null, confidence: a.confidence ?? null, probabilities: a.probabilities };
253
241
  return { id: q.id, kind: q.kind, schema_ok: typeof a.score === 'number', value: a.score ?? null, confidence: a.confidence ?? null, probabilities: a.probabilities, legend: a.legend };
254
242
  });
255
- meta = { model: res.model ?? 'jev-latest', provider: 'typesafe', connection, latency_ms: Date.now() - started, usage: res.usage ?? {} };
243
+ meta = { model: res.model ?? String(this.getNodeParameter('model', i, 'jev-1.13.0')), provider: 'typesafe', connection, latency_ms: Date.now() - started, usage: res.usage ?? {} };
256
244
  }
257
245
  else if (connection === 'trial') {
258
- const res = await askTrial(this, { state: state, questions: qs.map((q) => ({ id: q.id, kind: q.kind, text: q.text,
259
- ...(q.kind === 'choice' ? { options: Object.fromEntries(parseOptions(q.options)) } : {}), ...(q.kind === 'score' ? { levels: split(q.levels, /\s*\|\s*/) } : {}),
260
- ...(q.kind === 'noul' && q.yesMeans?.trim() ? { yes_means: q.yesMeans.trim() } : {}), ...(q.kind === 'noul' && q.noMeans?.trim() ? { no_means: q.noMeans.trim() } : {}) })) });
261
- rawRes = res;
262
- answers = res.answers ?? [];
263
- meta = { model: res.model, provider: 'typesafe', connection, latency_ms: res.latency_ms, usage: res.usage, trial: res.trial, next: res.next };
246
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'The Free Trial connection was removed in 2.0.0. Create a TypeSafe API credential with your own key from console.typesafe.ai and set Connection to "Direct to TypeSafe".', { itemIndex: i });
264
247
  }
265
248
  else {
266
249
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown connection: ${connection}`, { itemIndex: i });
@@ -282,7 +265,7 @@ class TaifoonTypeSafe {
282
265
  const rubric = rubricOn ? rubric_1.RUBRIC_v1 : (0, rubric_1.defineRubric)({ version: 'n8n-routing.v1', questions: qs.map((q) => ({ id: q.id, text: q.text, options: q.kind === 'choice' ? parseOptions(q.options).map(([o]) => o) : q.kind === 'noul' ? ['yes', 'no'] : split(q.levels, /\s*\|\s*/) })),
283
266
  compose: () => { const b = routed?.branch ?? 'pass'; return { verdict: VERDICT_OF[b] ?? 'needs_review', auto: b !== 'review', forced: null, reasons: [`routing branch ${b}`], scores: { spec_met: null, unsupported_claim: null, scope_ok: null, cheat_shaped: null, ending: null, severity: null } }; } });
284
267
  const sent = input ?? { state: (0, receipt_1.packOf)(state), jevState: (0, receipt_1.packOf)(state) };
285
- const receipt = (0, receipt_1.buildReceipt)({ rubric, subject, state: sent.state, jevState: sent.jevState, facts: facts, answers: (0, receipt_1.answersOf)(asked), model, connection: connection === 'trial' ? 'trial' : 'key', latency_ms: typeof meta.latency_ms === 'number' ? meta.latency_ms : null, caller: 'n8n' });
268
+ const receipt = (0, receipt_1.buildReceipt)({ rubric, subject, state: sent.state, jevState: sent.jevState, facts: facts, answers: (0, receipt_1.answersOf)(asked), model, connection: 'key', latency_ms: typeof meta.latency_ms === 'number' ? meta.latency_ms : null, caller: 'n8n' });
286
269
  jevOut = await jevOutput(jev, receipt);
287
270
  }
288
271
  const byId = {};
@@ -305,7 +288,7 @@ class TaifoonTypeSafe {
305
288
  if (error instanceof n8n_workflow_1.NodeOperationError)
306
289
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, { itemIndex: i, description: error.description ?? undefined });
307
290
  const safe = safeError(error);
308
- const step = nextStep(Number(safe.httpCode), String(this.getNodeParameter('connection', i, 'direct')));
291
+ const step = nextStep(Number(safe.httpCode));
309
292
  if (step)
310
293
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), step, { itemIndex: i, description: String(safe.description ?? '') });
311
294
  throw new n8n_workflow_1.NodeApiError(this.getNode(), safe, { itemIndex: i });