converse-mcp-server 4.5.2 → 4.6.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/.env.example +7 -0
- package/README.md +5 -3
- package/docs/API.md +43 -19
- package/docs/PROVIDERS.md +6 -4
- package/package.json +10 -10
- package/src/config.js +16 -1
- package/src/decisionProviders/formats.js +229 -0
- package/src/decisionProviders/{systemOne.js → http.js} +56 -25
- package/src/decisionProviders/index.js +167 -73
- package/src/providers/anthropic.js +32 -3
- package/src/providers/claude.js +19 -0
- package/src/providers/copilot.js +25 -1
- package/src/tools/decide.js +138 -29
package/.env.example
CHANGED
|
@@ -57,8 +57,15 @@ ABLITERATION_API_KEY=your_abliteration_api_key_here
|
|
|
57
57
|
# Get your TypeSafe API key from: https://typesafe.ai
|
|
58
58
|
# Enables the decide tool's native host for Jev decision models. Without it,
|
|
59
59
|
# decide reaches Jev through OPENROUTER_API_KEY instead.
|
|
60
|
+
# (OPENAI_API_KEY above also enables decide's default model, gpt-6-luna.)
|
|
60
61
|
TYPESAFE_API_KEY=your_typesafe_api_key_here
|
|
61
62
|
|
|
63
|
+
# Cloudflare Workers AI: the decide tool's native host for the Clef decision
|
|
64
|
+
# models (clef, clef-flash). The token needs Workers AI permission. Without
|
|
65
|
+
# these, decide reaches Clef through OPENROUTER_API_KEY instead.
|
|
66
|
+
CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id_here
|
|
67
|
+
CLOUDFLARE_API_TOKEN=your_cloudflare_api_token_here
|
|
68
|
+
|
|
62
69
|
# ============================================
|
|
63
70
|
# Codex Configuration (Optional)
|
|
64
71
|
# ============================================
|
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ Cancel running asynchronous operations when needed.
|
|
|
187
187
|
|
|
188
188
|
### 4. Decide Tool
|
|
189
189
|
|
|
190
|
-
Ask a
|
|
190
|
+
Ask a decision model (OpenAI's `gpt-6-luna`, TypeSafe's Jev, or Cloudflare's Clef) typed questions about a state and get calibrated answers instead of text: a yes probability (`noul`), a chosen option with per-option probabilities (`choice`), or a rubric position with per-level probabilities (`score`). Batch many questions into one call; they are judged in parallel against the same state. Needs any of `OPENAI_API_KEY`, `TYPESAFE_API_KEY`, `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_API_TOKEN`, or `OPENROUTER_API_KEY`; each model runs on its native host first and falls back to OpenRouter. `model: "auto"` tries `gpt-6-luna`, then `jev-latest`, then `clef`. Pass `images` to judge pictures with `gpt-6-luna` or Clef.
|
|
191
191
|
|
|
192
192
|
```javascript
|
|
193
193
|
{
|
|
@@ -280,7 +280,8 @@ SUMMARIZATION_MODEL=gpt-5-nano # Default: gpt-5-nano
|
|
|
280
280
|
- **claude-opus-4-5** / **claude-opus-4-1**: Legacy Opus models with extended thinking (64K / 32K output)
|
|
281
281
|
- **claude-sonnet-5-5** (aliases: `sonnet`, `sonnet-5.5`): Current Sonnet with adaptive thinking and effort (1M context, 128K output)
|
|
282
282
|
- **claude-sonnet-4-6** (alias: `sonnet-4.6`): Previous Sonnet generation with adaptive thinking (64K output)
|
|
283
|
-
- **claude-haiku-
|
|
283
|
+
- **claude-haiku-5-5** (aliases: `haiku`, `haiku-5.5`): Current Haiku, fastest and cheapest Claude, with adaptive thinking and effort (1M context, 128K output)
|
|
284
|
+
- **claude-haiku-4-5** (alias: `haiku-4.5`): Previous Haiku generation with extended thinking (64K output)
|
|
284
285
|
|
|
285
286
|
### Mistral Models
|
|
286
287
|
|
|
@@ -337,6 +338,7 @@ Claude via the Claude Agent SDK. `claude` uses its default model (Opus 5.5, or `
|
|
|
337
338
|
- **claude-opus-5-5** (default; aliases: `opus`, `claude-opus`, `opus-5.5`), **claude-opus-5** (`opus-5`)
|
|
338
339
|
- **claude-fable-5-1** (aliases: `fable`, `claude-fable`, `fable-5.1`), **claude-fable-5** (`fable-5`)
|
|
339
340
|
- **claude-sonnet-5-5** (aliases: `sonnet`, `claude-sonnet`, `sonnet-5.5`)
|
|
341
|
+
- **claude-haiku-5-5** (aliases: `haiku`, `claude-haiku`, `haiku-5.5`)
|
|
340
342
|
- Uses Claude Code CLI authentication (`claude login`) - no API key needed
|
|
341
343
|
- Direct filesystem access from working directory
|
|
342
344
|
|
|
@@ -345,7 +347,7 @@ Claude via the Claude Agent SDK. `claude` uses its default model (Opus 5.5, or `
|
|
|
345
347
|
Reach these only with the `copilot:` namespace (e.g. `copilot:gpt-6.1-sol`) — Copilot never serves bare model names. `copilot` alone uses GPT-6.1 Sol, or `COPILOT_DEFAULT_MODEL`. Uses your GitHub Copilot subscription (`gh auth login`) - no API key needed:
|
|
346
348
|
|
|
347
349
|
- **OpenAI**: `gpt-6.1-sol` (aliases: `gpt-6`, `gpt-6.1`, `gpt-5`, `sol`), `gpt-6-sol`, `gpt-6-luna` (alias: `luna`), `gpt-5.6-sol` (alias: `gpt-5.6`), `gpt-5.6-terra`, `gpt-5.6-luna` (all support `reasoning_effort`)
|
|
348
|
-
- **Anthropic**: `claude-opus-5.5` (aliases: `opus`, `claude`), `claude-fable-5` (alias: `fable`), `claude-sonnet-5.5` (alias: `sonnet`), `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4.8`
|
|
350
|
+
- **Anthropic**: `claude-opus-5.5` (aliases: `opus`, `claude`), `claude-fable-5.1` (alias: `fable`), `claude-fable-5`, `claude-sonnet-5.5` (alias: `sonnet`), `claude-haiku-5.5` (alias: `haiku`), `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4.8`
|
|
349
351
|
- **Google**: `gemini-3.1-pro-preview` (aliases: `gemini`, `gemini-3.1-pro`), `gemini-3.8-flash` (aliases: `gemini-3.8`, `flash-3.8`), `gemini-3.5-flash` (alias: `gemini-flash`)
|
|
350
352
|
|
|
351
353
|
## 📚 Help & Documentation
|
package/docs/API.md
CHANGED
|
@@ -368,7 +368,7 @@ Only jobs in a `queued` or `running` state can be cancelled; already-completed,
|
|
|
368
368
|
|
|
369
369
|
## Decide Tool
|
|
370
370
|
|
|
371
|
-
**Description**: Ask a
|
|
371
|
+
**Description**: Ask a decision model (OpenAI's `gpt-6-luna`, TypeSafe's Jev, Cloudflare's Clef) typed questions about a state. Decision models return calibrated probabilities, never text, so they have their own tool and their own providers: `chat` routing never reaches them, and `decide` never reaches a chat model. Requests and answers use TypeSafe's System One schema for every model; providers whose API differs translate it.
|
|
372
372
|
|
|
373
373
|
### Request Schema
|
|
374
374
|
|
|
@@ -390,7 +390,8 @@ Only jobs in a `queued` or `running` state can be cancelled; already-completed,
|
|
|
390
390
|
}
|
|
391
391
|
},
|
|
392
392
|
"model": { "type": "string", "description": "Default: \"auto\"" },
|
|
393
|
-
"files": { "type": "array", "items": { "type": "string" } }
|
|
393
|
+
"files": { "type": "array", "items": { "type": "string" } },
|
|
394
|
+
"images": { "type": "array", "items": { "type": "string" } }
|
|
394
395
|
},
|
|
395
396
|
"required": ["questions"],
|
|
396
397
|
"additionalProperties": false
|
|
@@ -399,30 +400,50 @@ Only jobs in a `queued` or `running` state can be cancelled; already-completed,
|
|
|
399
400
|
|
|
400
401
|
- **`state`**: the material to judge. An object with descriptively named fields works best; use an array for sequences such as chat messages. Optional when `files` is given.
|
|
401
402
|
- **`questions`**: named questions, each judged in parallel and in isolation against the same state. The name is your own label and becomes the answer key. Batching many questions into one call adds almost no latency or cost.
|
|
402
|
-
- **`files`**: text files added to the state as `{ "files": { "<path>": "<content>" } }`. When `state` is also given, it moves to `input`. Line ranges (`file.txt{10:50}`) are supported;
|
|
403
|
+
- **`files`**: text files added to the state as `{ "files": { "<path>": "<content>" } }`. When `state` is also given, it moves to `input`. Line ranges (`file.txt{10:50}`) are supported; image files belong in `images`.
|
|
404
|
+
- **`images`**: PNG, JPEG or WebP images judged together with the state, as file paths or base64 data URLs; `state` is optional when images are given. Only `gpt-6-luna` (OpenAI) and `clef` / `clef-flash` (Cloudflare) take images, so `auto` skips other models and naming a text-only model is rejected before sending. OpenAI receives the state and images as one user message. Clef takes at most 4 images, and Cloudflare rejects a request whose size estimate (one token per 4 characters, base64 included) exceeds its 65,536-token window, so Clef images must total about 180 KB; `decide` checks this locally and suggests downscaling. OpenRouter routes are text only.
|
|
403
405
|
|
|
404
406
|
| Type | `criteria` | Answer |
|
|
405
407
|
|---|---|---|
|
|
406
408
|
| `noul` | Optional `{ "true": "...", "false": "..." }` | `noul`: probability 0..1 of yes |
|
|
407
409
|
| `choice` | Required `{ "<option>": "description" \| null }`, 2–255 options | `choice`, per-option `probabilities`, `confidence` |
|
|
408
|
-
| `score` | Required ordered array of levels, lowest first, 2–10 levels | `score` (probability-weighted level position), `legend`, per-level `probabilities`, `confidence` |
|
|
410
|
+
| `score` | Required ordered array of levels, lowest first, 2–10 levels; each a description or `{ "label": "...", "description": ... }` | `score` (probability-weighted level position), `legend`, per-level `probabilities`, `confidence` |
|
|
409
411
|
|
|
410
412
|
`instructions` and criteria descriptions may be objects or arrays that bundle reference data with the question; refer to their fields by `` `name` `` in the text. Questions are validated before any request is sent.
|
|
411
413
|
|
|
414
|
+
A labeled score level (`{ "label": "Very angry", "description": "Hostile, threatening to leave" }`) keeps a short name apart from its full description. OpenAI receives the pair as-is; System One hosts (TypeSafe, Cloudflare, OpenRouter) take one description per level, so they receive `"label: description"`. Either way the answer's `legend` shows the labels.
|
|
415
|
+
|
|
416
|
+
Any answer from `gpt-6-luna` may be `{ "type": "refusal" }` instead of a value, shown as `- name (refusal): the model declined to answer`. Treat it as unanswered, never as a no. Jev and Clef have no refusal answer and always return a value.
|
|
417
|
+
|
|
412
418
|
### Models and Providers
|
|
413
419
|
|
|
414
|
-
|
|
|
415
|
-
|
|
416
|
-
| `
|
|
417
|
-
| `
|
|
420
|
+
| Model | Aliases | Native host | OpenRouter slug |
|
|
421
|
+
|---|---|---|---|
|
|
422
|
+
| `gpt-6-luna` | `luna`, `gpt-6-luna-decisions` | `openai` | `openai/gpt-6-luna-decisions` |
|
|
423
|
+
| `jev-latest` | `jev` | `typesafe` (`jev-latest`) | `~typesafe/jev-latest` |
|
|
424
|
+
| `jev-1.13` | `jev-1.13.0` | `typesafe` (`jev-1.13.0`) | `typesafe/jev-1.13` |
|
|
425
|
+
| `jev-preview` | | `typesafe` | not served |
|
|
426
|
+
| `clef` | `@cf/cloudflare/clef` | `cloudflare` | `cloudflare/clef` |
|
|
427
|
+
| `clef-flash` | `@cf/cloudflare/clef-flash` | `cloudflare` | `cloudflare/clef-flash` |
|
|
428
|
+
|
|
429
|
+
| Provider | Endpoint | Configuration | Wire format |
|
|
430
|
+
|---|---|---|---|
|
|
431
|
+
| `openai` | `POST https://api.openai.com/v1/decisions` | `OPENAI_API_KEY` | OpenAI Decisions (translated) |
|
|
432
|
+
| `typesafe` | `POST https://api.typesafe.ai/v1/systemone` | `TYPESAFE_API_KEY` | System One; also any versioned `jev-X.Y.Z` |
|
|
433
|
+
| `cloudflare` | `POST https://api.cloudflare.com/client/v4/accounts/{account}/ai/run/@cf/cloudflare/{model}` | `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_API_TOKEN` (Workers AI permission) | System One in the Workers AI envelope |
|
|
434
|
+
| `openrouter` | `POST https://openrouter.ai/api/v1/systemone` | `OPENROUTER_API_KEY` | System One; also any `vendor/model` slug |
|
|
435
|
+
|
|
436
|
+
- `auto` (default): `gpt-6-luna`, then `jev-latest`, then `clef`, each on every configured host (native first, then OpenRouter).
|
|
437
|
+
- A model name goes to every configured provider that serves it, native first, and each provider receives its own model ID.
|
|
438
|
+
- `provider` alone uses that provider's default (`openai` and `openrouter`: `gpt-6-luna`; `typesafe`: `jev-latest`; `cloudflare`: `clef`). `provider:model` pins one provider, e.g. `cloudflare:clef-flash` or `openrouter:jev-latest`.
|
|
439
|
+
|
|
440
|
+
OpenAI's Decisions API takes the same three kinds of question in its own schema, and only strings for the input and instructions. `decide` translates: object or array `state` and `instructions` are sent as JSON text, `noul` becomes a predicate whose `true`/`false` criteria are appended to the instructions, choice options and score levels become `choices` and `levels` arrays, and answers are mapped back to System One shapes.
|
|
418
441
|
|
|
419
|
-
|
|
420
|
-
- A bare name such as `jev-1.13` goes to every configured provider that serves it, native first, and each provider receives its own model ID. OpenRouter does not serve `jev-preview` or patch-level IDs other than those listed above.
|
|
421
|
-
- `typesafe:jev-1.13.0` or `openrouter:~typesafe/jev-latest` pins one provider.
|
|
442
|
+
Clef accepts at most 64 questions per call, named with letters, digits, `_`, `.` and `-` (up to 100 characters); `decide` checks this before sending.
|
|
422
443
|
|
|
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
|
|
444
|
+
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 candidate. A 400/422 request fault skips the remaining hosts of the same model, which would reject it the same way, but `auto` still moves on to its next model.
|
|
424
445
|
|
|
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
|
|
446
|
+
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 to Jev 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 does not retry Jev on another host. Treat a block as an unanswered question, not as a decision, and report the Ray ID to TypeSafe.
|
|
426
447
|
|
|
427
448
|
### Example Usage
|
|
428
449
|
|
|
@@ -466,11 +487,11 @@ Decision · typesafe/jev-1.13-20260917 via OpenRouter · 394 input tokens · $0.
|
|
|
466
487
|
```
|
|
467
488
|
````
|
|
468
489
|
|
|
469
|
-
`usage.cost` is reported by OpenRouter only (`null` from
|
|
490
|
+
`usage.cost` is reported by OpenRouter only (`null` from the native hosts). A fallback is noted under the summary line.
|
|
470
491
|
|
|
471
492
|
### Usage Guidance
|
|
472
493
|
|
|
473
|
-
|
|
494
|
+
Decision models read questions literally and are weak at counting, arithmetic, date comparison, and multi-hop reasoning; do those in code and ask narrow, atomic questions. Split compound judgments into separate questions and combine them in code. Treat low `confidence` as a signal to escalate to a generative model or a human. Limits: Jev is text only and takes about 64k tokens per request and 32k for the state plus the longest single question; Clef has a 65,536-token context.
|
|
474
495
|
|
|
475
496
|
## Supported Models
|
|
476
497
|
|
|
@@ -527,9 +548,10 @@ Provide models as plain name strings in the `models` array. Each entry is `auto`
|
|
|
527
548
|
| `claude-opus-4-5-20251101`, `claude-opus-4-1-20250805` | `opus-4.5`, `opus-4.1` | 200K | 64K / 32K | Earlier Opus tiers |
|
|
528
549
|
| `claude-sonnet-5-5` | `sonnet`, `sonnet-5.5`, `claude-sonnet` | 1M | 128K | Current Sonnet, adaptive thinking + effort, compaction |
|
|
529
550
|
| `claude-sonnet-4-6` | `sonnet-4.6` | 200K (1M beta) | 64K | Previous Sonnet, adaptive thinking |
|
|
530
|
-
| `claude-haiku-
|
|
551
|
+
| `claude-haiku-5-5` | `haiku`, `haiku-5.5`, `claude-haiku` | 1M | 128K | Current Haiku, adaptive thinking + effort, compaction |
|
|
552
|
+
| `claude-haiku-4-5-20251001` | `haiku-4.5` | 200K | 64K | Previous Haiku, extended thinking |
|
|
531
553
|
|
|
532
|
-
Models with adaptive thinking control depth via `reasoning_effort`, which is passed by name to Anthropic's `effort` parameter and clamped to what each model accepts: Opus 5.5, Sonnet 5.5, Fable 5, Opus 5, Opus 4.8, and Opus 4.7 take `low`–`max`; Opus 4.6 and Sonnet 4.6 lack `xhigh` (it becomes `max`); Opus 4.5 tops out at `high`. `none` and `minimal` become `low` everywhere. System prompts are automatically cached for 1 hour; cache stats appear in response metadata as `cache_creation_input_tokens` / `cache_read_input_tokens`.
|
|
554
|
+
Models with adaptive thinking control depth via `reasoning_effort`, which is passed by name to Anthropic's `effort` parameter and clamped to what each model accepts: Opus 5.5, Sonnet 5.5, Haiku 5.5, Fable 5, Opus 5, Opus 4.8, and Opus 4.7 take `low`–`max`; Opus 4.6 and Sonnet 4.6 lack `xhigh` (it becomes `max`); Opus 4.5 tops out at `high`. `none` and `minimal` become `low` everywhere. System prompts are automatically cached for 1 hour; cache stats appear in response metadata as `cache_creation_input_tokens` / `cache_read_input_tokens`.
|
|
533
555
|
|
|
534
556
|
### Mistral Models
|
|
535
557
|
|
|
@@ -592,7 +614,7 @@ All three are uncensored ("abliterated") reasoning models using Abliteration's O
|
|
|
592
614
|
**Claude** is available through the Claude Agent SDK, using Claude Code CLI authentication instead of an API key:
|
|
593
615
|
|
|
594
616
|
- **Model**: `claude` (namespaces: `claude`, `claude-code`, `claude-sdk`) — defaults to Claude Opus 5.5 (`claude-opus-5-5`), or `CLAUDE_DEFAULT_MODEL`
|
|
595
|
-
- **Model selection**: `claude-opus-5-5` (`opus`, `claude-opus`, `opus-5.5`), `claude-opus-5` (`opus-5`), `claude-fable-5-1` (`fable`, `claude-fable`, `fable-5.1`), `claude-fable-5` (`fable-5`), `claude-sonnet-5-5` (`sonnet`, `claude-sonnet`, `sonnet-5.5`), e.g. `claude:opus`, `claude:fable`, `claude:sonnet`. Other names are rejected with suggestions.
|
|
617
|
+
- **Model selection**: `claude-opus-5-5` (`opus`, `claude-opus`, `opus-5.5`), `claude-opus-5` (`opus-5`), `claude-fable-5-1` (`fable`, `claude-fable`, `fable-5.1`), `claude-fable-5` (`fable-5`), `claude-sonnet-5-5` (`sonnet`, `claude-sonnet`, `sonnet-5.5`), `claude-haiku-5-5` (`haiku`, `claude-haiku`, `haiku-5.5`), e.g. `claude:opus`, `claude:fable`, `claude:sonnet`, `claude:haiku`. Other names are rejected with suggestions.
|
|
596
618
|
- **Authentication**: `claude login` — no `ANTHROPIC_API_KEY` needed
|
|
597
619
|
- **Availability**: the Claude Agent SDK is installed and `~/.claude/.credentials.json` exists (`$CLAUDE_CONFIG_DIR` when set), or `CLAUDE_CODE_OAUTH_TOKEN`/`ANTHROPIC_API_KEY` is in the environment; on macOS the Keychain login is assumed. An expired login is caught at call time and bare-name/`auto` routing fails over.
|
|
598
620
|
- **Permissions**: runs with `bypassPermissions`
|
|
@@ -633,7 +655,7 @@ agy
|
|
|
633
655
|
Reach these only with the `copilot:` namespace (also `github-copilot:`, `copilot-sdk:`; e.g. `copilot:gpt-6.1-sol`) — Copilot never serves bare model names. `copilot` alone uses GPT-6.1 Sol, or `COPILOT_DEFAULT_MODEL`. Available when the Copilot SDK is installed; uses your GitHub Copilot subscription (`gh auth login`) — no API key needed:
|
|
634
656
|
|
|
635
657
|
- **OpenAI**: `gpt-6.1-sol` (aliases: `gpt-6`, `gpt-6.1`, `gpt-5`, `sol`), `gpt-6-sol`, `gpt-6-luna` (alias: `luna`), `gpt-5.6-sol` (alias: `gpt-5.6`), `gpt-5.6-terra`, `gpt-5.6-luna` (all accept `reasoning_effort`)
|
|
636
|
-
- **Anthropic**: `claude-opus-5.5` (aliases: `opus`, `claude`), `claude-fable-5` (alias: `fable`), `claude-sonnet-5.5` (alias: `sonnet`), `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4.8`
|
|
658
|
+
- **Anthropic**: `claude-opus-5.5` (aliases: `opus`, `claude`), `claude-fable-5.1` (alias: `fable`), `claude-fable-5`, `claude-sonnet-5.5` (alias: `sonnet`), `claude-haiku-5.5` (alias: `haiku`), `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4.8`
|
|
637
659
|
- **Google**: `gemini-3.1-pro-preview` (aliases: `gemini`, `gemini-3.1-pro`), `gemini-3.8-flash` (aliases: `gemini-3.8`, `flash-3.8`), `gemini-3.5-flash` (alias: `gemini-flash`)
|
|
638
660
|
- Any other `copilot:<id>` is rejected with suggestions
|
|
639
661
|
|
|
@@ -836,6 +858,8 @@ DEEPSEEK_API_KEY=...
|
|
|
836
858
|
OPENROUTER_API_KEY=sk-or-...
|
|
837
859
|
ABLITERATION_API_KEY=ak_...
|
|
838
860
|
TYPESAFE_API_KEY=... # decide tool only
|
|
861
|
+
CLOUDFLARE_ACCOUNT_ID=... # decide tool only (Clef), with CLOUDFLARE_API_TOKEN
|
|
862
|
+
CLOUDFLARE_API_TOKEN=...
|
|
839
863
|
```
|
|
840
864
|
|
|
841
865
|
**MCP client configuration:**
|
package/docs/PROVIDERS.md
CHANGED
|
@@ -57,7 +57,8 @@ This guide documents all supported AI providers in the Converse MCP Server and t
|
|
|
57
57
|
- `claude-sonnet-5-5` (aliases `sonnet`, `sonnet-5.5`, `claude-sonnet`) - Current Sonnet: speed and capability for everyday coding and agentic work; adaptive thinking, effort `low`–`max` (1M context, 128K output)
|
|
58
58
|
- `claude-sonnet-4-6` (alias `sonnet-4.6`) - Previous Sonnet generation with adaptive thinking (64K output)
|
|
59
59
|
- `claude-sonnet-4-5-20250929` - Legacy Sonnet (64K output)
|
|
60
|
-
- `claude-haiku-
|
|
60
|
+
- `claude-haiku-5-5` (aliases `haiku`, `haiku-5.5`, `claude-haiku`) - Current Haiku: fastest and cheapest Claude for high-volume and latency-sensitive work; adaptive thinking, effort `low`–`max` (1M context, 128K output)
|
|
61
|
+
- `claude-haiku-4-5-20251001` (alias `haiku-4.5`) - Previous Haiku generation with extended thinking (64K output)
|
|
61
62
|
|
|
62
63
|
### Mistral
|
|
63
64
|
- **API Key Format**: 32+ character string
|
|
@@ -229,7 +230,8 @@ agy
|
|
|
229
230
|
- `claude-fable-5-1` (aliases: `fable`, `claude-fable`, `fable-5.1`) - Claude Fable 5.1 (`claude:fable`)
|
|
230
231
|
- `claude-fable-5` (alias: `fable-5`) - Claude Fable 5.0
|
|
231
232
|
- `claude-sonnet-5-5` (aliases: `sonnet`, `claude-sonnet`, `sonnet-5.5`) - Claude Sonnet 5.5 (`claude:sonnet`)
|
|
232
|
-
-
|
|
233
|
+
- `claude-haiku-5-5` (aliases: `haiku`, `claude-haiku`, `haiku-5.5`) - Claude Haiku 5.5 (`claude:haiku`)
|
|
234
|
+
- Names outside this catalog are rejected with suggestions (e.g. `claude:sonnet-4.6` suggests `anthropic:sonnet-4.6`)
|
|
233
235
|
|
|
234
236
|
**Key Features:**
|
|
235
237
|
- **Subscription Access**: Uses your Claude subscription instead of API credits
|
|
@@ -242,7 +244,7 @@ agy
|
|
|
242
244
|
**Differences from Anthropic API Provider:**
|
|
243
245
|
- **Authentication**: Claude Code login vs `ANTHROPIC_API_KEY`
|
|
244
246
|
- **Billing**: Claude subscription vs pay-per-use API
|
|
245
|
-
- **Model Routing**: `claude` and `claude:<model>` → SDK provider; `anthropic:<model>` → API provider; bare names both serve as the same model (`opus`, `sonnet`, `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-fable-5`) → SDK first, then the API on authentication/availability failure; bare names only the API serves (e.g., `haiku`, `sonnet-4.6`) → API provider. Bare `fable` is Fable 5.1 on the SDK but Fable 5 on the API, so it does not fail over between them.
|
|
247
|
+
- **Model Routing**: `claude` and `claude:<model>` → SDK provider; `anthropic:<model>` → API provider; bare names both serve as the same model (`opus`, `sonnet`, `haiku`, `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-haiku-5-5`, `claude-fable-5`) → SDK first, then the API on authentication/availability failure; bare names only the API serves (e.g., `haiku-4.5`, `sonnet-4.6`) → API provider. Bare `fable` is Fable 5.1 on the SDK but Fable 5 on the API, so it does not fail over between them.
|
|
246
248
|
- **Permissions**: The SDK runs with `bypassPermissions`, and bare names and `auto` reach it whenever it is available. Use `anthropic:<model>` to keep a request on the plain API.
|
|
247
249
|
|
|
248
250
|
### GitHub Copilot SDK
|
|
@@ -254,7 +256,7 @@ agy
|
|
|
254
256
|
- **Supported Models** (namespace-only: reach them with `copilot:`, `github-copilot:` or `copilot-sdk:`, e.g. `copilot:gpt-6.1-sol`; Copilot never serves bare model names):
|
|
255
257
|
- `copilot` - GPT-6.1 Sol, or `COPILOT_DEFAULT_MODEL`
|
|
256
258
|
- OpenAI: `gpt-6.1-sol` (aliases: `gpt-6`, `gpt-6.1`, `gpt-5`, `sol`), `gpt-6-sol`, `gpt-6-luna` (alias: `luna`), `gpt-5.6-sol` (alias: `gpt-5.6`), `gpt-5.6-terra`, `gpt-5.6-luna`
|
|
257
|
-
- Anthropic: `claude-opus-5.5` (aliases: `opus`, `claude`; Copilot Pro+/Max/Business/Enterprise), `claude-fable-5` (alias: `fable`), `claude-sonnet-5.5` (alias: `sonnet`), `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4.8`
|
|
259
|
+
- Anthropic: `claude-opus-5.5` (aliases: `opus`, `claude`; Copilot Pro+/Max/Business/Enterprise), `claude-fable-5.1` (alias: `fable`), `claude-fable-5`, `claude-sonnet-5.5` (alias: `sonnet`), `claude-haiku-5.5` (alias: `haiku`), `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4.8`
|
|
258
260
|
- Google: `gemini-3.1-pro-preview` (aliases: `gemini`, `gemini-3.1-pro`), `gemini-3.8-flash` (aliases: `gemini-3.8`, `flash-3.8`), `gemini-3.5-flash` (alias: `gemini-flash`)
|
|
259
261
|
- **Reasoning**: The GPT-6 and GPT-5.6 tiers accept `reasoning_effort` (clamped onto Copilot's `low`–`xhigh`).
|
|
260
262
|
- **Unknown IDs**: Any other `copilot:<id>` is rejected with suggestions; only the IDs and aliases above are accepted.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "converse-mcp-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"description": "Converse MCP Server - Converse with other LLMs with chat and consensus tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -93,22 +93,22 @@
|
|
|
93
93
|
".env.example"
|
|
94
94
|
],
|
|
95
95
|
"dependencies": {
|
|
96
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
97
|
-
"@anthropic-ai/sdk": "^0.
|
|
98
|
-
"@github/copilot-sdk": "^1.0.
|
|
96
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.293",
|
|
97
|
+
"@anthropic-ai/sdk": "^0.132.0",
|
|
98
|
+
"@github/copilot-sdk": "^1.0.17",
|
|
99
99
|
"@google/genai": "^2.27.0",
|
|
100
100
|
"@lydell/node-pty": "1.2.0-beta.15",
|
|
101
101
|
"@mistralai/mistralai": "^2.7.0",
|
|
102
|
-
"@modelcontextprotocol/sdk": "^1.32.
|
|
103
|
-
"@openai/codex-sdk": "^0.
|
|
102
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
103
|
+
"@openai/codex-sdk": "^0.161.0",
|
|
104
104
|
"cors": "^2.8.6",
|
|
105
|
-
"dotenv": "^18.0.
|
|
105
|
+
"dotenv": "^18.0.6",
|
|
106
106
|
"express": "^5.2.1",
|
|
107
107
|
"lru-cache": "^11.5.3",
|
|
108
|
-
"nanoid": "^6.0.
|
|
109
|
-
"openai": "^7.
|
|
108
|
+
"nanoid": "^6.0.2",
|
|
109
|
+
"openai": "^7.30.0",
|
|
110
110
|
"p-limit": "^7.3.3",
|
|
111
|
-
"vite": "^8.3.
|
|
111
|
+
"vite": "^8.3.3"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@vitest/coverage-v8": "^5.0.3",
|
package/src/config.js
CHANGED
|
@@ -241,6 +241,18 @@ const CONFIG_SCHEMA = {
|
|
|
241
241
|
description:
|
|
242
242
|
'Optional OpenRouter attribution title, sent as the canonical X-OpenRouter-Title header',
|
|
243
243
|
},
|
|
244
|
+
// Cloudflare Workers AI (Clef decision models for the decide tool)
|
|
245
|
+
CLOUDFLARE_ACCOUNT_ID: {
|
|
246
|
+
type: 'string',
|
|
247
|
+
required: false,
|
|
248
|
+
description: 'Cloudflare account ID for Workers AI (decide tool: Clef)',
|
|
249
|
+
},
|
|
250
|
+
CLOUDFLARE_API_TOKEN: {
|
|
251
|
+
type: 'string',
|
|
252
|
+
required: false,
|
|
253
|
+
secret: true,
|
|
254
|
+
description: 'Cloudflare API token with Workers AI access (decide tool: Clef)',
|
|
255
|
+
},
|
|
244
256
|
OPENROUTER_DYNAMIC_MODELS: {
|
|
245
257
|
type: 'boolean',
|
|
246
258
|
default: false,
|
|
@@ -688,6 +700,9 @@ export async function loadConfig() {
|
|
|
688
700
|
// API-key providers require keys; SDK-based providers (codex, claude, gemini-cli, copilot)
|
|
689
701
|
// work via subscription auth — check if their packages are actually installed
|
|
690
702
|
const availableKeys = Object.keys(config.apiKeys);
|
|
703
|
+
if (config.providers.cloudflareaccountid && config.providers.cloudflareapitoken) {
|
|
704
|
+
availableKeys.push('cloudflare');
|
|
705
|
+
}
|
|
691
706
|
const hasVertexAI =
|
|
692
707
|
config.providers.googlegenaiusevertexai &&
|
|
693
708
|
config.providers.googlecloudproject &&
|
|
@@ -721,7 +736,7 @@ export async function loadConfig() {
|
|
|
721
736
|
|
|
722
737
|
if (availableKeys.length === 0 && !hasVertexAI && !hasSdkProvider) {
|
|
723
738
|
errors.push(
|
|
724
|
-
'At least one API key must be configured: OPENAI_API_KEY, XAI_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY, ANTHROPIC_API_KEY, MISTRAL_API_KEY, DEEPSEEK_API_KEY, OPENROUTER_API_KEY, ABLITERATION_API_KEY, or
|
|
739
|
+
'At least one API key must be configured: OPENAI_API_KEY, XAI_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY, ANTHROPIC_API_KEY, MISTRAL_API_KEY, DEEPSEEK_API_KEY, OPENROUTER_API_KEY, ABLITERATION_API_KEY, TYPESAFE_API_KEY, or CLOUDFLARE_ACCOUNT_ID with CLOUDFLARE_API_TOKEN. Alternatively, configure Google Vertex AI or use an SDK-based provider (codex, claude, copilot) or the Antigravity CLI (gemini-cli).',
|
|
725
740
|
);
|
|
726
741
|
}
|
|
727
742
|
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision wire formats
|
|
3
|
+
*
|
|
4
|
+
* The `decide` tool speaks System One: a `state`, a map of typed questions
|
|
5
|
+
* (`noul` / `choice` / `score`), and a map of answers. Each format turns that
|
|
6
|
+
* into one host's request body and maps the host's response back to System One
|
|
7
|
+
* answer shapes, so the tool formats every host's answers the same way.
|
|
8
|
+
*
|
|
9
|
+
* Every format returns `{ model, answers, usage, id }` from `parse`.
|
|
10
|
+
* `request(model, state, questions, images)` receives images as
|
|
11
|
+
* `{ mimeType, base64 }`; `images: true` marks formats that can send them.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { DecisionError } from './http.js';
|
|
15
|
+
|
|
16
|
+
function asText(value) {
|
|
17
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function dataUrl(image) {
|
|
21
|
+
return `data:${image.mimeType};base64,${image.base64}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A score level given as `{ label, description? }` rather than a single
|
|
26
|
+
* description. OpenAI's levels take that pair natively; System One only takes
|
|
27
|
+
* one description per level.
|
|
28
|
+
*/
|
|
29
|
+
export function isLabeledLevel(level) {
|
|
30
|
+
return level !== null && typeof level === 'object' && !Array.isArray(level) && 'label' in level;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function flattenLevel(level) {
|
|
34
|
+
if (!isLabeledLevel(level)) return level;
|
|
35
|
+
return level.description === undefined ? level.label : `${level.label}: ${asText(level.description)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Rewrite labeled score levels into System One's one-description form. */
|
|
39
|
+
function toSystemOneQuestions(questions) {
|
|
40
|
+
return Object.fromEntries(
|
|
41
|
+
Object.entries(questions).map(([name, question]) =>
|
|
42
|
+
question.type === 'score' && question.criteria.some(isLabeledLevel)
|
|
43
|
+
? [name, { ...question, criteria: question.criteria.map(flattenLevel) }]
|
|
44
|
+
: [name, question],
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* TypeSafe and OpenRouter serve System One natively. Jev is text only, and
|
|
51
|
+
* OpenRouter's image routing for other decision models is unverified, so this
|
|
52
|
+
* format sends no images.
|
|
53
|
+
*/
|
|
54
|
+
export const systemOneFormat = {
|
|
55
|
+
images: false,
|
|
56
|
+
request: (model, state, questions) => ({ model, state, questions: toSystemOneQuestions(questions) }),
|
|
57
|
+
parse: (body) => body,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Cloudflare rejects requests outside these bounds; checking first turns a
|
|
61
|
+
// nested validation dump into a clear message.
|
|
62
|
+
const CLEF_MAX_QUESTIONS = 64;
|
|
63
|
+
const CLEF_QUESTION_ID = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
64
|
+
const CLEF_MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
65
|
+
const CLEF_MAX_TOTAL_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
66
|
+
// Workers AI rejects a request whose estimated size exceeds the context
|
|
67
|
+
// window before running it, estimating one token per four characters of the
|
|
68
|
+
// body including base64 image data, though far fewer tokens are billed.
|
|
69
|
+
const CLEF_CONTEXT_TOKENS = 65536;
|
|
70
|
+
const CLEF_CHARS_PER_ESTIMATED_TOKEN = 4;
|
|
71
|
+
|
|
72
|
+
function decodedBytes(image) {
|
|
73
|
+
return Math.floor((image.base64.length * 3) / 4);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function checkClefImages(images, state, questions) {
|
|
77
|
+
const sizes = images.map(decodedBytes);
|
|
78
|
+
const tooLarge = sizes.findIndex((bytes) => bytes > CLEF_MAX_IMAGE_BYTES);
|
|
79
|
+
if (tooLarge !== -1) {
|
|
80
|
+
throw new DecisionError(`Clef accepts images up to 4 MiB each; image ${tooLarge + 1} is ${(sizes[tooLarge] / 1048576).toFixed(1)} MiB.`, { terminal: true });
|
|
81
|
+
}
|
|
82
|
+
if (sizes.reduce((a, b) => a + b, 0) > CLEF_MAX_TOTAL_IMAGE_BYTES) {
|
|
83
|
+
throw new DecisionError('Clef accepts at most 8 MiB of images per call.', { terminal: true });
|
|
84
|
+
}
|
|
85
|
+
const chars = images.reduce((n, image) => n + dataUrl(image).length, 0) + asText(state ?? '').length + JSON.stringify(questions).length;
|
|
86
|
+
const estimate = Math.ceil(chars / CLEF_CHARS_PER_ESTIMATED_TOKEN);
|
|
87
|
+
if (estimate > CLEF_CONTEXT_TOKENS) {
|
|
88
|
+
throw new DecisionError(
|
|
89
|
+
`Cloudflare estimates this request at ~${estimate} tokens (4 base64 characters per token), over Clef's ${CLEF_CONTEXT_TOKENS}-token window. ` +
|
|
90
|
+
'Downscale or recompress the images (about 180 KB in total fits), or use gpt-6-luna.',
|
|
91
|
+
{ terminal: true },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Cloudflare Workers AI runs Clef with the System One request and answer
|
|
98
|
+
* shapes, wrapped in its `{ result, success, errors }` envelope. Clef adds up
|
|
99
|
+
* to four embedded images; `state` stays required, but may be empty when the
|
|
100
|
+
* images are the whole input.
|
|
101
|
+
*/
|
|
102
|
+
export const cloudflareFormat = {
|
|
103
|
+
images: true,
|
|
104
|
+
request(model, state, questions, images = []) {
|
|
105
|
+
const ids = Object.keys(questions);
|
|
106
|
+
if (ids.length > CLEF_MAX_QUESTIONS) {
|
|
107
|
+
throw new DecisionError(`Clef accepts at most ${CLEF_MAX_QUESTIONS} questions per call (got ${ids.length}).`, { terminal: true });
|
|
108
|
+
}
|
|
109
|
+
const bad = ids.filter((id) => !CLEF_QUESTION_ID.test(id));
|
|
110
|
+
if (bad.length) {
|
|
111
|
+
throw new DecisionError(
|
|
112
|
+
`Clef question names may use only letters, digits, "_", ".", and "-" (max 100 characters): ${bad.join(', ')}.`,
|
|
113
|
+
{ terminal: true },
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const body = { model, state: state ?? '', questions: toSystemOneQuestions(questions) };
|
|
117
|
+
if (images.length === 0) return body;
|
|
118
|
+
checkClefImages(images, state, questions);
|
|
119
|
+
return { ...body, images: images.map(dataUrl) };
|
|
120
|
+
},
|
|
121
|
+
parse(body) {
|
|
122
|
+
if (body?.success === false) {
|
|
123
|
+
const detail = (body.errors || []).map((e) => e?.message || JSON.stringify(e)).join('; ');
|
|
124
|
+
throw new DecisionError(`Cloudflare reported failure: ${detail || 'no details'}`, { retryable: true });
|
|
125
|
+
}
|
|
126
|
+
return body?.result ?? body;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* OpenAI's predicates take no criteria, so a noul question's true/false
|
|
132
|
+
* descriptions become part of its instructions.
|
|
133
|
+
*/
|
|
134
|
+
function predicateInstructions(question) {
|
|
135
|
+
const instructions = asText(question.instructions);
|
|
136
|
+
if (!question.criteria) return instructions;
|
|
137
|
+
return `${instructions}\n\nAnswer true if: ${asText(question.criteria.true)}\nAnswer false if: ${asText(question.criteria.false)}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function toOpenAIQuestion(name, question) {
|
|
141
|
+
if (question.type === 'noul') {
|
|
142
|
+
return { type: 'predicate', name, instructions: predicateInstructions(question) };
|
|
143
|
+
}
|
|
144
|
+
if (question.type === 'choice') {
|
|
145
|
+
return {
|
|
146
|
+
type: 'choice',
|
|
147
|
+
name,
|
|
148
|
+
instructions: asText(question.instructions),
|
|
149
|
+
choices: Object.entries(question.criteria).map(([value, description]) =>
|
|
150
|
+
description === null || description === undefined ? { value } : { value, description: asText(description) },
|
|
151
|
+
),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
type: 'score',
|
|
156
|
+
name,
|
|
157
|
+
instructions: asText(question.instructions),
|
|
158
|
+
levels: question.criteria.map((level) => {
|
|
159
|
+
if (!isLabeledLevel(level)) return { label: asText(level) };
|
|
160
|
+
return level.description === undefined
|
|
161
|
+
? { label: level.label }
|
|
162
|
+
: { label: level.label, description: asText(level.description) };
|
|
163
|
+
}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function fromOpenAIAnswer(answer) {
|
|
168
|
+
const { type, name: _name, ...rest } = answer;
|
|
169
|
+
if (type === 'predicate') {
|
|
170
|
+
return { type: 'noul', noul: answer.probability };
|
|
171
|
+
}
|
|
172
|
+
if (type === 'choice') {
|
|
173
|
+
return {
|
|
174
|
+
type: 'choice',
|
|
175
|
+
choice: answer.choice,
|
|
176
|
+
probabilities: Object.fromEntries((answer.probabilities || []).map((p) => [p.value, p.probability])),
|
|
177
|
+
confidence: answer.confidence,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (type === 'score') {
|
|
181
|
+
const levels = answer.probabilities || [];
|
|
182
|
+
return {
|
|
183
|
+
type: 'score',
|
|
184
|
+
score: answer.score,
|
|
185
|
+
legend: Object.fromEntries(levels.map((p) => [String(p.value), p.label])),
|
|
186
|
+
probabilities: Object.fromEntries(levels.map((p) => [String(p.value), p.probability])),
|
|
187
|
+
confidence: answer.confidence,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return { type, ...rest };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* OpenAI's Decisions API (`POST /v1/decisions`) asks the same three kinds of
|
|
195
|
+
* question in its own schema: string `input` instead of `state`, a question
|
|
196
|
+
* array with `name`, `predicate` for `noul`, `choices` and `levels` arrays,
|
|
197
|
+
* and answers as an array with probability lists. It only accepts strings for
|
|
198
|
+
* `input` and `instructions`, so structured values are sent as JSON text.
|
|
199
|
+
* Images turn `input` into one user message of text and `input_image` parts.
|
|
200
|
+
*/
|
|
201
|
+
export const openaiFormat = {
|
|
202
|
+
images: true,
|
|
203
|
+
request(model, state, questions, images = []) {
|
|
204
|
+
const input = images.length === 0
|
|
205
|
+
? asText(state)
|
|
206
|
+
: [{
|
|
207
|
+
role: 'user',
|
|
208
|
+
content: [
|
|
209
|
+
...(state === undefined ? [] : [{ type: 'input_text', text: asText(state) }]),
|
|
210
|
+
...images.map((image) => ({ type: 'input_image', image_url: dataUrl(image) })),
|
|
211
|
+
],
|
|
212
|
+
}];
|
|
213
|
+
return {
|
|
214
|
+
model,
|
|
215
|
+
input,
|
|
216
|
+
questions: Object.entries(questions).map(([name, question]) => toOpenAIQuestion(name, question)),
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
parse(body) {
|
|
220
|
+
if (!Array.isArray(body?.answers)) return body;
|
|
221
|
+
const answers = Object.fromEntries(body.answers.map((answer) => [answer.name, fromOpenAIAnswer(answer)]));
|
|
222
|
+
return {
|
|
223
|
+
model: body.model,
|
|
224
|
+
answers,
|
|
225
|
+
usage: body.usage ? { input_tokens: body.usage.input_tokens, output_tokens: body.usage.output_tokens } : undefined,
|
|
226
|
+
...(body.id ? { id: body.id } : {}),
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
};
|