Compare commits

..

3 Commits

Author SHA1 Message Date
benzntech
6428c85e66 docs(providers): use canonical chromewebstore URL for Cookie Editor install link 2026-08-09 02:22:42 -03:00
benzntech
ce08eae72d docs(providers): add ChatGPT Web session credential guide
Add docs/providers/CHATGPT_WEB.md covering how to obtain and update
chatgpt-web session credentials via the Cookie Editor extension:

- extension option settings (export format, HttpOnly, domain filter)
- verifying __Secure-next-auth.session-token in a live network request
- adding/updating credentials in the dashboard + bulk/session-pool APIs
- contributing changes back via a PR

Fill the previously _(verify)_ ChatGPT Web row in WEB-COOKIE-GUIDE.md.
2026-08-09 02:22:42 -03:00
diegosouzapw
7ffa5e24fd fix(deps): bump transitive deps for 6 Dependabot + remaining audit vulns on main
Same overrides as #9464 (ip-address, hono, fast-uri, socket.io-parser, undici)
applied directly to main. Also covers brace-expansion (scoped), js-yaml v4 copies,
and mermaid.

npm audit: 6→0 vulnerabilities.
Closes Dependabot #161-#166.
2026-08-09 02:22:42 -03:00
9 changed files with 440 additions and 518 deletions

View File

@@ -1,9 +1,5 @@
{
"_rebaseline_2026_08_09_9296_adobe_media_capabilities": "PR #9296 (artickc, fix/adobe-firefly-model-capabilities) own growth: src/app/api/v1/models/catalog.ts 1590->1597 (+7). The image and video catalog serializers now expose the already-normalized Adobe Firefly discovery capability data (media_capabilities, plus the existing video modality/size fields) at their only response-emission chokepoints. The discovery parser and capability normalization remain in open-sse/services/adobeFireflyModels.ts; extracting these seven serialization fields would obscure the catalog contract. Covered by tests/unit/adobe-firefly.test.ts and tests/unit/image-upscale.test.ts.",
"_rebaseline_2026_08_08_v3850_base_drift_batch_9757": "Base drift on release/v3.8.50, not own growth: the 08-06..08-08 merge batches grew 12 already-frozen (or newly-landed) files without carrying their rebaselines — the dedicated rebaseline PR #9616 was closed as 'superseded' but its file-size entries never actually reached the base, and later merges (#8894 combos page, #9539 EditConnectionModal, #8895 models route, #9294/#9293 catalog, #9541 db/core, #8970 tokenHealthCheck, #8925 mcp schemas+server, #8890 accountFallback, #9467 chat.ts, #8931 openai-to-kiro, ProxyRegistryManager) kept growing them. All 12 values re-measured on THIS branch's tree (= pure tip + this PR's 1-line chat.ts fix, which adds zero lines). This PR's own source changes (chat.ts identifier restore, stream.ts format carve-out) do not grow any frozen file past these values.",
"_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.",
"_rebaseline_2026_08_08_9183_reasoning_cache_index_sync": "Extracted fix(responses-api): sync reasoning-cache write index with the fixed read side (from the originally-authored #9183) — chatCore.ts's write side cached every response under a hardcoded messageIndex:0, and translator/index.ts's plain-turn (non-tool-call) cache-key lookup ALSO still hardcoded messageIndex 0 at its call site (a second, previously-undiscovered instance of the same hardcoding bug, found while re-verifying this fix against the current upstream tip — the two never agreed once a conversation went past its first assistant turn, so DeepSeek/Xiaomi-mimo plain-turn reasoning replay silently missed the cache). Own growth: open-sse/handlers/chatCore.ts 5034->5042 (+8, computing messageIndex from the incoming request's message count at both the streaming and non-streaming cache-write call sites) — irreducible call-site wiring. Covered by tests/unit/reasoning-cache.test.ts (new end-to-end write/read regression test, rebaselined below) and tests/unit/translator-helper-branches.test.ts fixture updates. Other #9183 sub-fixes (output_index collision prevention, reasoning-content-alias generalization) were originally assumed already superseded by upstream's own independent fix — a live incident 2026-08-08 disproved that for the message-vs-tool-call collision case specifically (fixed separately in #9822); not re-extracted here since this PR's own scope is the narrower messageIndex sync only.",
"_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
"_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgents conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PRs own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.",
"_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() — when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) — new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.",
"_rebaseline_2026_07_25_8499_ts7_result_union_predicates": "PR #8499 (backryun, chore/ts7-types-executor-scattered) own growth: muse-spark-web.ts 1396->1405 (+9, irreducible). Under this workspace's `strictNullChecks: false`, the boolean-literal discriminant on `GraphqlResult` (`{ ok: true } | { ok: false; error: string }`) narrows the positive `.ok===true` branch but leaves `!result.ok` at the full union under TS7, making `.error` unreachable to the checker at the two call sites (warmup, mode-switch). Fixed by adding a single `isGraphqlFailure()` type-predicate helper (doc comment + 3-line body) reused at both call sites instead of duplicating the predicate inline — not extractable to a shared module without splitting a single-file executor's local narrowing helper out of its own file. Covered by the existing muse-spark-web executor test suite (no behavior change, pure narrowing fix).",
@@ -166,7 +162,6 @@
"cap": 1000,
"testCap": 1000,
"testFrozen": {
"tests/unit/reasoning-cache.test.ts": 1035,
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
@@ -355,7 +350,7 @@
"open-sse/executors/deepseek-web.ts": 1148,
"open-sse/executors/grok-web.ts": 1044,
"open-sse/executors/muse-spark-web.ts": 1405,
"open-sse/handlers/chatCore.ts": 5042,
"open-sse/handlers/chatCore.ts": 5034,
"open-sse/handlers/imageGeneration.ts": 3101,
"open-sse/handlers/responseSanitizer.ts": 1128,
"open-sse/handlers/search.ts": 1536,

View File

@@ -18,7 +18,7 @@ Unlike API-key providers, Web Cookie providers authenticate using the credential
Many authentication issues are caused by copying cookies from the wrong place.
## Do NOT copy from Cookie Storage
## Do NOT copy from Cookie Storage
Most browsers expose stored cookies through:
@@ -36,7 +36,7 @@ Although these cookies look correct, they may be:
Using these values may cause authentication failures even if they appear valid.
## Copy from a Live Request
## Copy from a Live Request
Instead, use the cookies from a successful request:
@@ -80,14 +80,14 @@ The exact credentials required depend on the provider.
Different websites store authentication differently. Some require only cookies, while others may require additional headers or tokens.
| Provider | Credential Format | Provider Guide |
|----------|-------------------|----------------|
| Claude Web | Full Cookie request header | `docs/providers/CLAUDE_WEB.md` |
| ChatGPT Web | _(verify)_ | |
| Gemini Web | _(verify)_ | |
| Copilot Web | _(verify)_ | |
| Grok Web | _(verify)_ | |
| ... | ... | ... |
| Provider | Credential Format | Provider Guide |
| ----------- | -------------------------------------------------------------- | ------------------------------- |
| Claude Web | Full Cookie request header | `docs/providers/CLAUDE_WEB.md` |
| ChatGPT Web | Full Cookie header or `__Secure-next-auth.session-token` value | `docs/providers/CHATGPT_WEB.md` |
| Gemini Web | _(verify)_ | |
| Copilot Web | _(verify)_ | |
| Grok Web | _(verify)_ | |
| ... | ... | ... |
> Update this table as new Web Cookie providers are added or existing providers change their authentication requirements.

View File

@@ -0,0 +1,143 @@
---
title: "Providers — ChatGPT Web (session credentials via Cookie Editor)"
version: 3.8.50
lastUpdated: 2026-08-08
---
# Providers — ChatGPT Web (Plus/Pro session credentials)
`chatgpt-web` (alias `cgpt-web`, display name **ChatGPT Web (Plus/Pro)**) sends OpenAI-format chat requests through an authenticated `chatgpt.com` browser session. It authenticates with the `__Secure-next-auth.session-token` cookie — **no API key required**.
> **New to Web Cookie providers?**
>
> Read **`docs/getting-started/WEB-COOKIE-GUIDE.md`** for the general setup process, limitations, and troubleshooting before following this provider-specific guide.
---
## 1. What credential does OmniRoute need?
Defined in `src/shared/constants/providers/web-cookie.ts` + `src/shared/providers/webSessionCredentials.ts`:
| Field | Value |
| -------------------------- | ----------------------------------------------------------------------------- |
| Provider id | `chatgpt-web` |
| Credential name | `__Secure-next-auth.session-token` |
| Accepts full Cookie header | ✅ yes |
| Accepted storage keys | `cookie`, `sessionToken`, `session-token`, `__Secure-next-auth.session-token` |
Two paste formats both work:
- **Bare value** — just the token contents: `eyJhbGciOi...`
- **Full Cookie header** — `__Secure-next-auth.session-token=eyJhbGciOi...; cf_clearance=...` (preferred — carries rotation/anti-bot cookies the executor needs)
---
## 2. Get the cookie fast with Cookie Editor
> **Why Cookie Editor instead of DevTools?** The repo's general guide insists you copy from a **live network request**, not cookie storage. Cookie Editor gives you both in one click: it can read the _live_ cookies for the domain (including `HttpOnly` ones DevTools hides) and export them as a ready-made Cookie header — no manual request-hunting, no stale-storage pitfalls.
### 2.1 Install and pin
1. Install **[Cookie-Editor](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)** (Moustachauve) in Chrome/Edge, or the Firefox equivalent.
2. Pin it to the toolbar (right-click icon → Pin).
### 2.2 Update the extension's option settings (one-time)
Click the Cookie Editor icon → the **⚙️ Options** tab, then set:
| Option | Set to | Why |
| ----------------------------------- | ------------------- | --------------------------------------------------------------------------------------- |
| **Export format** | `Cookie header` | Produces a paste-ready `name=value; name=value` string — exactly what OmniRoute accepts |
| **Show / include HttpOnly cookies** | ON | `__Secure-next-auth.session-token` is HttpOnly; hidden by default in some skins |
| **Show expired cookies** | OFF | Keeps the list clean — expired tokens are useless |
| **Domain filter behavior** | Active tab's domain | Auto-scopes to `chatgpt.com` when you open the popup there |
### 2.3 Copy the credential
1. Go to **https://chatgpt.com** and make sure you're **signed in with the Plus/Pro account** you want OmniRoute to use.
2. Open a conversation and send at least one message (forces the session token to be live/refreshed).
3. Click the **Cookie Editor** icon → the popup shows only `chatgpt.com` cookies.
4. Find `__Secure-next-auth.session-token`. If it's split into chunks (`__Secure-next-auth.session-token.0`, `.1`, …), select **all** of them — OmniRoute's `nextAuthCookie.ts` merges rotated chunk families.
5. Click **Export → Copy** (the icon in the header). With `Export format = Cookie header` this copies the full header in one click.
> **If the token is missing:** you're signed out, or the account has no active Plus/Pro subscription (chatgpt-web is a `subscriptionRisk: true` provider — free accounts won't authenticate).
---
## 3. Verify the required data (before pasting)
The repo's `WEB-COOKIE-GUIDE.md` mandates a live-request check. Do it once per session:
1. With chatgpt.com open, press **F12****Network** tab.
2. Refresh the page, then send a chat message.
3. Click the conversation request (e.g. `/backend-api/conversation` or the SSE stream) → **Headers****Request Headers****Cookie**.
4. Confirm it contains `__Secure-next-auth.session-token=...`**not** just `cf_clearance` or `__cf_bm`.
The value you copied in step 2.3 must match what the live request sends. If they differ, re-copy from Cookie Editor.
---
## 4. Add / update the credential in OmniRoute
### Dashboard (typical user path)
1. Open the OmniRoute dashboard → **Providers****Add Provider**.
2. Search **ChatGPT Web (Plus/Pro)** (id `chatgpt-web`).
3. Paste the copied cookie header into the credential field.
4. Click **Test Connection**.
5. Save.
> **Validation caveat:** per `WEB-COOKIE-GUIDE.md`, Test Connection only checks the format — it doesn't guarantee upstream auth (tracked as Issue #7857). If requests later 401, re-copy from a fresh live session (sessions rotate; the executor auto-merges `Set-Cookie` rotations, but expired tokens need a manual refresh).
### Bulk / session pools (many accounts)
For multiple ChatGPT sessions, use the bulk web-session import or session-pool endpoints:
- `POST /api/providers/bulk-web-session` — import many cookie credentials at once
- `GET /api/session-pools` + `/api/session-pools/[provider]` — pool rotation across accounts
Each credential blob must carry the `__Secure-next-auth.session-token` value under one of the accepted storage keys (`cookie`, `sessionToken`, `session-token`, or the cookie's exact name).
### Renewing when the session expires
Web sessions expire on sign-out or server-side rotation. Re-run steps 2.3 + 4 whenever requests start failing with 401/403. There is no refresh token — the cookie **is** the credential.
---
## 5. Contributing: update docs / constants and open a PR
If you changed the credential contract (new storage key, new cookie name, changed hint) or are filling the docs gap, contribute it:
1. Update `src/shared/providers/webSessionCredentials.ts` (credential name / placeholder / storage keys) or `src/shared/constants/providers/web-cookie.ts` (`authHint`).
2. Update this guide (`docs/providers/CHATGPT_WEB.md`) and the provider table in `docs/getting-started/WEB-COOKIE-GUIDE.md` (currently shows `_(verify)_` for ChatGPT Web).
3. Update `.env.example` + `docs/reference/ENVIRONMENT.md` if you touched env vars, then run:
```bash
node scripts/check/check-env-doc-sync.mjs # must pass
```
4. Run the provider/unit tests:
```bash
npm run test:unit
# targeted: tests/unit/chatgpt-web.test.ts (stealth path)
```
5. Commit with a Conventional Commit message (no `Co-Authored-By`), push to your fork, and open the PR against `main` (or `release/v3.8.49` per CONTRIBUTING.md):
```bash
git checkout -b docs/chatgpt-web-cookie-guide
git add docs/providers/CHATGPT_WEB.md docs/getting-started/WEB-COOKIE-GUIDE.md
git commit -m "docs(providers): add ChatGPT Web cookie credential guide"
git push -u origin docs/chatgpt-web-cookie-guide
gh pr create --base main --head docs/chatgpt-web-cookie-guide --title "docs(providers): ChatGPT Web session credential guide"
```
> ⚠️ **Never commit a real cookie value.** All examples above are placeholders. If a test fixture needs a token, use a fake `eyJhbGciOi...` string.
---
## Troubleshooting
| Symptom | Likely cause | Fix |
| -------------------------------- | -------------------------------------------- | --------------------------------------------------------- |
| Cookie not in Cookie Editor | Signed out / not HttpOnly-visible | Sign in; enable HttpOnly display in options |
| Token missing from live request | Free account, or request isn't authenticated | Use a Plus/Pro account; send a chat message first |
| 401 after Test Connection passed | Expired/rotated session (Issue #7857) | Re-copy from a fresh live request |
| Chunked token fails | Only one chunk pasted | Select all `__Secure-next-auth.session-token.*` chunks |
| 403 from a different machine | Cloudflare-pinned session | Copy + use from the same browser profile/IP as the cookie |

View File

@@ -207,6 +207,7 @@ import { stageTrace } from "./chatCore/stageTrace.ts";
import { attachCompressionUsageReceiptAfterAnalytics as attachCompressionUsageReceiptAfterAnalyticsFor } from "./chatCore/compressionUsageReceipt.ts";
import { prepareUpstreamBody } from "./chatCore/upstreamBody.ts";
import { getQuotaScopeLabelForProvider } from "../services/antigravityQuotaFamily.ts";
import {
getCallLogPipelineCaptureStreamChunks,
getCallLogPipelineMaxSizeBytes,
@@ -366,7 +367,9 @@ import {
isTpmExhausted,
isRpmExhausted,
} from "../services/geminiRateLimitTracker.ts";
import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
/**
* Core chat handler - shared between SSE and Worker
* Returns { success, response, status, error } for caller to handle fallback
@@ -386,8 +389,10 @@ import { isSmallEnoughForSemanticCache } from "../utils/estimateSize.ts";
* @param {boolean} options.isCombo - Whether this request is from a combo
* @param {string} options.connectionId - Connection ID for settings lookup
*/
// extractSystemRoleMessages extracted to chatCore/claudeSystemRole.ts (#3501); re-exported above so
// existing importers (e.g. tests/unit/system-role-extraction.test.ts) keep resolving it from here.
export async function handleChatCore({
body,
modelInfo,
@@ -423,6 +428,7 @@ export async function handleChatCore({
/* fail open */
}
}
// Per-request model-routing metadata (first extracted slice of the request-setup phase).
const { apiFormat, customModelTargetFormat, requestedModel } = resolveChatCoreRequestSetup(
modelInfo,
@@ -436,6 +442,7 @@ export async function handleChatCore({
// (not Math.random) purely to satisfy CodeQL js/insecure-randomness — this id
// is a log-correlation token, not a security secret.
const traceId = globalThis.crypto.randomUUID().slice(0, 6);
// Emit request.started event for real-time dashboard
setImmediate(() => {
emit("request.started", {
@@ -4326,14 +4333,9 @@ export async function handleChatCore({
try {
const firstChoice = translatedResponse?.choices?.[0];
const msg = firstChoice?.message;
// The response being cached now will be replayed as history on the *next*
// turn, where the read side (translator/index.ts) keys the lookup by the
// message's real position in that future `messages` array — i.e. right
// after everything the client sent this turn.
const bodyMessages = (body as { messages?: unknown[] } | null | undefined)?.messages;
cacheReasoningFromAssistantMessage(msg, provider, model, {
requestId: skillRequestId,
messageIndex: Array.isArray(bodyMessages) ? bodyMessages.length : 0,
messageIndex: 0,
});
} catch {
// Cache capture is non-critical — never block the response
@@ -4758,15 +4760,12 @@ export async function handleChatCore({
// with tool_calls so it can be replayed on subsequent turns (DeepSeek V4, Kimi K2, etc.)
if (normalizedStreamStatus === 200 && streamResponseBody) {
try {
const streamBody = streamResponseBody as Record<string, unknown>;
const choices = streamBody.choices as { message?: Record<string, unknown> }[] | undefined;
const body = streamResponseBody as Record<string, unknown>;
const choices = body.choices as { message?: Record<string, unknown> }[] | undefined;
const msg = choices?.[0]?.message;
// See the non-streaming capture above: messageIndex must match the
// position this message will occupy in the *next* turn's history.
const bodyMessages = (body as { messages?: unknown[] } | null | undefined)?.messages;
cacheReasoningFromAssistantMessage(msg, provider, model, {
requestId: skillRequestId,
messageIndex: Array.isArray(bodyMessages) ? bodyMessages.length : 0,
messageIndex: 0,
});
} catch {
// Cache capture is non-critical — never block the stream
@@ -5026,6 +5025,7 @@ export async function handleChatCore({
}),
};
}
export function isTokenExpiringSoon(expiresAt, bufferMs = 5 * 60 * 1000) {
if (!expiresAt) return false;
const expiresAtMs = new Date(expiresAt).getTime();

View File

@@ -570,7 +570,7 @@ export function translateRequest(
const cacheKey = hasToolCalls
? msg.tool_calls[0]?.id
: getAssistantMessageCacheKey(result, messageIndex);
: getAssistantMessageCacheKey(result, 0);
if (cacheKey) {
const cached = lookupReasoning(cacheKey);
if (cached) {

399
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.8.50",
"version": "3.8.49",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.8.50",
"version": "3.8.49",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
@@ -31,7 +31,7 @@
"clsx": "^2.1.1",
"commander": "^15.0.0",
"csv-stringify": "^6.7.0",
"dompurify": "^3.4.13",
"dompurify": "^3.4.12",
"express": "^5.2.1",
"fetch-socks": "^1.3.3",
"fflate": "^0.8.3",
@@ -79,7 +79,7 @@
"sqlite-vec": "^0.1.9",
"tailwind-merge": "^3.6.0",
"tsx": "^4.23.0",
"undici": "^8.10.0",
"undici": "^8.3.0",
"update-notifier": "^7.3.1",
"uuid": "^14.0.0",
"ws": "^8.18.0",
@@ -132,7 +132,6 @@
"lint-staged": "^17.0.8",
"lockfile-lint": "^5.0.0",
"node-loader": "^2.1.0",
"opencode-ai": "1.18.8",
"playwright-ctrf-json-reporter": "^0.0.29",
"prettier": "^3.8.3",
"promptfoo": "^0.121.18",
@@ -152,7 +151,7 @@
"@atjsh/llmlingua-2": "2.0.3",
"@huggingface/transformers": "3.5.2",
"@tensorflow/tfjs": "4.22.0",
"better-sqlite3": "^13.0.2",
"better-sqlite3": "^13.0.1",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"tls-client-node": "^0.2.0",
@@ -3693,6 +3692,9 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3709,6 +3711,9 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3725,6 +3730,9 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3741,6 +3749,9 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3757,6 +3768,9 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3773,6 +3787,9 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3789,6 +3806,9 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3805,6 +3825,9 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -3821,6 +3844,9 @@
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3843,6 +3869,9 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3865,6 +3894,9 @@
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3887,6 +3919,9 @@
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3909,6 +3944,9 @@
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3931,6 +3969,9 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3953,6 +3994,9 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3975,6 +4019,9 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -5346,6 +5393,9 @@
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5362,6 +5412,9 @@
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5378,6 +5431,9 @@
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -5394,6 +5450,9 @@
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10611,6 +10670,9 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10628,6 +10690,9 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10645,6 +10710,9 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -10662,6 +10730,9 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -12708,6 +12779,9 @@
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12721,6 +12795,9 @@
"arm"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -12734,6 +12811,9 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12747,6 +12827,9 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -12760,6 +12843,9 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"optional": true,
"os": [
"linux"
@@ -12773,6 +12859,9 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
@@ -13598,14 +13687,11 @@
}
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
@@ -13670,9 +13756,10 @@
}
},
"node_modules/better-sqlite3": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.2.tgz",
"integrity": "sha512-jW6oufeDhXZaiX9Lw5A+oerVClx4iFrI6uDj1zu7SqUAjak9vbJvA0NEcKLNxHiQHb6kYCoFzzXYV0YOauhV3g==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.1.tgz",
"integrity": "sha512-LYpmOXdkpQYf4wmlxkdzW01XGlOXNIbjLg45yNkh0FQ4814VbK9PdOFmhZpYbej+EZtR/i3FDdhEG98HqZdgnA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -13954,16 +14041,14 @@
}
},
"node_modules/brace-expansion": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
@@ -16979,9 +17064,9 @@
"license": "MIT"
},
"node_modules/dompurify": {
"version": "3.4.13",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
"version": "3.4.12",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
"integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -24392,25 +24477,6 @@
"node": ">= 14"
}
},
"node_modules/libxmljs2/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/libxmljs2/node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/libxmljs2/node_modules/cacache": {
"version": "19.0.1",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz",
@@ -26883,24 +26949,6 @@
"node": "*"
}
},
"node_modules/minimatch/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/minimatch/node_modules/brace-expansion": {
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@@ -27528,9 +27576,9 @@
"optional": true
},
"node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"funding": [
{
"type": "github",
@@ -28739,205 +28787,6 @@
}
}
},
"node_modules/opencode-ai": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-ai/-/opencode-ai-1.18.8.tgz",
"integrity": "sha512-eZvYK0rIc/NUDQ+s3LsO9gyUU3MswsbNOLZz06iPwVhbg/2jF6bkTaroBgiIdFWKwUn5sj+kSMc4TBYxFkMrNQ==",
"cpu": [
"arm64",
"x64"
],
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"os": [
"darwin",
"linux",
"win32"
],
"bin": {
"opencode": "bin/opencode.exe"
},
"optionalDependencies": {
"opencode-darwin-arm64": "1.18.8",
"opencode-darwin-x64": "1.18.8",
"opencode-darwin-x64-baseline": "1.18.8",
"opencode-linux-arm64": "1.18.8",
"opencode-linux-arm64-musl": "1.18.8",
"opencode-linux-x64": "1.18.8",
"opencode-linux-x64-baseline": "1.18.8",
"opencode-linux-x64-baseline-musl": "1.18.8",
"opencode-linux-x64-musl": "1.18.8",
"opencode-windows-arm64": "1.18.8",
"opencode-windows-x64": "1.18.8",
"opencode-windows-x64-baseline": "1.18.8"
}
},
"node_modules/opencode-darwin-arm64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-darwin-arm64/-/opencode-darwin-arm64-1.18.8.tgz",
"integrity": "sha512-ZZCIEgTvHxOHk52Aeqhq59t/R0aqs29bPIgu45XE4rkgjmn/XCkTWalCPtyzJHipdcEbq/g0lqsE1OlJV0oNbA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/opencode-darwin-x64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-darwin-x64/-/opencode-darwin-x64-1.18.8.tgz",
"integrity": "sha512-2EXRMJbRKnFPWI9oDU9tb7jDGmKiPmfjCLtwJMe3EF57h5wfcdEH9sP25bR3Og5NbE2M+PtMcJm0jMeHn2XoLQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/opencode-darwin-x64-baseline": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-darwin-x64-baseline/-/opencode-darwin-x64-baseline-1.18.8.tgz",
"integrity": "sha512-eLXa2tK9LRuZ5e20QG2k4dmWAA5xnLgJ1afRTSD0/ybE6CAeK02i8vFCnFFDaxuBo+gnq+yqO8AkqvN1m64V/Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
]
},
"node_modules/opencode-linux-arm64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-arm64/-/opencode-linux-arm64-1.18.8.tgz",
"integrity": "sha512-7kj3c9JEdryHgK+o8zE/N9KzTOdbiDn6KpY8dl+hM9n5Cnmxezx4IAlgJeC9QxpIx8Omop6CYuZ+17KfrKdKLw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-arm64-musl": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-arm64-musl/-/opencode-linux-arm64-musl-1.18.8.tgz",
"integrity": "sha512-tww5TF/LIOv/GoTNyzGYgqDRhbJrhoMu8R+p5yD/SpnXPg3rcfYREw2wRy9yikyPU9sAQksuIIteTsyGerPjlA==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64/-/opencode-linux-x64-1.18.8.tgz",
"integrity": "sha512-Sm4fbQ9BdLI6hgN6FYYX8Nql+Sqe/2EKHJu3iWg0UYs93AXN4ROi0rvOmRbMk+ycYgOchb0hL6Ti2opxLx17sg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64-baseline": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64-baseline/-/opencode-linux-x64-baseline-1.18.8.tgz",
"integrity": "sha512-egeEF4tk1rK9flIQjjeSVB9cR/X3zUti0pNAHW6ROJkNkj72z2C2FmjK1hZbfjtteCueMXPLptS23JROHGWL1w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64-baseline-musl": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64-baseline-musl/-/opencode-linux-x64-baseline-musl-1.18.8.tgz",
"integrity": "sha512-S+438BXs48gLeXX/ya4TSNytDy9mliU3sOAf6j9rfFjzGiF/S08LedemSAnHkr0riBtamik1aRPSmTjhQ0dOBg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-linux-x64-musl": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-linux-x64-musl/-/opencode-linux-x64-musl-1.18.8.tgz",
"integrity": "sha512-c+E4Zsp0DYVcuqcDtgxw/4YcFLrVYWdGBR8x4CzpW48ga3RshaH+BlmUiy+GY0yr1x6UR+e2V3w4uzvzm/L9UQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/opencode-windows-arm64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-windows-arm64/-/opencode-windows-arm64-1.18.8.tgz",
"integrity": "sha512-7NjdtEIiX28kmsKD9jHbFG4bbwBB5T4dAe2UwdnOqCBb2cl+ETV5eO6kbdC/xWxrgOghgZM1Wtw791T5pQPyag==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/opencode-windows-x64": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-windows-x64/-/opencode-windows-x64-1.18.8.tgz",
"integrity": "sha512-G+NEgEMvu/dEYshH5IaqHVTmsHVuGdORBvVmgphFiknT7q/NXPuoZCMtMIdfNlEFbu54BlzRDdJCR3Mqe98gUw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/opencode-windows-x64-baseline": {
"version": "1.18.8",
"resolved": "https://registry.npmjs.org/opencode-windows-x64-baseline/-/opencode-windows-x64-baseline-1.18.8.tgz",
"integrity": "sha512-IGbjFyWoSN9rdGUJX7TWkQ1Yl673Q3dDna54b5NtqeRcZ839p+Z47zzM5m883HKAxrdKCC6Z22HYuDcXLV0laA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
]
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
@@ -32174,13 +32023,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/rimraf/node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
@@ -35157,9 +34999,9 @@
"license": "MIT"
},
"node_modules/undici": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz",
"integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==",
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
"integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
@@ -36950,7 +36792,12 @@
},
"open-sse": {
"name": "@omniroute/open-sse",
"version": "3.8.50"
"version": "3.8.49",
"dependencies": {
"@toon-format/toon": "^4.1.0",
"safe-regex": "^2.1.1",
"smol-toml": "1.7.1"
}
}
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "omniroute",
"version": "3.8.50",
"description": "Unified AI router with 290 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"version": "3.8.49",
"description": "Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
"type": "module",
"bin": {
"omniroute": "bin/omniroute.mjs",
@@ -23,7 +23,6 @@
".env.example",
"scripts/build/postinstall.mjs",
"scripts/build/fixTlsClientNodeBinary.mjs",
"scripts/build/fixPlaywrightAndroid.mjs",
"bin/cli/runtime/",
"scripts/postinstall.mjs",
"scripts/build/postinstallSupport.mjs",
@@ -34,15 +33,11 @@
"scripts/dev/tls-options.mjs",
"scripts/check/check-supported-node-runtime.ts",
"scripts/dev/sync-env.mjs",
"scripts/build/assembleStandalone.mjs",
"scripts/build/backendOnlyPages.mjs",
"scripts/build/build-next-isolated.mjs",
"scripts/build/build-tproxy-native.mjs",
"scripts/build/native-binary-compat.mjs",
"scripts/build/build-next-isolated.mjs",
"scripts/build/runtime-env.mjs",
"README.md",
"LICENSE",
"!**/node_modules/**",
"!**/__tests__/**",
"!**/*.test.ts",
"!**/*.test.tsx",
@@ -115,8 +110,6 @@
"test:unit:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:unit:ci:shard": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD \"tests/unit/serial/**/*.test.ts\"",
"test:unit:fast": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:scoped": "bash scripts/quality/test-scoped.sh",
"test:scoped:staged": "bash scripts/quality/test-scoped.sh --staged",
"test:unit:shard": "concurrently --kill-others-on-fail -n s1,s2 \"npm:test:unit:shard:1\" \"npm:test:unit:shard:2\"",
"test:unit:shard:1": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"",
"test:unit:shard:2": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"",
@@ -150,7 +143,6 @@
"check:node-runtime": "node --import tsx scripts/check/check-supported-node-runtime.ts",
"check:pack-artifact": "node --import tsx scripts/build/validate-pack-artifact.ts",
"check:pack-boot": "node scripts/check/check-pack-boot.mjs",
"check:install-upgrade": "node scripts/check/check-install-upgrade.mjs",
"check:pack-policy": "node --import tsx scripts/build/validate-pack-artifact.ts --policy-only",
"check:cli-i18n": "node scripts/check/check-cli-i18n.mjs",
"check:openapi-coverage": "node scripts/check/check-openapi-coverage.mjs",
@@ -169,7 +161,6 @@
"check:test-masking": "node scripts/check/check-test-masking.mjs",
"check:test-runner-api": "node scripts/check/check-test-runner-api.mjs",
"check:changelog-integrity": "node scripts/check/check-changelog-integrity.mjs",
"sweep:stale-fragments": "node scripts/release/sweep-stale-fragments.mjs",
"changelog:aggregate": "node scripts/release/aggregate-changelog.mjs",
"check:agent-skills-sync": "node --import tsx/esm scripts/skills/generate-agent-skills.mjs",
"check:build-scope": "node scripts/check/check-build-scope.mjs",
@@ -193,7 +184,6 @@
"check:bundle-size": "node scripts/check/check-bundle-size.mjs",
"check:circular-deps": "node scripts/check/check-circular-deps.mjs",
"check:mutation-ratchet": "node scripts/check/check-mutation-ratchet.mjs",
"check:rtl-ratchet": "node scripts/check/check-rtl-ratchet.mjs",
"check:licenses": "node scripts/check/check-licenses.mjs",
"check:pr-evidence": "node scripts/check/check-pr-evidence.mjs",
"check:vuln-ratchet": "node scripts/check/check-vuln-ratchet.mjs",
@@ -210,11 +200,9 @@
"typecheck:core": "tsc --pretty false -p tsconfig.typecheck-core.json",
"typecheck:noimplicit:core": "tsc --pretty false -p tsconfig.typecheck-noimplicit-core.json",
"check:dashboard-typecheck": "node scripts/check/check-dashboard-typecheck.mjs",
"check:open-sse-typecheck": "node scripts/check/check-open-sse-typecheck.mjs",
"backfill-aggregation": "node --import tsx src/scripts/backfillAggregation.ts",
"env:sync": "node scripts/dev/sync-env.mjs",
"test:integration": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 tests/integration/*.test.ts \"tests/integration/combo-matrix/*.test.ts\"",
"test:integration:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD tests/integration/*.test.ts \"tests/integration/combo-matrix/*.test.ts\"",
"test:combo:matrix": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/integration/combo-matrix/*.test.ts\"",
"test:combo:live": "cross-env RUN_COMBO_LIVE=1 DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/integration/combo-live/*.live.test.ts\"",
"test:combo:live:vps": "node scripts/test/combo-live-vps.mjs",
@@ -244,7 +232,6 @@
"prepare": "husky",
"system-info": "node scripts/dev/system-info.mjs",
"build:cli-api": "node --import tsx/esm scripts/cli/generate-api-commands.mjs",
"postbuild": "node scripts/build/colocate-standalone.mjs",
"release:contributors": "node scripts/release/gen-contributors.mjs",
"release:uncovered": "node scripts/release/list-uncovered-commits.mjs",
"test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
@@ -269,7 +256,7 @@
"clsx": "^2.1.1",
"commander": "^15.0.0",
"csv-stringify": "^6.7.0",
"dompurify": "^3.4.13",
"dompurify": "^3.4.12",
"express": "^5.2.1",
"fetch-socks": "^1.3.3",
"fflate": "^0.8.3",
@@ -317,7 +304,7 @@
"sqlite-vec": "^0.1.9",
"tailwind-merge": "^3.6.0",
"tsx": "^4.23.0",
"undici": "^8.10.0",
"undici": "^8.3.0",
"update-notifier": "^7.3.1",
"uuid": "^14.0.0",
"ws": "^8.18.0",
@@ -330,7 +317,7 @@
"@atjsh/llmlingua-2": "2.0.3",
"@huggingface/transformers": "3.5.2",
"@tensorflow/tfjs": "4.22.0",
"better-sqlite3": "^13.0.2",
"better-sqlite3": "^13.0.1",
"js-tiktoken": "^1.0.20",
"keytar": "^7.9.0",
"tls-client-node": "^0.2.0",
@@ -376,7 +363,6 @@
"lint-staged": "^17.0.8",
"lockfile-lint": "^5.0.0",
"node-loader": "^2.1.0",
"opencode-ai": "1.18.8",
"playwright-ctrf-json-reporter": "^0.0.29",
"prettier": "^3.8.3",
"promptfoo": "^0.121.18",
@@ -408,16 +394,8 @@
"sharp"
]
},
"allowScripts": {
"better-sqlite3": true,
"esbuild": true,
"@swc/core": true,
"@parcel/watcher": true,
"keytar": true,
"protobufjs": true,
"unrs-resolver": true
},
"overrides": {
"dompurify": "^3.4.12",
"fast-xml-parser": "^5.10.1",
"sharp": "^0.35.0",
"postcss": "^8.5.18",
@@ -447,14 +425,27 @@
"adm-zip": "^0.6.0",
"promptfoo": {
"js-yaml": "^5.2.2",
"@apidevtools/json-schema-ref-parser": {
"js-yaml": "^4.3.1"
},
"undici": "^7.29.0"
},
"socket.io-parser": "^4.2.7",
"tar": "^7.5.21",
"nanoid": "^3.3.17",
"brace-expansion": "^5.0.9",
"minimatch": {
"brace-expansion": "^1.1.18"
},
"libxmljs2": {
"minimatch": {
"brace-expansion": "^2.1.4"
}
},
"rimraf": {
"minimatch": {
"brace-expansion": "^2.1.4"
}
},
"@apidevtools/json-schema-ref-parser": {
"js-yaml": "^4.3.1"
},
"@eslint/eslintrc": {
"js-yaml": "^4.3.1"
},
@@ -463,12 +454,6 @@
},
"xmlbuilder2": {
"js-yaml": "^4.3.1"
},
"monaco-editor": {
"dompurify": "^3.4.13"
},
"@apidevtools/json-schema-ref-parser": {
"js-yaml": "^4.3.1"
}
}
}

View File

@@ -865,12 +865,11 @@ describe("Reasoning Replay Cache — Translator Replay", () => {
}),
},
});
// The non-tool-call cache key is built as `getAssistantMessageCacheKey(result, messageIndex)`
// where messageIndex is the assistant message's real position in the `messages`
// array (index 1 here: user, assistant, user) — matching what the write side
// (chatCore.ts) now caches under once the response is generated.
// NOTE: the non-tool-call cache key is built as `getAssistantMessageCacheKey(result, 0)`
// — the message index is hardcoded to 0 in the translator, so the key is always
// `request:<id>:message:0` regardless of the assistant message's actual position.
cacheReasoning(
"request:req-plain-1:message:1",
"request:req-plain-1:message:0",
"deepseek",
"deepseek-v4-pro",
"Real cached plain-turn reasoning"
@@ -900,60 +899,6 @@ describe("Reasoning Replay Cache — Translator Replay", () => {
);
assert.equal(getReasoningCacheServiceStats().replays, 1);
});
it("write side (chatCore's messageIndex) and read side (translateRequest) agree on the same key end-to-end", () => {
// Regression for a mismatch where chatCore.ts always cached under
// `messageIndex: 0` (the position of the response within *its own* choices
// array) while translateRequest's read side looked up the message's real
// position in the *next* turn's full history — the two never agreed once a
// conversation went past its first assistant turn, so replay silently
// fell back to the placeholder in real multi-turn usage.
clearReasoningCacheAll();
clearModelsDevCapabilities();
saveModelsDevCapabilities({
deepseek: {
"deepseek-v4-pro": buildCapability({
interleaved_field: "reasoning_content",
reasoning: true,
tool_call: true,
}),
},
});
// Turn 1: the incoming request has a single user message (length 1), so
// the assistant response chatCore is about to cache will occupy index 1
// once it's appended to history for turn 2 — mirroring
// `messageIndex: bodyMessages.length` in chatCore.ts.
const turn1RequestBody = { messages: [{ role: "user", content: "hi" }] };
cacheReasoningFromAssistantMessage(
{ role: "assistant", content: "Hello! How can I help?", reasoning_content: "real reasoning" },
"deepseek",
"deepseek-v4-pro",
{ requestId: "req-e2e-1", messageIndex: turn1RequestBody.messages.length }
);
// Turn 2: client replays the full history including the cached assistant
// turn, now genuinely at index 1.
const translated = translateRequest(
FORMATS.OPENAI,
FORMATS.OPENAI,
"deepseek-v4-pro",
{
request_id: "req-e2e-1",
messages: [
{ role: "user", content: "hi" },
{ role: "assistant", content: "Hello! How can I help?" },
{ role: "user", content: "tell me more" },
],
},
false,
null,
"deepseek"
);
assert.equal(translated.messages[1].reasoning_content, "real reasoning");
assert.equal(getReasoningCacheServiceStats().replays, 1);
});
});
describe("Reasoning Replay Cache — API Route", () => {

View File

@@ -632,7 +632,7 @@ test("translateRequest replays cached reasoning-only messages when interleaved f
},
});
cacheReasoningByKey(
"request:req_reasoning_only:message:1",
"request:req_reasoning_only:message:0",
"deepseek",
"deepseek-v4-flash",
"cached reasoning only"
@@ -690,131 +690,138 @@ test("translateRequest does not replay reasoning-only messages for non-DeepSeek
clearReasoningCacheAll();
});
test("translateRequest uses Kimi Coding's empty thinking marker instead of cached replay", () => {
clearReasoningCacheAll();
cacheReasoningByKey(
"toolu_kimi_claude",
"kimi-coding",
"kimi-for-coding",
"cached thinking for Kimi tool call"
);
test("translateRequest uses Kimi Coding's empty thinking marker instead of cached replay", () => {
clearReasoningCacheAll();
cacheReasoningByKey(
"toolu_kimi_claude",
"kimi-coding",
"kimi-for-coding",
"cached thinking for Kimi tool call"
);
// Claude-format request: assistant has tool_use in content[] but NO thinking block
// This simulates the scenario that causes infinite loops
const result = translateRequest(
FORMATS.OPENAI,
FORMATS.CLAUDE,
"kimi-for-coding",
{
reasoning_effort: "high",
messages: [
{ role: "user", content: "read the file" },
{
role: "assistant",
content: [
{
type: "tool_use",
id: "toolu_kimi_claude",
name: "read_file",
input: { path: "test.ts" },
},
],
},
{ role: "tool", tool_call_id: "toolu_kimi_claude", content: "file data" },
],
},
false,
null,
"kimi-coding"
);
// Claude-format request: assistant has tool_use in content[] but NO thinking block
// This simulates the scenario that causes infinite loops
const result = translateRequest(
FORMATS.OPENAI,
FORMATS.CLAUDE,
"kimi-for-coding",
{
reasoning_effort: "high",
messages: [
{ role: "user", content: "read the file" },
{
role: "assistant",
content: [
{
type: "tool_use",
id: "toolu_kimi_claude",
name: "read_file",
input: { path: "test.ts" },
},
],
},
{ role: "tool", tool_call_id: "toolu_kimi_claude", content: "file data" },
],
},
false,
null,
"kimi-coding"
);
const assistantMsg = result.messages.find((m) => m.role === "assistant");
assert.ok(assistantMsg, "assistant message should exist");
assert.ok(Array.isArray(assistantMsg.content), "content should be array");
const assistantMsg = result.messages.find((m) => m.role === "assistant");
assert.ok(assistantMsg, "assistant message should exist");
assert.ok(Array.isArray(assistantMsg.content), "content should be array");
// Kimi Code CLI 0.26 sends an explicit empty thinking marker before tool_use.
const thinkingBlock = assistantMsg.content.find((b) => b?.type === "thinking");
assert.ok(thinkingBlock, "thinking block should be injected");
assert.equal(thinkingBlock.thinking, "");
// Kimi Code CLI 0.26 sends an explicit empty thinking marker before tool_use.
const thinkingBlock = assistantMsg.content.find((b) => b?.type === "thinking");
assert.ok(thinkingBlock, "thinking block should be injected");
assert.equal(thinkingBlock.thinking, "");
// Thinking block should appear before tool_use
const thinkingIdx = assistantMsg.content.indexOf(thinkingBlock);
const toolUseIdx = assistantMsg.content.findIndex((b) => b?.type === "tool_use");
assert.ok(thinkingIdx < toolUseIdx, "thinking block should be before tool_use");
// Thinking block should appear before tool_use
const thinkingIdx = assistantMsg.content.indexOf(thinkingBlock);
const toolUseIdx = assistantMsg.content.findIndex((b) => b?.type === "tool_use");
assert.ok(thinkingIdx < toolUseIdx, "thinking block should be before tool_use");
assert.equal(getReasoningCacheServiceStats().replays, 0);
clearReasoningCacheAll();
});
assert.equal(getReasoningCacheServiceStats().replays, 0);
clearReasoningCacheAll();
});
test("translateRequest uses an empty Kimi Coding thinking marker on cache miss", () => {
clearReasoningCacheAll();
test("translateRequest uses an empty Kimi Coding thinking marker on cache miss", () => {
clearReasoningCacheAll();
const result = translateRequest(
FORMATS.OPENAI,
FORMATS.CLAUDE,
"kimi-for-coding",
{
reasoning_effort: "high",
messages: [
{ role: "user", content: "do it" },
{
role: "assistant",
content: [{ type: "tool_use", id: "toolu_miss", name: "bash", input: { command: "ls" } }],
},
{ role: "tool", tool_call_id: "toolu_miss", content: "output" },
],
},
false,
null,
"kimi-coding"
);
const result = translateRequest(
FORMATS.OPENAI,
FORMATS.CLAUDE,
"kimi-for-coding",
{
reasoning_effort: "high",
messages: [
{ role: "user", content: "do it" },
{
role: "assistant",
content: [
{ type: "tool_use", id: "toolu_miss", name: "bash", input: { command: "ls" } },
],
},
{ role: "tool", tool_call_id: "toolu_miss", content: "output" },
],
},
false,
null,
"kimi-coding"
);
const assistantMsg = result.messages.find((m) => m.role === "assistant");
assert.ok(assistantMsg, "assistant message should exist");
const assistantMsg = result.messages.find((m) => m.role === "assistant");
assert.ok(assistantMsg, "assistant message should exist");
const thinkingBlock =
Array.isArray(assistantMsg.content) && assistantMsg.content.find((b) => b?.type === "thinking");
assert.ok(thinkingBlock, "thinking block should be injected on cache miss");
assert.equal(thinkingBlock.thinking, "");
const thinkingBlock =
Array.isArray(assistantMsg.content) &&
assistantMsg.content.find((b) => b?.type === "thinking");
assert.ok(thinkingBlock, "thinking block should be injected on cache miss");
assert.equal(thinkingBlock.thinking, "");
clearReasoningCacheAll();
});
clearReasoningCacheAll();
});
test("translateRequest does NOT inject duplicate thinking for Claude-format messages with existing thinking block", () => {
clearReasoningCacheAll();
test("translateRequest does NOT inject duplicate thinking for Claude-format messages with existing thinking block", () => {
clearReasoningCacheAll();
const result = translateRequest(
FORMATS.OPENAI,
FORMATS.CLAUDE,
"kimi-for-coding",
{
messages: [
{ role: "user", content: "hi" },
{
role: "assistant",
content: [
{ type: "thinking", thinking: "I already have this" },
{ type: "tool_use", id: "toolu_existing", name: "read", input: {} },
],
},
{ role: "tool", tool_call_id: "toolu_existing", content: "data" },
],
},
false,
null,
"kimi-coding"
);
const result = translateRequest(
FORMATS.OPENAI,
FORMATS.CLAUDE,
"kimi-for-coding",
{
messages: [
{ role: "user", content: "hi" },
{
role: "assistant",
content: [
{ type: "thinking", thinking: "I already have this" },
{ type: "tool_use", id: "toolu_existing", name: "read", input: {} },
],
},
{ role: "tool", tool_call_id: "toolu_existing", content: "data" },
],
},
false,
null,
"kimi-coding"
);
const assistantMsg = result.messages.find((m) => m.role === "assistant");
const thinkingBlocks =
Array.isArray(assistantMsg.content) &&
assistantMsg.content.filter((b) => b?.type === "thinking");
assert.equal(thinkingBlocks?.length, 1, "should have exactly one thinking block (no duplicate)");
assert.equal(
thinkingBlocks[0].thinking,
"I already have this",
"original thinking should be preserved"
);
const assistantMsg = result.messages.find((m) => m.role === "assistant");
const thinkingBlocks =
Array.isArray(assistantMsg.content) &&
assistantMsg.content.filter((b) => b?.type === "thinking");
assert.equal(
thinkingBlocks?.length,
1,
"should have exactly one thinking block (no duplicate)"
);
assert.equal(
thinkingBlocks[0].thinking,
"I already have this",
"original thinking should be preserved"
);
clearReasoningCacheAll();
});
clearReasoningCacheAll();
});