clauderipple 0.8.7 → 0.8.8
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/CHANGELOG.md +10 -0
- package/dist/app/package.json +1 -1
- package/dist/router/src/admin.js +6 -1
- package/dist/router/src/config.js +22 -0
- package/dist/router/src/presets.js +7 -0
- package/dist/router/src/proxy.js +11 -6
- package/dist/router/src/version.js +1 -1
- package/dist/router/src/websearch.js +2 -1
- package/dist/ui/app.js +8 -4
- package/docs/ARCHITECTURE.md +10 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.8 — 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Web search works with OpenCode Go's DeepSeek on the small/fast slot.** With that slot on
|
|
8
|
+
DeepSeek V4.1 Flash, V4 Flash or V4 Pro through OpenCode Go, every `WebSearch` was refused
|
|
9
|
+
and the Clients screen marked those models "no web search here". The same plan runs the search on
|
|
10
|
+
its Anthropic endpoint, so the router now sends searches for these models there. Their other
|
|
11
|
+
requests are unchanged.
|
|
12
|
+
|
|
3
13
|
## 0.8.7 — 2026-10-07
|
|
4
14
|
|
|
5
15
|
### Fixed
|
package/dist/app/package.json
CHANGED
package/dist/router/src/admin.js
CHANGED
|
@@ -23,7 +23,7 @@ import path from "node:path";
|
|
|
23
23
|
import http from "node:http";
|
|
24
24
|
import { execFile, spawn } from "node:child_process";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
|
-
import { homeDir, validate } from "./config.js";
|
|
26
|
+
import { homeDir, searchProviderFor, validate } from "./config.js";
|
|
27
27
|
import { PRESETS } from "./presets.js";
|
|
28
28
|
import { resolveCompatibleCaps } from "./compat.js";
|
|
29
29
|
import { CHATGPT_FALLBACK_MODELS } from "./providers/chatgpt/catalog.js";
|
|
@@ -318,6 +318,11 @@ async function buildStatus(deps, opts = {}) {
|
|
|
318
318
|
// events — a backend the router does not have, so a search still cannot go through it.
|
|
319
319
|
// Naming the provider's own capability instead would be a promise the router cannot keep.
|
|
320
320
|
...(providerCanSearch(name, cfg) ? { webSearch: true } : {}),
|
|
321
|
+
// A provider whose plan searches on another endpoint does so for some models only.
|
|
322
|
+
...(() => {
|
|
323
|
+
const models = (("models" in p && p.models) || []).map((m) => typeof m === "string" ? m : m.id).filter((id) => searchProviderFor(p, id));
|
|
324
|
+
return models.length ? { webSearchModels: models } : {};
|
|
325
|
+
})(),
|
|
321
326
|
// Reaching the host says nothing about being able to use it: a chatgpt provider with no
|
|
322
327
|
// credentials is not "connected", and calling it that sends the user off believing it works.
|
|
323
328
|
...(p.type === "chatgpt" ? { needsLogin: !chatgptSignedIn(p.auth) } : {}),
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
|
+
import { PRESETS } from "./presets.js";
|
|
9
10
|
/**
|
|
10
11
|
* The provider's own key, re-sent under the header another wire on the same account expects.
|
|
11
12
|
*
|
|
@@ -76,6 +77,27 @@ export function providerFor(provider, model) {
|
|
|
76
77
|
}
|
|
77
78
|
return { ...shared, type: "openai-compatible", url: url ?? "", ...(entry.wire ? { wire: entry.wire } : {}) };
|
|
78
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The Anthropic endpoint a web search for `model` runs on, when the provider's preset measured that
|
|
82
|
+
* its plan searches there although the model is spoken to on another wire (`anthropicSearch`).
|
|
83
|
+
* OpenCode Go's DeepSeek models answer on Chat Completions, which has no search, while the same key
|
|
84
|
+
* on the plan's Anthropic endpoint runs the server tool (2026-10-07).
|
|
85
|
+
*/
|
|
86
|
+
export function searchProviderFor(provider, model) {
|
|
87
|
+
const presetId = provider && "preset" in provider ? provider.preset : undefined;
|
|
88
|
+
const search = presetId ? PRESETS.find((entry) => entry.id === presetId)?.anthropicSearch : undefined;
|
|
89
|
+
if (!provider || !search || !search.models.includes(model))
|
|
90
|
+
return undefined;
|
|
91
|
+
const headers = reauthorized("headers" in provider ? provider.headers : undefined, search.authHeader);
|
|
92
|
+
const sessionHeader = "sessionHeader" in provider ? provider.sessionHeader : undefined;
|
|
93
|
+
return {
|
|
94
|
+
type: "anthropic-compatible",
|
|
95
|
+
url: search.url,
|
|
96
|
+
...(headers ? { headers } : {}),
|
|
97
|
+
...(sessionHeader ? { sessionHeader } : {}),
|
|
98
|
+
...(presetId ? { preset: presetId } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
79
101
|
export const PICKER_HOSTS_DEFAULT = ["claude.ai"];
|
|
80
102
|
export function terminateHosts(c) {
|
|
81
103
|
const hosts = [c.upstream];
|
|
@@ -260,6 +260,13 @@ export const PRESETS = [
|
|
|
260
260
|
// to the Responses models above; the Chat and Anthropic ones carry their own empty ladder.
|
|
261
261
|
effortLevels: ["none", "minimal", "low", "medium", "high", "xhigh"],
|
|
262
262
|
thinking: "none",
|
|
263
|
+
// The DeepSeek models are served on Chat Completions, which ignores every search option, but
|
|
264
|
+
// the same key on the Anthropic endpoint runs `web_search_20250305` for them: Claude Code's own
|
|
265
|
+
// side request came back with a `web_search_tool_result` of ten real pages and
|
|
266
|
+
// `web_search_requests: 1` (measured 2026-10-07; a bearer header there answers "Missing API
|
|
267
|
+
// key"). Kimi K3, MiniMax M3 and Qwen3.8 Flash take the tool on that endpoint and never search;
|
|
268
|
+
// GLM and MiMo are not served there at all.
|
|
269
|
+
anthropicSearch: { url: "https://opencode.ai/zen/go", authHeader: "x-api-key", models: ["deepseek-v4.1-flash", "deepseek-v4-flash", "deepseek-v4-pro"] },
|
|
263
270
|
verified: true,
|
|
264
271
|
notes: "One subscription and key, three endpoints by wire. Measured 2026-09-18. Responses: Muse Spark answers, effort ladder tops out at xhigh, prompt cache reached 96% on a repeated turn. Chat: glm-5.3, glm-5.3-flash, kimi-k3, kimi-k2.7-code, deepseek-v4.1-flash, deepseek-v4-pro, longcat-2.0 and mimo-v2.5-pro all answered. Anthropic: minimax-m3, qwen3.8-max and qwen3.8-flash answered with the prompt cache at 99% on a repeated turn, while union-alpha, the free row, answered \"Model is unavailable\". The Muse Spark Contributor tier is ~90% cheaper because Meta states those interactions improve its products, and OpenCode refuses the model until the workspace opts in (403 DataPolicyError); it is also limited to some regions.",
|
|
265
272
|
docsUrl: "https://opencode.ai/docs/go/",
|
package/dist/router/src/proxy.js
CHANGED
|
@@ -31,7 +31,7 @@ import { ChatGptAdapter } from "./providers/chatgpt/index.js";
|
|
|
31
31
|
import { OpenAiCompatibleAdapter } from "./providers/openai/index.js";
|
|
32
32
|
import { GoogleAdapter } from "./providers/google/index.js";
|
|
33
33
|
import { conversationKey } from "./providers/chatgpt/translate.js";
|
|
34
|
-
import { providerFor, terminateHosts } from "./config.js";
|
|
34
|
+
import { providerFor, searchProviderFor, terminateHosts } from "./config.js";
|
|
35
35
|
import { injectPickerModels, isBootstrapPath } from "./picker.js";
|
|
36
36
|
import { effortLevelsForModel } from "./admin.js";
|
|
37
37
|
import { ResponseUsageTap } from "./requestlog.js";
|
|
@@ -724,7 +724,9 @@ export class Proxy {
|
|
|
724
724
|
// search of a `smallFast` slot pointed at Luna while the Clients screen called that provider
|
|
725
725
|
// searchable (2026-10-07: 41 searches failed with nothing on screen to say why). A configured
|
|
726
726
|
// backend on this same provider has already had its try above.
|
|
727
|
-
|
|
727
|
+
// The same holds for a plan that searches on another of its endpoints (`anthropicSearch`).
|
|
728
|
+
const own = (cfg.providers[route.provider]?.type === "chatgpt" || searchProviderFor(cfg.providers[route.provider], route.model) !== undefined) &&
|
|
729
|
+
cfg.webSearch?.provider !== route.provider;
|
|
728
730
|
if (own && (await this.serveWebSearch(res, json, search, { provider: route.provider, model: route.model }, record, finish)))
|
|
729
731
|
return;
|
|
730
732
|
const blocks = webSearchErrorBlocks(search, "unavailable");
|
|
@@ -1358,8 +1360,9 @@ export class Proxy {
|
|
|
1358
1360
|
return false;
|
|
1359
1361
|
}
|
|
1360
1362
|
// Which backend is chosen below depends on the wire the search model speaks, which may not be
|
|
1361
|
-
// the provider's own.
|
|
1362
|
-
const
|
|
1363
|
+
// the provider's own — or on the endpoint its plan searches on, which may not be the model's.
|
|
1364
|
+
const searchVia = searchProviderFor(configured, settings.model);
|
|
1365
|
+
const provider = searchVia ?? providerFor(configured, settings.model);
|
|
1363
1366
|
const url = "url" in provider && typeof provider.url === "string" ? provider.url : undefined;
|
|
1364
1367
|
if (!url && provider.type !== "chatgpt") {
|
|
1365
1368
|
this.deps.log.warn(`web search: provider ${settings.provider} has no url; leaving the request alone`);
|
|
@@ -1368,10 +1371,12 @@ export class Proxy {
|
|
|
1368
1371
|
// Which backend depends on how the provider is spoken to, not on the vendor. An
|
|
1369
1372
|
// anthropic-compatible one is asked in Anthropic's own shape and hands back the blocks the CLI
|
|
1370
1373
|
// already parses; an openai-compatible one is asked through its chat web plugin.
|
|
1374
|
+
// OpenCode Go refuses a request without its session header outright (400 MissingSessionID).
|
|
1375
|
+
const sessionHeader = "sessionHeader" in provider ? provider.sessionHeader : undefined;
|
|
1371
1376
|
const common = {
|
|
1372
1377
|
name: settings.provider,
|
|
1373
1378
|
url: url ?? "",
|
|
1374
|
-
headers: ("headers" in provider && provider.headers) || {},
|
|
1379
|
+
headers: { ...(("headers" in provider && provider.headers) || {}), ...(sessionHeader ? { [sessionHeader]: crypto.randomUUID() } : {}) },
|
|
1375
1380
|
model: settings.model,
|
|
1376
1381
|
...(settings.maxResults ? { maxResults: settings.maxResults } : {}),
|
|
1377
1382
|
};
|
|
@@ -1380,7 +1385,7 @@ export class Proxy {
|
|
|
1380
1385
|
// Refuse before sending rather than after. A provider that cannot run the server tool is sent
|
|
1381
1386
|
// "perform a web search" with no tool attached, and a model told to search with nothing to
|
|
1382
1387
|
// search with narrates a tool call instead — an answer shaped like success, holding nothing.
|
|
1383
|
-
if (!this.canRunServerTools(settings.provider, this.deps.config())) {
|
|
1388
|
+
if (!searchVia && !this.canRunServerTools(settings.provider, this.deps.config())) {
|
|
1384
1389
|
this.deps.log.warn(`web search: provider ${settings.provider} does not run server tools; leaving the request alone`);
|
|
1385
1390
|
return false;
|
|
1386
1391
|
}
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
// from the manifests. Until 0.1.1 the router and the CLI each carried their own literal, and both
|
|
6
6
|
// still said "0.1.0" in the 0.1.1 release: after an update nobody could tell which router was
|
|
7
7
|
// running, and the tray app had nothing to compare (2026-09-15, reported from a Windows install).
|
|
8
|
-
export const VERSION = "0.8.
|
|
8
|
+
export const VERSION = "0.8.8";
|
|
@@ -296,7 +296,8 @@ export function anthropicServerToolBackend(opts) {
|
|
|
296
296
|
// No result blocks means the provider did not run the tool, whatever else it said. A model
|
|
297
297
|
// told to search with no tool it can reach will narrate one instead — an OpenCode Go DeepSeek
|
|
298
298
|
// answered this exact request with its own tool-call markup as plain text and zero searches
|
|
299
|
-
// (measured 2026-09-18
|
|
299
|
+
// (measured 2026-09-18; that same model on the plan's Anthropic endpoint, with its x-api-key
|
|
300
|
+
// header, does search — 2026-10-07). Treat that as the failure it is.
|
|
300
301
|
const deduped = dedupeHits(hits).slice(0, opts.maxResults ?? 10);
|
|
301
302
|
if (deduped.length === 0)
|
|
302
303
|
throw new Error(`${opts.name} web search: no results returned`);
|
package/dist/ui/app.js
CHANGED
|
@@ -519,8 +519,12 @@ const MODEL_SLOTS = ["smallFast", "subagent", "main"];
|
|
|
519
519
|
// A provider the router measured as unable to search. Only the router can know this — it comes from
|
|
520
520
|
// the status snapshot, never from the model's name — and the `smallFast` slot is exactly where it
|
|
521
521
|
// matters, because a search sent to such a model comes back as invented prose or a visible failure.
|
|
522
|
-
|
|
523
|
-
|
|
522
|
+
// A plan that searches on another of its endpoints does so for the models it names only.
|
|
523
|
+
function providerCannotSearch(name, model) {
|
|
524
|
+
const entry = status && status.providers && status.providers[name];
|
|
525
|
+
if (!entry || entry.webSearch === true) return false;
|
|
526
|
+
const id = String(model || "").replace(/@[^@]*$/, "");
|
|
527
|
+
return !(Array.isArray(entry.webSearchModels) && entry.webSearchModels.includes(id));
|
|
524
528
|
}
|
|
525
529
|
|
|
526
530
|
function renderModelSlots() {
|
|
@@ -535,7 +539,7 @@ function renderModelSlots() {
|
|
|
535
539
|
if (chosen[slot] === model.id) option.selected = true;
|
|
536
540
|
// `smallFast` is what a WebSearch runs on, so an inability to search is fatal there and
|
|
537
541
|
// merely worth knowing everywhere else.
|
|
538
|
-
if (slot === "smallFast" && providerCannotSearch(group.name)) {
|
|
542
|
+
if (slot === "smallFast" && providerCannotSearch(group.name, model.id)) {
|
|
539
543
|
option.textContent += ` — ${t("slots.noWebSearch")}`;
|
|
540
544
|
}
|
|
541
545
|
select.appendChild(option);
|
|
@@ -572,7 +576,7 @@ async function saveModelSlots() {
|
|
|
572
576
|
// screen was already guilty of once. The label on the option warns before the fact; this catches
|
|
573
577
|
// a session that already had it selected.
|
|
574
578
|
const owners = models.smallFast ? providersOffering(next, models.smallFast) : [];
|
|
575
|
-
if (owners.length && providerCannotSearch(owners[0])) toast(t("slots.noWebSearchWarn"), true);
|
|
579
|
+
if (owners.length && providerCannotSearch(owners[0], models.smallFast)) toast(t("slots.noWebSearchWarn"), true);
|
|
576
580
|
else toast(t("slots.slotSaved"));
|
|
577
581
|
} catch (error) { toast(t("common.saveFailed"), true, error.message); }
|
|
578
582
|
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -506,7 +506,16 @@ request went, and a `Pane` holds the request log.
|
|
|
506
506
|
runs OpenAI's hosted `web_search` and cites real pages, but its wire is OpenAI-shaped while
|
|
507
507
|
its hits arrive as Responses events (`web_search_call`, `url_citation`), a backend the router
|
|
508
508
|
does not have. DeepSeek's own endpoint and the ChatGPT provider run Anthropic's server tool;
|
|
509
|
-
OpenCode Go's chat
|
|
509
|
+
OpenCode Go's chat endpoint, and every `openai-compatible` provider, do not.
|
|
510
|
+
- **OpenCode Go's Anthropic endpoint does, for DeepSeek only** (corrected 2026-10-07). The
|
|
511
|
+
line above once said it did not. Re-measured with the key in `x-api-key` (a bearer there
|
|
512
|
+
answers "Missing API key"), Claude Code's own side request to `/zen/go/v1/messages` came back
|
|
513
|
+
with ten real hits and `web_search_requests: 1` for deepseek-v4.1-flash, deepseek-v4-flash
|
|
514
|
+
and deepseek-v4-pro; Kimi K3, MiniMax M3 and Qwen3.8 Flash took the tool and never searched;
|
|
515
|
+
GLM and MiMo are not served there. The preset lists those three as `anthropicSearch`, and a
|
|
516
|
+
search routed to one of them (a `smallFast` slot on OpenCode Go's DeepSeek) is relayed there
|
|
517
|
+
by `anthropicServerToolBackend`, though the model's turns stay on Chat Completions. The
|
|
518
|
+
Clients screen reads `webSearchModels` from `/api/status` to stop calling them unsearchable.
|
|
510
519
|
- **A model that cannot search does not merely decline — it invents.** Measured 2026-09-21
|
|
511
520
|
against OpenCode Go's chat endpoint: `plugins: [{id: "web"}]` is accepted and ignored (HTTP
|
|
512
521
|
200, no `annotations`, cache reporting no lookup), and the model answers a search-shaped
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clauderipple",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"description": "Use GPT (any ChatGPT plan with Codex), DeepSeek and 400+ other models in the Claude Desktop Code tab and Claude Code while staying signed in to your Claude subscription — no third-party gateway mode. Several ChatGPT accounts that take over when one runs out, in Codex too.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"workspaces": [
|