converse-mcp-server 4.1.0 → 4.1.2
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/docs/API.md +5 -3
- package/package.json +1 -1
- package/src/decisionProviders/systemOne.js +50 -4
- package/src/tools/decide.js +10 -3
package/docs/API.md
CHANGED
|
@@ -422,6 +422,8 @@ Only jobs in a `queued` or `running` state can be cancelled; already-completed,
|
|
|
422
422
|
|
|
423
423
|
Each provider call retries timeouts, 408, 429 and 5xx with backoff, honoring `Retry-After`. Auth failures, exhausted retries and malformed responses fall back to the next provider. A 400/422 request fault stops immediately, because every host would reject it the same way.
|
|
424
424
|
|
|
425
|
+
TypeSafe's API sits behind a Cloudflare firewall that rejects some request bodies containing SQL-injection or shell-command patterns (for example `-- ; DROP TABLE`, or a quoted `'...; DROP TABLE ...;'`) with a 403 before the model sees them. This applies through OpenRouter too, since it forwards to the same edge. `decide` reports it as `Blocked by typesafe.ai's Cloudflare firewall before reaching the model (Ray ID …)` and stops without retrying or falling back. Treat a block as an unanswered question, not as a decision, and report the Ray ID to TypeSafe.
|
|
426
|
+
|
|
425
427
|
### Example Usage
|
|
426
428
|
|
|
427
429
|
```json
|
|
@@ -445,13 +447,13 @@ Each provider call retries timeouts, 408, 429 and 5xx with backoff, honoring `Re
|
|
|
445
447
|
|
|
446
448
|
### Response Format
|
|
447
449
|
|
|
448
|
-
A one-line summary per answer, followed by the answers in the same JSON shape whichever provider served them:
|
|
450
|
+
A one-line summary per answer (options at 0.00 omitted), followed by the answers with full distributions, in the same JSON shape whichever provider served them:
|
|
449
451
|
|
|
450
452
|
````
|
|
451
453
|
Decision · typesafe/jev-1.13-20260917 via OpenRouter · 394 input tokens · $0.000017
|
|
452
454
|
- urgent (noul): 0.97
|
|
453
|
-
- team (choice): billing · confidence 1.00 · billing 1.00
|
|
454
|
-
- frustration (score): 1.24 on 0–2 · confidence 0.64 · 1 Frustrated 0.76, 2 Very angry 0.24
|
|
455
|
+
- team (choice): billing · confidence 1.00 · billing 1.00
|
|
456
|
+
- frustration (score): 1.24 on 0–2 · confidence 0.64 · 1 Frustrated 0.76, 2 Very angry 0.24
|
|
455
457
|
|
|
456
458
|
```json
|
|
457
459
|
{
|
package/package.json
CHANGED
|
@@ -84,6 +84,43 @@ export function extractErrorMessage(body, rawText) {
|
|
|
84
84
|
return rawText?.trim() || 'No error details returned';
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
const MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Summarize an HTML error page, which arrives raw from TypeSafe's edge and
|
|
91
|
+
* embedded in OpenRouter's JSON error message when it forwards one.
|
|
92
|
+
* Cloudflare's block page means the firewall rejected the request content
|
|
93
|
+
* before any model saw it: typically SQL-injection or shell-command patterns
|
|
94
|
+
* inside state or questions.
|
|
95
|
+
* @returns {{ message: string, firewall: boolean, rayId: string|null }|null}
|
|
96
|
+
* null when the text is not an HTML page
|
|
97
|
+
*/
|
|
98
|
+
export function describeHtmlError(text) {
|
|
99
|
+
if (!/<!doctype html|<html[\s>]/i.test(text || '')) return null;
|
|
100
|
+
const rayId = text.match(/Cloudflare Ray ID:\s*<strong[^>]*>([0-9a-f]+)</i)?.[1] ?? null;
|
|
101
|
+
if (/cloudflare/i.test(text) && /you have been blocked|Attention Required/i.test(text)) {
|
|
102
|
+
const host = text.match(/unable to access<\/span>\s*([^<\s]+)/i)?.[1] ?? 'the upstream';
|
|
103
|
+
return {
|
|
104
|
+
firewall: true,
|
|
105
|
+
rayId,
|
|
106
|
+
message:
|
|
107
|
+
`Blocked by ${host}'s Cloudflare firewall before reaching the model${rayId ? ` (Ray ID ${rayId})` : ''}: ` +
|
|
108
|
+
'the request content matched an attack signature, typically SQL-injection or shell-command patterns in state or questions. ' +
|
|
109
|
+
'Retrying the same content will not help; report the Ray ID to the provider.',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const title = text.match(/<title>([^<]*)<\/title>/i)?.[1]?.trim();
|
|
113
|
+
return {
|
|
114
|
+
firewall: false,
|
|
115
|
+
rayId,
|
|
116
|
+
message: `Upstream returned an HTML error page${title ? `: ${title}` : ''}${rayId ? ` (Ray ID ${rayId})` : ''}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function truncate(text) {
|
|
121
|
+
return text.length > MAX_ERROR_MESSAGE_LENGTH ? `${text.slice(0, MAX_ERROR_MESSAGE_LENGTH)}…` : text;
|
|
122
|
+
}
|
|
123
|
+
|
|
87
124
|
function parseRetryAfter(headers) {
|
|
88
125
|
const ms = Number(headers.get('retry-after-ms'));
|
|
89
126
|
if (Number.isFinite(ms) && ms > 0) return ms;
|
|
@@ -145,11 +182,20 @@ async function sendOnce({ url, headers, body, signal, timeoutMs }) {
|
|
|
145
182
|
|
|
146
183
|
if (!response.ok) {
|
|
147
184
|
const status = response.status;
|
|
148
|
-
|
|
185
|
+
const detail = extractErrorMessage(parsed, rawText);
|
|
186
|
+
const html = describeHtmlError(detail);
|
|
187
|
+
// Every host forwards to TypeSafe's edge, so a firewall block repeats on
|
|
188
|
+
// retry and on failover alike.
|
|
189
|
+
const firewall = html?.firewall === true;
|
|
190
|
+
throw new DecisionError(`HTTP ${status}: ${html ? html.message : truncate(detail)}`, {
|
|
149
191
|
status,
|
|
150
|
-
retryable: status === 408 || status === 429 || status >= 500 || status === 401 || status === 403,
|
|
151
|
-
terminal: status === 400 || status === 422,
|
|
152
|
-
requestId:
|
|
192
|
+
retryable: !firewall && (status === 408 || status === 429 || status >= 500 || status === 401 || status === 403),
|
|
193
|
+
terminal: firewall || status === 400 || status === 422,
|
|
194
|
+
requestId:
|
|
195
|
+
response.headers.get('x-typesafe-request-id') ||
|
|
196
|
+
response.headers.get('x-generation-id') ||
|
|
197
|
+
html?.rayId ||
|
|
198
|
+
response.headers.get('cf-ray'),
|
|
153
199
|
retryAfterMs: parseRetryAfter(response.headers),
|
|
154
200
|
});
|
|
155
201
|
}
|
package/src/tools/decide.js
CHANGED
|
@@ -148,8 +148,13 @@ function fixed(n) {
|
|
|
148
148
|
return typeof n === 'number' ? n.toFixed(2) : String(n);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Options at or rounding to zero are left out of the summary line; the JSON
|
|
153
|
+
* block still carries the full distribution.
|
|
154
|
+
*/
|
|
151
155
|
function byProbability(probabilities, label = (k) => k) {
|
|
152
156
|
return Object.entries(probabilities || {})
|
|
157
|
+
.filter(([, p]) => fixed(p) !== '0.00')
|
|
153
158
|
.sort(([, a], [, b]) => b - a)
|
|
154
159
|
.map(([k, p]) => `${label(k)} ${fixed(p)}`)
|
|
155
160
|
.join(', ');
|
|
@@ -261,9 +266,11 @@ decideTool.description =
|
|
|
261
266
|
'DECIDE — ask a System One decision model (TypeSafe Jev) typed questions about a state and get calibrated answers, not text. ' +
|
|
262
267
|
'Question types: "noul" (yes/no → probability 0..1), "choice" (pick one of 2–255 named options → choice, per-option probabilities, confidence), ' +
|
|
263
268
|
'"score" (ordered rubric of 2–10 levels → weighted position, per-level probabilities, confidence). ' +
|
|
264
|
-
'Batch
|
|
265
|
-
'Best for fast
|
|
266
|
-
'split
|
|
269
|
+
'Batch independent questions over the same state into one call: they are judged in parallel and in isolation, so none sees another\'s answer; each extra question adds its own input tokens. ' +
|
|
270
|
+
'Best for fast semantic judgments (classify, route, select, verify, rank). Ask one narrow, coherent judgment per question, with its full meaning in the question; ' +
|
|
271
|
+
'split independently useful dimensions, but a bounded action choice or contextual interpretation is a valid single question. Do counting, arithmetic, and date comparison in code. ' +
|
|
272
|
+
'confidence measures how concentrated the distribution is, not permission to act: take the top option to pick a best, and treat a noul near 0.5 as "yes and no equally likely". ' +
|
|
273
|
+
'Text only, no explanations are returned. ' +
|
|
267
274
|
'Limits: ~64k tokens per request, ~32k for state plus the longest question.';
|
|
268
275
|
|
|
269
276
|
decideTool.inputSchema = {
|