deepseek-web-retrieval 0.7.0 → 0.8.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/CHANGELOG.md +11 -0
- package/README.md +92 -101
- package/dist/cli.js +255 -18
- package/docs/agents/claude-code.md +273 -0
- package/docs/agents/codex.md +2 -0
- package/docs/agents/opencode-v2.md +2 -0
- package/docs/agents/pi.md +2 -0
- package/docs/configuration.md +109 -0
- package/docs/design/installer-design.md +16 -13
- package/docs/hosts.jsonc +5 -1
- package/docs/integration.md +9 -6
- package/docs/research/control-matrix.md +34 -9
- package/docs/research/mcp-compatibility.md +2 -2
- package/package.json +1 -1
- package/src/core/env.ts +9 -6
- package/src/installer/hosts/claude-code.ts +325 -0
- package/src/installer/hosts/codex.ts +4 -1
- package/src/installer/hosts/index.ts +3 -0
- package/src/installer/hosts/pi.ts +24 -3
- package/src/installer/state.ts +1 -1
- package/src/installer/types.ts +1 -1
- package/src/installer/uninstall.ts +4 -1
package/dist/cli.js
CHANGED
|
@@ -3619,7 +3619,7 @@ var init_state = __esm(() => {
|
|
|
3619
3619
|
recordSchema = z.object({
|
|
3620
3620
|
version: z.literal(1),
|
|
3621
3621
|
installerVersion: z.string().optional(),
|
|
3622
|
-
host: z.enum(["opencode", "pi", "codex"]),
|
|
3622
|
+
host: z.enum(["opencode", "pi", "codex", "claude-code"]),
|
|
3623
3623
|
scope: z.enum(["global", "project"]),
|
|
3624
3624
|
tiers: z.object({ search: tier, fetch: tier }),
|
|
3625
3625
|
createdAt: z.string().refine((value) => Number.isFinite(Date.parse(value))),
|
|
@@ -4656,7 +4656,7 @@ function isFalseValue(value) {
|
|
|
4656
4656
|
const trimmed = value?.trim().toLowerCase();
|
|
4657
4657
|
return trimmed === "false" || trimmed === "0" || trimmed === "no" || trimmed === "off";
|
|
4658
4658
|
}
|
|
4659
|
-
var NUMBER_ENV, TEXT_ENV, DOMAINS_ENV, FALSE_ONLY_ENV, OPTION_ENV_VARS;
|
|
4659
|
+
var NUMBER_ENV, TEXT_ENV, DOMAINS_ENV, FALSE_ONLY_ENV, OPTION_ENV_BY_KEY, OPTION_ENV_VARS;
|
|
4660
4660
|
var init_env = __esm(() => {
|
|
4661
4661
|
init_constants();
|
|
4662
4662
|
init_domains();
|
|
@@ -4689,12 +4689,13 @@ var init_env = __esm(() => {
|
|
|
4689
4689
|
searchEnabled: ENABLE_SEARCH_ENV,
|
|
4690
4690
|
fetchEnabled: ENABLE_FETCH_ENV
|
|
4691
4691
|
};
|
|
4692
|
-
|
|
4693
|
-
...
|
|
4694
|
-
...
|
|
4695
|
-
...
|
|
4696
|
-
...
|
|
4697
|
-
|
|
4692
|
+
OPTION_ENV_BY_KEY = {
|
|
4693
|
+
...NUMBER_ENV,
|
|
4694
|
+
...TEXT_ENV,
|
|
4695
|
+
...DOMAINS_ENV,
|
|
4696
|
+
...FALSE_ONLY_ENV
|
|
4697
|
+
};
|
|
4698
|
+
OPTION_ENV_VARS = Object.values(OPTION_ENV_BY_KEY);
|
|
4698
4699
|
});
|
|
4699
4700
|
|
|
4700
4701
|
// src/installer/config-command.ts
|
|
@@ -4870,10 +4871,227 @@ var init_shared = __esm(() => {
|
|
|
4870
4871
|
init_formats();
|
|
4871
4872
|
});
|
|
4872
4873
|
|
|
4873
|
-
// src/installer/hosts/
|
|
4874
|
+
// src/installer/hosts/claude-code.ts
|
|
4874
4875
|
import { spawnSync } from "node:child_process";
|
|
4876
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
4877
|
+
function valueKind(value) {
|
|
4878
|
+
return Array.isArray(value) ? "an array" : `a ${typeof value}`;
|
|
4879
|
+
}
|
|
4880
|
+
function probeClaudeCodeVersion(err) {
|
|
4881
|
+
const result = spawnSync("claude", ["--version"], { timeout: 5000, encoding: "utf8", env: process.env, shell: true });
|
|
4882
|
+
const version = result.status === 0 ? parseClaudeCodeVersion(result.stdout ?? "") : undefined;
|
|
4883
|
+
if (version === undefined) {
|
|
4884
|
+
err("note: claude was not found on PATH or printed no version; the version-drift check was skipped");
|
|
4885
|
+
}
|
|
4886
|
+
return version;
|
|
4887
|
+
}
|
|
4888
|
+
function parseClaudeCodeVersion(output) {
|
|
4889
|
+
return [...output.matchAll(/\b\d+\.\d+\.\d+\b/g)].map((match) => match[0]).at(-1);
|
|
4890
|
+
}
|
|
4891
|
+
function claudeCodePaths(input) {
|
|
4892
|
+
return input.scope === "global" ? {
|
|
4893
|
+
config: join2(input.home, ".claude.json"),
|
|
4894
|
+
settings: join2(input.home, ".claude", "settings.json"),
|
|
4895
|
+
rules: join2(input.home, ".claude", "CLAUDE.md")
|
|
4896
|
+
} : {
|
|
4897
|
+
config: join2(input.cwd, ".mcp.json"),
|
|
4898
|
+
settings: join2(input.cwd, ".claude", "settings.json"),
|
|
4899
|
+
rules: join2(input.cwd, "CLAUDE.md")
|
|
4900
|
+
};
|
|
4901
|
+
}
|
|
4902
|
+
function managedSettingsPath(input) {
|
|
4903
|
+
const platform = input.platform ?? process.platform;
|
|
4904
|
+
if (platform === "darwin")
|
|
4905
|
+
return "/Library/Application Support/ClaudeCode/managed-settings.json";
|
|
4906
|
+
if (platform === "win32")
|
|
4907
|
+
return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
|
|
4908
|
+
return "/etc/claude-code/managed-settings.json";
|
|
4909
|
+
}
|
|
4910
|
+
function planClaudeCode(input, _previous) {
|
|
4911
|
+
const paths = claudeCodePaths(input);
|
|
4912
|
+
const loaded = codecFor("json", input.host).load(paths.config);
|
|
4913
|
+
const tiers = { search: input.search, fetch: input.fetch };
|
|
4914
|
+
const header = planHeader(input, paths, loaded, "JSON");
|
|
4915
|
+
if (header.abort) {
|
|
4916
|
+
return { host: "claude-code", scope: input.scope, tiers, edits: [], notes: header.notes, errors: header.errors };
|
|
4917
|
+
}
|
|
4918
|
+
const notes = [...header.notes];
|
|
4919
|
+
const errors = [...header.errors];
|
|
4920
|
+
const edits = [];
|
|
4921
|
+
if (input.hostVersion !== undefined && input.hostVersion !== CLAUDE_CODE_TESTED_VERSION) {
|
|
4922
|
+
notes.push(`Claude Code ${input.hostVersion} is installed, but this installer's Claude Code facts are verified against ` + `${CLAUDE_CODE_TESTED_VERSION} (the docs/hosts.jsonc lock). Deny-rule and tool-visibility behavior may have ` + "drifted between the two — re-verify the tiers if routing misbehaves.");
|
|
4923
|
+
}
|
|
4924
|
+
const managed = managedSettingsPath(input);
|
|
4925
|
+
if (existsSync9(managed)) {
|
|
4926
|
+
notes.push(`${managed} exists: the managed policy layer outranks every settings file this plan writes, so a deny or allow ` + "there overrides these changes. Check with your administrator if routing does not take effect.");
|
|
4927
|
+
}
|
|
4928
|
+
const servers = loaded.value.mcpServers;
|
|
4929
|
+
if (servers !== undefined && !isPlainObject(servers)) {
|
|
4930
|
+
errors.push({
|
|
4931
|
+
message: `${paths.config}: mcpServers is ${valueKind(servers)}, not an object; fix it before installing.`
|
|
4932
|
+
});
|
|
4933
|
+
}
|
|
4934
|
+
const existingEntry = isPlainObject(servers) ? servers.deepseek : undefined;
|
|
4935
|
+
const entry = {
|
|
4936
|
+
command: "npx",
|
|
4937
|
+
args: ["-y", input.packageRef],
|
|
4938
|
+
env: { [API_KEY_ENV]: `\${${API_KEY_ENV}}` }
|
|
4939
|
+
};
|
|
4940
|
+
const ops = [{ op: "set", path: ["mcpServers", "deepseek"], value: entry, previous: existingEntry }];
|
|
4941
|
+
edits.push({
|
|
4942
|
+
file: paths.config,
|
|
4943
|
+
format: "json",
|
|
4944
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
4945
|
+
ops
|
|
4946
|
+
});
|
|
4947
|
+
if (isPlainObject(servers)) {
|
|
4948
|
+
if ("deepseek" in servers)
|
|
4949
|
+
notes.push(replacingExistingEntryNote("mcpServers.deepseek"));
|
|
4950
|
+
}
|
|
4951
|
+
notes.push(`\`${API_KEY_ENV}\` is written as "\${${API_KEY_ENV}}" in the server's env block, so the key expands from the ` + "environment that launches Claude Code and stays out of the file.");
|
|
4952
|
+
if (input.scope === "project") {
|
|
4953
|
+
notes.push("Claude Code asks once per project whether to use servers from .mcp.json; approve the deepseek server when " + "prompted (or via the /mcp menu), or its tools will not load in that project.");
|
|
4954
|
+
} else {
|
|
4955
|
+
notes.push(`User-scope servers live in the mcpServers key of ${paths.config} — Claude Code's own user-level store, where ` + "`claude mcp add --scope user` writes too; every other key in the file is left untouched.");
|
|
4956
|
+
}
|
|
4957
|
+
const forced = input.search === "forced" || input.fetch === "forced";
|
|
4958
|
+
const scanned = {};
|
|
4959
|
+
if (isPlainObject(servers))
|
|
4960
|
+
Object.assign(scanned, servers);
|
|
4961
|
+
const otherFile = input.scope === "project" ? join2(input.home, ".claude.json") : join2(input.cwd, ".mcp.json");
|
|
4962
|
+
if (forced && existsSync9(otherFile)) {
|
|
4963
|
+
let otherLoaded;
|
|
4964
|
+
try {
|
|
4965
|
+
otherLoaded = codecFor("json", input.host).load(otherFile);
|
|
4966
|
+
} catch (error) {
|
|
4967
|
+
errors.push({
|
|
4968
|
+
message: `${otherFile} exists but cannot be read (${error instanceof Error ? error.message : String(error)}); the forced-tier competitor scan requires it. Fix the file or its permissions, or choose a lower tier.`
|
|
4969
|
+
});
|
|
4970
|
+
}
|
|
4971
|
+
if (otherLoaded?.parseError) {
|
|
4972
|
+
errors.push({ message: `${otherFile} exists but does not parse as JSON: ${otherLoaded.parseError}` });
|
|
4973
|
+
} else if (otherLoaded !== undefined && otherLoaded.value.mcpServers !== undefined && !isPlainObject(otherLoaded.value.mcpServers)) {
|
|
4974
|
+
errors.push({
|
|
4975
|
+
message: `${otherFile}: mcpServers is ${valueKind(otherLoaded.value.mcpServers)}, not an object; the forced-tier competitor scan cannot read it. Fix the file or choose a lower tier.`
|
|
4976
|
+
});
|
|
4977
|
+
} else if (otherLoaded !== undefined && isPlainObject(otherLoaded.value.mcpServers)) {
|
|
4978
|
+
const otherServers = otherLoaded.value.mcpServers;
|
|
4979
|
+
Object.assign(scanned, otherServers);
|
|
4980
|
+
if ("deepseek" in otherServers) {
|
|
4981
|
+
notes.push(`A deepseek server is also registered in ${otherFile}; Claude Code resolves the name local first, then ` + "project, then user scope, so consider uninstalling the other scope to keep one registration.");
|
|
4982
|
+
}
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
const denyEntries = [];
|
|
4986
|
+
const { competitors, bystanders } = splitCompetitors(scanned);
|
|
4987
|
+
if (forced && competitors.length > 0) {
|
|
4988
|
+
const rules = competitors.map((name) => `mcp__${name}`);
|
|
4989
|
+
denyEntries.push(...rules);
|
|
4990
|
+
notes.push(`Forced tier: web-capable MCP servers are denied by server-level rules (${rules.join(", ")}), so DeepSeek ` + "routing is exclusive while the entries stay." + (input.scope === "global" ? " These denies apply in every project on this machine and match by server name, so a same-named server in another project is denied too." : ""));
|
|
4991
|
+
}
|
|
4992
|
+
const stayRegistered = forced ? bystanders : [...competitors, ...bystanders];
|
|
4993
|
+
if (stayRegistered.length > 0)
|
|
4994
|
+
notes.push(othersStayRegisteredNote(stayRegistered));
|
|
4995
|
+
if (input.search === "forced")
|
|
4996
|
+
denyEntries.push(BUILTIN_TOOL_IDS.search);
|
|
4997
|
+
if (input.fetch === "forced")
|
|
4998
|
+
denyEntries.push(BUILTIN_TOOL_IDS.fetch);
|
|
4999
|
+
if (input.search === "off")
|
|
5000
|
+
denyEntries.push(MCP_TOOL_IDS.search);
|
|
5001
|
+
if (input.fetch === "off")
|
|
5002
|
+
denyEntries.push(MCP_TOOL_IDS.fetch);
|
|
5003
|
+
if (denyEntries.length > 0) {
|
|
5004
|
+
const settingsLoaded = codecFor("json", input.host).load(paths.settings);
|
|
5005
|
+
if (settingsLoaded.parseError) {
|
|
5006
|
+
errors.push({ message: `${paths.settings} exists but does not parse as JSON: ${settingsLoaded.parseError}` });
|
|
5007
|
+
} else {
|
|
5008
|
+
const permissions = settingsLoaded.value.permissions;
|
|
5009
|
+
const deny = isPlainObject(permissions) ? permissions.deny : undefined;
|
|
5010
|
+
if (permissions !== undefined && !isPlainObject(permissions)) {
|
|
5011
|
+
errors.push({
|
|
5012
|
+
message: `${paths.settings}: the permissions key is ${valueKind(permissions)}, not an object; fix it before installing.`
|
|
5013
|
+
});
|
|
5014
|
+
} else if (deny !== undefined && !Array.isArray(deny)) {
|
|
5015
|
+
errors.push({
|
|
5016
|
+
message: `${paths.settings}: permissions.deny is ${valueKind(deny)}, not an array; fix it before installing.`
|
|
5017
|
+
});
|
|
5018
|
+
} else {
|
|
5019
|
+
const existingCount = Array.isArray(deny) ? deny.length : 0;
|
|
5020
|
+
const existingDeny = Array.isArray(deny) ? deny.filter((item) => typeof item === "string") : [];
|
|
5021
|
+
const denyOps = [
|
|
5022
|
+
{
|
|
5023
|
+
op: "append-array",
|
|
5024
|
+
path: ["permissions", "deny"],
|
|
5025
|
+
entries: denyEntries,
|
|
5026
|
+
existingCount
|
|
5027
|
+
}
|
|
5028
|
+
];
|
|
5029
|
+
edits.push({
|
|
5030
|
+
file: paths.settings,
|
|
5031
|
+
format: "json",
|
|
5032
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5033
|
+
ops: denyOps
|
|
5034
|
+
});
|
|
5035
|
+
if (existingCount > 0) {
|
|
5036
|
+
notes.push(`permissions.deny already holds ${existingCount} ${existingCount === 1 ? "entry" : "entries"}; ` + "the plan appends only its own and never removes yours.");
|
|
5037
|
+
}
|
|
5038
|
+
const preexisting = denyEntries.filter((name) => existingDeny.includes(name));
|
|
5039
|
+
if (preexisting.length > 0) {
|
|
5040
|
+
notes.push(`${preexisting.join(", ")} ${preexisting.length === 1 ? "is" : "are"} already present in permissions.deny — present entries are not duplicated.`);
|
|
5041
|
+
}
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
if (errors.length > 0) {
|
|
5046
|
+
return { host: "claude-code", scope: input.scope, tiers, edits: [], notes, errors };
|
|
5047
|
+
}
|
|
5048
|
+
const forcedIds = [
|
|
5049
|
+
...input.search === "forced" ? [BUILTIN_TOOL_IDS.search] : [],
|
|
5050
|
+
...input.fetch === "forced" ? [BUILTIN_TOOL_IDS.fetch] : []
|
|
5051
|
+
];
|
|
5052
|
+
if (forcedIds.length > 0) {
|
|
5053
|
+
notes.push(`Forced is exclusion: bare deny rules (${forcedIds.join(", ")}) in permissions.deny remove the built-in tool ` + "from the model's context entirely — deny outranks allow at every settings level and cannot be carved out, so " + `the exclusion also applies to every other workflow that relied on the tool ${input.scope === "global" ? "in every project on this machine" : "in this project"}. ` + FORCED_COST_NOTE);
|
|
5054
|
+
}
|
|
5055
|
+
const offIds = [
|
|
5056
|
+
...input.search === "off" ? [MCP_TOOL_IDS.search] : [],
|
|
5057
|
+
...input.fetch === "off" ? [MCP_TOOL_IDS.fetch] : []
|
|
5058
|
+
];
|
|
5059
|
+
if (offIds.length > 0) {
|
|
5060
|
+
notes.push(`Off capabilities are written as their full tool ids (${offIds.join(", ")}) in permissions.deny: the tool ` + "leaves the model's context while the server keeps running; delete the entry to re-enable it.");
|
|
5061
|
+
}
|
|
5062
|
+
if (input.search === "optional" || input.fetch === "optional") {
|
|
5063
|
+
notes.push("Claude Code ships built-in WebSearch and WebFetch tools; under the optional tier they stay available " + "alongside the DeepSeek tools.");
|
|
5064
|
+
}
|
|
5065
|
+
if (input.search === "preferred" || input.fetch === "preferred") {
|
|
5066
|
+
if (input.scope === "project" && !existsSync9(paths.rules) && existsSync9(join2(input.cwd, "AGENTS.md"))) {
|
|
5067
|
+
notes.push(`This plan creates ${paths.rules} where none exists, and ${join2(input.cwd, "AGENTS.md")} is present: Claude ` + "Code reads AGENTS.md only when no CLAUDE.md exists above the cwd, so the new CLAUDE.md takes precedence — " + "merge anything you rely on from AGENTS.md before applying.");
|
|
5068
|
+
}
|
|
5069
|
+
edits.push({
|
|
5070
|
+
file: paths.rules,
|
|
5071
|
+
format: "rules",
|
|
5072
|
+
block: buildRulesBlock(input.host, rulesLines(MCP_TOOL_IDS, input.search, input.fetch))
|
|
5073
|
+
});
|
|
5074
|
+
notes.push(input.scope === "global" ? `Rules block upserted in ${paths.rules}; Claude Code loads the user CLAUDE.md plus every CLAUDE.md from the cwd up the directory tree, concatenating the levels.` : `Rules block upserted in ${paths.rules}; Claude Code loads every CLAUDE.md from the cwd up the directory tree, concatenating the levels.`);
|
|
5075
|
+
}
|
|
5076
|
+
return { host: "claude-code", scope: input.scope, tiers, edits, notes, errors };
|
|
5077
|
+
}
|
|
5078
|
+
var CLAUDE_CODE_TESTED_VERSION = "2.1.303", BUILTIN_TOOL_IDS;
|
|
5079
|
+
var init_claude_code = __esm(() => {
|
|
5080
|
+
init_constants();
|
|
5081
|
+
init_detect();
|
|
5082
|
+
init_formats();
|
|
5083
|
+
init_rules();
|
|
5084
|
+
init_value();
|
|
5085
|
+
init_paths();
|
|
5086
|
+
init_rulesContent();
|
|
5087
|
+
init_shared();
|
|
5088
|
+
BUILTIN_TOOL_IDS = { search: "WebSearch", fetch: "WebFetch" };
|
|
5089
|
+
});
|
|
5090
|
+
|
|
5091
|
+
// src/installer/hosts/codex.ts
|
|
5092
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
4875
5093
|
function probeCodexVersion(err) {
|
|
4876
|
-
const result =
|
|
5094
|
+
const result = spawnSync2("codex", ["--version"], { timeout: 5000, encoding: "utf8", env: process.env, shell: true });
|
|
4877
5095
|
const version = result.status === 0 ? parseCodexVersion(result.stdout ?? "") : undefined;
|
|
4878
5096
|
if (version === undefined) {
|
|
4879
5097
|
err("note: codex was not found on PATH or printed no version; the version-drift check was skipped");
|
|
@@ -5163,7 +5381,12 @@ function planPiExtension(input, previous) {
|
|
|
5163
5381
|
previous: typeof namespace.enableFetch === "boolean" ? namespace.enableFetch : undefined
|
|
5164
5382
|
});
|
|
5165
5383
|
}
|
|
5166
|
-
edits.push({
|
|
5384
|
+
edits.push({
|
|
5385
|
+
file: paths.config,
|
|
5386
|
+
format: "json",
|
|
5387
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5388
|
+
ops
|
|
5389
|
+
});
|
|
5167
5390
|
notes.push("Pi resolves the packages source at session start; the first launch after apply fetches the package from npm " + "(pi update --extensions refreshes it later).");
|
|
5168
5391
|
notes.push(`The extension reads ${API_KEY_ENV} from the Pi process environment; no env forwarding is needed. ` + "Set it in the shell that launches Pi.");
|
|
5169
5392
|
if (input.search === "off" || input.fetch === "off") {
|
|
@@ -5227,8 +5450,14 @@ function planMcpCompetitorDisables(input, notes, errors) {
|
|
|
5227
5450
|
const edits = [];
|
|
5228
5451
|
if (competitors.length > 0) {
|
|
5229
5452
|
const ops = planCompetitorDisables(servers, competitors, notes, errors);
|
|
5230
|
-
if (ops.length > 0)
|
|
5231
|
-
edits.push({
|
|
5453
|
+
if (ops.length > 0) {
|
|
5454
|
+
edits.push({
|
|
5455
|
+
file: mcpFile,
|
|
5456
|
+
format: "json",
|
|
5457
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5458
|
+
ops
|
|
5459
|
+
});
|
|
5460
|
+
}
|
|
5232
5461
|
}
|
|
5233
5462
|
if (bystanders.length > 0)
|
|
5234
5463
|
notes.push(othersStayRegisteredNote(bystanders));
|
|
@@ -5260,7 +5489,12 @@ function planPiMcp(input) {
|
|
|
5260
5489
|
const servers = loaded.value.mcpServers;
|
|
5261
5490
|
const existingEntry = isPlainObject(servers) ? servers.deepseek : undefined;
|
|
5262
5491
|
const ops = [{ op: "set", path: ["mcpServers", "deepseek"], value: entry, previous: existingEntry }];
|
|
5263
|
-
edits.push({
|
|
5492
|
+
edits.push({
|
|
5493
|
+
file: paths.config,
|
|
5494
|
+
format: "json",
|
|
5495
|
+
...input.scope === "global" ? { shared: true } : {},
|
|
5496
|
+
ops
|
|
5497
|
+
});
|
|
5264
5498
|
if (isPlainObject(servers)) {
|
|
5265
5499
|
if (servers.deepseek !== undefined)
|
|
5266
5500
|
notes.push(replacingExistingEntryNote("mcpServers.deepseek"));
|
|
@@ -5447,16 +5681,19 @@ var init_hosts = __esm(() => {
|
|
|
5447
5681
|
init_value();
|
|
5448
5682
|
init_ownership();
|
|
5449
5683
|
init_state();
|
|
5684
|
+
init_claude_code();
|
|
5450
5685
|
init_codex();
|
|
5451
5686
|
init_opencode();
|
|
5452
5687
|
init_pi();
|
|
5453
5688
|
HOST_PLANNERS = {
|
|
5454
5689
|
opencode: planOpenCode,
|
|
5455
5690
|
pi: planPi,
|
|
5456
|
-
codex: planCodex
|
|
5691
|
+
codex: planCodex,
|
|
5692
|
+
"claude-code": planClaudeCode
|
|
5457
5693
|
};
|
|
5458
5694
|
HOST_VERSION_PROBES = {
|
|
5459
|
-
codex: probeCodexVersion
|
|
5695
|
+
codex: probeCodexVersion,
|
|
5696
|
+
"claude-code": probeClaudeCodeVersion
|
|
5460
5697
|
};
|
|
5461
5698
|
});
|
|
5462
5699
|
|
|
@@ -5547,8 +5784,8 @@ function uninstallLocked(input, record) {
|
|
|
5547
5784
|
for (const file of [...record.files].reverse()) {
|
|
5548
5785
|
lines.push(...revertRecordedFile(file, record.host, { label: "", whole: true }));
|
|
5549
5786
|
}
|
|
5550
|
-
deleteRecord(input);
|
|
5551
5787
|
releaseFileOwnership(input, record.files);
|
|
5788
|
+
deleteRecord(input);
|
|
5552
5789
|
pruneEmptyDir(stateDir(input, input.scope));
|
|
5553
5790
|
lines.push(`removed install record (${input.host}, ${input.scope})`);
|
|
5554
5791
|
return lines;
|
|
@@ -5871,7 +6108,7 @@ var init_package = __esm(() => {
|
|
|
5871
6108
|
package_default = {
|
|
5872
6109
|
$schema: "https://json.schemastore.org/package.json",
|
|
5873
6110
|
name: "deepseek-web-retrieval",
|
|
5874
|
-
version: "0.
|
|
6111
|
+
version: "0.8.0",
|
|
5875
6112
|
description: "Token-efficient DeepSeek Search and Fetch for coding agents, starting with OpenCode v2.",
|
|
5876
6113
|
type: "module",
|
|
5877
6114
|
license: "MIT",
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Claude Code
|
|
2
|
+
|
|
3
|
+
Claude Code adapter for [DeepSeek Web Retrieval](../../README.md).
|
|
4
|
+
|
|
5
|
+
Integration mechanism: MCP server over stdio, registered in Claude Code's own MCP config
|
|
6
|
+
files — `.mcp.json` at the project root (project scope) or the top-level `mcpServers` key
|
|
7
|
+
of `~/.claude.json` (user scope, where `claude mcp add --scope user` writes). Claude Code
|
|
8
|
+
negotiates MCP revision `2026-07-28`; the server accepts clients from `2024-11-05` through
|
|
9
|
+
`2026-07-28`, so the same build works for other MCP hosts (see
|
|
10
|
+
[the compatibility research](../research/mcp-compatibility.md)).
|
|
11
|
+
|
|
12
|
+
When registered, it adds:
|
|
13
|
+
|
|
14
|
+
- A `deepseek_web_search` tool backed by DeepSeek Search
|
|
15
|
+
- A `deepseek_fetch` tool backed by DeepSeek Fetch
|
|
16
|
+
|
|
17
|
+
The tools appear in Claude Code as `mcp__deepseek__deepseek_web_search` and
|
|
18
|
+
`mcp__deepseek__deepseek_fetch`. Claude Code's built-in `WebSearch` and `WebFetch` tools
|
|
19
|
+
are not replaced; see [Control tiers](#control-tiers) for routing them away.
|
|
20
|
+
|
|
21
|
+
For shared search/fetch limits, retries, and privacy behavior, see
|
|
22
|
+
[General behavior](../../README.md#general-behavior).
|
|
23
|
+
For configuration layers, the full knob mapping, and defaults, see the
|
|
24
|
+
[configuration guide](../configuration.md).
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Claude Code at the tested version in [`docs/hosts.jsonc`](../hosts.jsonc); init reports
|
|
29
|
+
version drift, and newer versions may require rechecking routing behavior
|
|
30
|
+
- Node.js 20 or newer (`npx` runs the server)
|
|
31
|
+
- A DeepSeek account with access to `/v1/tools/search` and `/v1/tools/fetch`
|
|
32
|
+
- A funded DeepSeek account; Search and Fetch are billed per successful request
|
|
33
|
+
- `DEEPSEEK_API_KEY` set in the shell that launches Claude Code
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
The quickest option is the installer CLI:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx deepseek-web-retrieval init --host claude-code --scope project \
|
|
41
|
+
--search <tier> --fetch <tier>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
It prints the full plan — every file, old value → new value, and notes about existing MCP
|
|
45
|
+
servers — and writes nothing until you pass `--apply`. Add `--pin` to register the exact
|
|
46
|
+
version of the CLI you are running instead of the always-latest package name. `uninstall`
|
|
47
|
+
removes recorded additions and restores snapshotted values that have not since been edited.
|
|
48
|
+
`--scope project` registers the server in `./.mcp.json` (shareable via VCS) and writes tier
|
|
49
|
+
artifacts to `./.claude/settings.json` and `./CLAUDE.md`; `--scope global` uses
|
|
50
|
+
`~/.claude.json`, `~/.claude/settings.json`, and `~/.claude/CLAUDE.md` instead — the two
|
|
51
|
+
scopes use disjoint files, so both can coexist. An `off` capability is written as the
|
|
52
|
+
tool's full id in the `permissions.deny` array — Claude Code's own mechanism, which removes
|
|
53
|
+
the tool from the model's context while the server keeps running — merging with any entries
|
|
54
|
+
you put there yourself. When `claude` is on PATH, `init` also probes `claude --version` and
|
|
55
|
+
adds a plan note when the installed release differs from the tested lock (see
|
|
56
|
+
[hosts.jsonc](../hosts.jsonc)) — version-anchored behavior such as the deny rules may have
|
|
57
|
+
drifted between the two.
|
|
58
|
+
|
|
59
|
+
For an existing installer-managed setup, follow the
|
|
60
|
+
[upgrade instructions](../integration.md#upgrading-existing-installs) from the original
|
|
61
|
+
project directory and scope.
|
|
62
|
+
|
|
63
|
+
### Manual install
|
|
64
|
+
|
|
65
|
+
Add the server to `.mcp.json` at the project root:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"deepseek": {
|
|
71
|
+
"command": "npx",
|
|
72
|
+
"args": ["-y", "deepseek-web-retrieval"],
|
|
73
|
+
"env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> [!NOTE]
|
|
80
|
+
> `"${DEEPSEEK_API_KEY}"` is Claude Code's own `${VAR}` expansion: the key is read from
|
|
81
|
+
> the environment that launches Claude Code and never sits in the file. A literal value
|
|
82
|
+
> works too if you prefer it in the file. For a user-scope install, the same entry goes
|
|
83
|
+
> under the top-level `mcpServers` key of `~/.claude.json`, or use
|
|
84
|
+
> `claude mcp add --scope user --env 'DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}' deepseek -- npx -y deepseek-web-retrieval`.
|
|
85
|
+
|
|
86
|
+
Claude Code asks once per project whether to use servers from `.mcp.json`; approve the
|
|
87
|
+
`deepseek` server when prompted (or via the `/mcp` menu), or its tools will not load in
|
|
88
|
+
that project.
|
|
89
|
+
|
|
90
|
+
To disable one capability, add the tool's full id to `permissions.deny` in
|
|
91
|
+
`.claude/settings.json` — Claude Code removes the tool from the model's context while the
|
|
92
|
+
server keeps running:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"permissions": {
|
|
97
|
+
"deny": ["mcp__deepseek__deepseek_web_search"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The ids match the tool names verbatim (`mcp__deepseek__deepseek_web_search`,
|
|
103
|
+
`mcp__deepseek__deepseek_fetch`). The server also honors
|
|
104
|
+
`DEEPSEEK_ENABLE_SEARCH`/`DEEPSEEK_ENABLE_FETCH` in the server's `env` block if you prefer
|
|
105
|
+
switching the tools off at the source; the installer uses `permissions.deny`.
|
|
106
|
+
|
|
107
|
+
### Local checkout
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
git clone https://github.com/gwokhou/deepseek-web-retrieval.git
|
|
111
|
+
cd deepseek-web-retrieval && bun install && bun run build
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"mcpServers": {
|
|
117
|
+
"deepseek": {
|
|
118
|
+
"command": "node",
|
|
119
|
+
"args": ["/absolute/path/to/deepseek-web-retrieval/dist/cli.js"],
|
|
120
|
+
"env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Control tiers
|
|
127
|
+
|
|
128
|
+
Claude Code ships built-in `WebSearch` and `WebFetch` tools; neither is replaceable, so
|
|
129
|
+
both capabilities use exclusion for the forced tier:
|
|
130
|
+
|
|
131
|
+
| Capability | Optional | Preferred | Forced |
|
|
132
|
+
| --- | --- | --- | --- |
|
|
133
|
+
| Search | Tools registered; the built-in `WebSearch` stays available | Plus a CLAUDE.md rules block steering the model to `mcp__deepseek__deepseek_web_search` | Exclusion: `permissions.deny: ["WebSearch"]` |
|
|
134
|
+
| Fetch | Tools registered; the built-in `WebFetch` stays available | Plus the rules block | Exclusion: `permissions.deny: ["WebFetch"]` |
|
|
135
|
+
|
|
136
|
+
The installer maps each tier to these artifacts. Deny entries land in
|
|
137
|
+
`~/.claude/settings.json` (user) or `./.claude/settings.json` (project) and merge with
|
|
138
|
+
your existing `permissions.deny` entries. The CLAUDE.md rules block lands in
|
|
139
|
+
`~/.claude/CLAUDE.md` (user) or `./CLAUDE.md` (project); Claude Code loads the user file
|
|
140
|
+
plus every CLAUDE.md from the cwd up the directory tree, concatenating the levels — so
|
|
141
|
+
the block steers wherever it is installed. Under a forced tier, other web-capable MCP
|
|
142
|
+
servers detected in the config are denied by server-level rules (`mcp__<server>` denies
|
|
143
|
+
every tool of that server), so routing stays exclusive.
|
|
144
|
+
|
|
145
|
+
> [!WARNING]
|
|
146
|
+
> Deny rules outrank allow rules at every settings level and **cannot be carved out** —
|
|
147
|
+
> denying `WebSearch` or `WebFetch` removes the tool for every workflow in the install's
|
|
148
|
+
> scope (every project on the machine for `--scope global`). A managed policy layer
|
|
149
|
+
> (`managed-settings.json`) outranks both files; the installer reports when one exists.
|
|
150
|
+
|
|
151
|
+
## Authentication
|
|
152
|
+
|
|
153
|
+
`DEEPSEEK_API_KEY` is the only source; MCP has no host credential store. It is forwarded
|
|
154
|
+
through the server's `env` block as `${DEEPSEEK_API_KEY}` and read per request, sent only
|
|
155
|
+
to `api.deepseek.com` as a Bearer token. Without it the server exits at startup with a
|
|
156
|
+
message on stderr.
|
|
157
|
+
|
|
158
|
+
## Usage
|
|
159
|
+
|
|
160
|
+
Ask naturally:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
Search the web for the latest Claude Code MCP config changes and cite the sources.
|
|
164
|
+
Fetch https://code.claude.com/docs/en/mcp and summarize the config keys.
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The model calls `mcp__deepseek__deepseek_web_search` or `mcp__deepseek__deepseek_fetch`.
|
|
168
|
+
|
|
169
|
+
The search tool accepts an optional `original_question` argument — the user's question in
|
|
170
|
+
their own words — forwarded as DeepSeek's `ori_question` to sharpen search intent; omit it
|
|
171
|
+
when the query already stands alone.
|
|
172
|
+
|
|
173
|
+
Tool output is capped at 25k tokens by default (`MAX_MCP_OUTPUT_TOKENS`), and successful
|
|
174
|
+
text results longer than 50,000 characters are saved to a file and referenced — not
|
|
175
|
+
dropped silently.
|
|
176
|
+
|
|
177
|
+
## Configuration
|
|
178
|
+
|
|
179
|
+
Set these in the server's `env` block in `.mcp.json` (or `~/.claude.json`). Values map onto
|
|
180
|
+
the shared option pipeline, so defaults and clamping match the other adapters.
|
|
181
|
+
`DEEPSEEK_API_KEY` goes in the same block as the `${VAR}` expansion.
|
|
182
|
+
|
|
183
|
+
> [!NOTE]
|
|
184
|
+
> A shared config file — `~/.config/deepseek-web-retrieval/config.json`
|
|
185
|
+
> (Windows: `%APPDATA%\deepseek-web-retrieval\config.json`), camelCase keys —
|
|
186
|
+
> sits under the environment layer and works with no env forwarding at all: the server
|
|
187
|
+
> reads the file itself, making it the way to set knobs without editing the MCP entry. The
|
|
188
|
+
> file carries neither the API key nor the host-integration switches. Run
|
|
189
|
+
> `npx deepseek-web-retrieval config` to see every knob's effective value and source
|
|
190
|
+
> (it replays file warnings visibly and exits 1 on file problems); `config --search "<query>"`
|
|
191
|
+
> verifies key, config, and network with one billed search.
|
|
192
|
+
|
|
193
|
+
### Search
|
|
194
|
+
|
|
195
|
+
The "Config file" column names each key's camelCase twin in the shared config file —
|
|
196
|
+
one vocabulary pair, one pipeline. `—` marks host-integration entries the file rejects.
|
|
197
|
+
|
|
198
|
+
| Env variable | Config file | Default | Description |
|
|
199
|
+
| --- | --- | --- | --- |
|
|
200
|
+
| `DEEPSEEK_ENABLE_SEARCH` | — | `true` | Register the search tool. Only an explicit `false`/`0`/`no`/`off` disables it; at least one tool must stay enabled. |
|
|
201
|
+
| `DEEPSEEK_SEARCH_COUNT` | `count` | `10` | Results requested from DeepSeek, 1–50. Billing is per request, not per result. |
|
|
202
|
+
| `DEEPSEEK_SEARCH_MAX_RESULTS` | `maxResults` | `10` | Results returned to the model after filtering, 0–50; defaults to `DEEPSEEK_SEARCH_COUNT` when unset. `0` returns no results. |
|
|
203
|
+
| `DEEPSEEK_SEARCH_MIN_RERANK` | `minRerank` | `0` | Drop results below this relevance score (0–3); results without a finite score pass at zero and are dropped at a positive threshold. |
|
|
204
|
+
| `DEEPSEEK_SEARCH_MIN_AUTHORITY` | `minAuthority` | `0` | Drop known authorities below this tier. Authority is 0–4. |
|
|
205
|
+
| `DEEPSEEK_SEARCH_ALLOW_UNKNOWN_AUTHORITY` | `allowUnknownAuthority` | `true` | Keep results whose authority is missing or unknown. Set `false`/`0`/`no`/`off` to drop them. |
|
|
206
|
+
| `DEEPSEEK_SEARCH_BLOCKED_DOMAINS` | `blockedDomains` | unset | Comma-separated; adds `-site:` operators to the query. |
|
|
207
|
+
| `DEEPSEEK_SEARCH_INCLUDE_DOMAINS` | `includeDomains` | unset | Comma-separated; restricts results with `site:` operators. |
|
|
208
|
+
| `DEEPSEEK_SEARCH_LANGUAGE` | `language` | unset | Adds a `lang:` operator, for example `zh,en`. |
|
|
209
|
+
| `DEEPSEEK_SEARCH_LOCALIZATION` | `searchLocalization` | unset | Valid IPv4 or IPv6 address sent as DeepSeek's `localization` to prefer geo-localized results; invalid values are ignored. Deployment-level only, never a per-call parameter. |
|
|
210
|
+
| `DEEPSEEK_SEARCH_MAX_SUMMARY_CHARS` | `maxSummaryChars` | `1200` | Per-result summary cap, 0–1,000,000. |
|
|
211
|
+
| `DEEPSEEK_SEARCH_TIMEOUT_MS` | `searchTimeoutMs` | `30000` | Search timeout, 1,000–120,000 ms. |
|
|
212
|
+
|
|
213
|
+
### Fetch
|
|
214
|
+
|
|
215
|
+
| Env variable | Config file | Default | Description |
|
|
216
|
+
| --- | --- | --- | --- |
|
|
217
|
+
| `DEEPSEEK_ENABLE_FETCH` | — | `true` | Register the fetch tool. Same false-only semantics as `DEEPSEEK_ENABLE_SEARCH`. |
|
|
218
|
+
| `DEEPSEEK_FETCH_MAX_CHARS` | `fetchMaxChars` | `16000` | Maximum characters returned, including the title, 0–1,000,000. |
|
|
219
|
+
| `DEEPSEEK_FETCH_TIMEOUT_MS` | `fetchTimeoutMs` | `30000` | Fetch timeout, 1,000–120,000 ms. |
|
|
220
|
+
| `DEEPSEEK_FETCH_ALLOWED_DOMAINS` | `fetchAllowedDomains` | unset | If set, only these hosts and subdomains may be fetched. |
|
|
221
|
+
| `DEEPSEEK_FETCH_BLOCKED_DOMAINS` | `fetchBlockedDomains` | unset | Always block these hosts and subdomains. Blocked wins over allowed. |
|
|
222
|
+
| `DEEPSEEK_FETCH_BLOCK_PRIVATE_HOSTS` | `fetchBlockPrivateHosts` | `true` | Reject localhost, private IPs, `.local`, `.internal`, and similar hosts. |
|
|
223
|
+
|
|
224
|
+
### Credentials and retries
|
|
225
|
+
|
|
226
|
+
| Env variable | Config file | Default | Description |
|
|
227
|
+
| --- | --- | --- | --- |
|
|
228
|
+
| `DEEPSEEK_API_KEY` | — | required | DeepSeek API key, expanded from the launching environment via `${DEEPSEEK_API_KEY}`. The server refuses to start without it. |
|
|
229
|
+
| `DEEPSEEK_MAX_RETRIES` | `maxRetries` | `2` | Retries for 408, 425, 429, and 5xx responses, 0–5. |
|
|
230
|
+
| `DEEPSEEK_RETRY_BASE_DELAY_MS` | `retryBaseDelayMs` | `250` | Base delay for exponential backoff. `Retry-After` is honored up to 10 seconds. |
|
|
231
|
+
|
|
232
|
+
## Troubleshooting
|
|
233
|
+
|
|
234
|
+
| Error or symptom | What to do |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| Server fails to start or `HTTP 401` from the tools | `DEEPSEEK_API_KEY` must be set in the environment that launches Claude Code; the `${DEEPSEEK_API_KEY}` expansion in the server's `env` block reads it at launch. |
|
|
237
|
+
| Project tools missing after install | Claude Code asks once per project whether to use `.mcp.json` servers; approve the `deepseek` server when prompted or via the `/mcp` menu. |
|
|
238
|
+
| Both tools missing after startup | At least one of `DEEPSEEK_ENABLE_SEARCH`/`DEEPSEEK_ENABLE_FETCH` must stay enabled; the server exits when both are false. |
|
|
239
|
+
| Forced tier still shows the built-in tool | The kill switch is the bare deny rule in `permissions.deny` at the install's scope; a managed policy layer (`managed-settings.json`) outranks it — the plan reports when one exists. |
|
|
240
|
+
| Routing not exclusive | Other MCP servers stay registered and are noted in the plan. Under a forced tier, web-capable servers detected in the config (and the other scope's file) are denied by server-level `mcp__<server>` rules; a server added after install is not covered — re-run init. |
|
|
241
|
+
| Fetch results appear as a file reference instead of content | Tool output is capped at 25k tokens by default; raise `MAX_MCP_OUTPUT_TOKENS` or lower `DEEPSEEK_FETCH_MAX_CHARS`. |
|
|
242
|
+
| `HTTP 402`, `HTTP 429` | Top up the DeepSeek account; wait, reduce concurrency, or set `DEEPSEEK_MAX_RETRIES`. |
|
|
243
|
+
| Fetch fails on localhost or an internal URL | Use a local fetch tool; DeepSeek cannot reach private networks. |
|
|
244
|
+
|
|
245
|
+
## Compatibility
|
|
246
|
+
|
|
247
|
+
| Component | Version |
|
|
248
|
+
| --- | --- |
|
|
249
|
+
| Claude Code | locked in [`docs/hosts.jsonc`](../hosts.jsonc) |
|
|
250
|
+
| MCP revision | `2026-07-28` negotiated; server accepts `2024-11-05`–`2026-07-28` |
|
|
251
|
+
| MCP SDK | `@modelcontextprotocol/server` 2.3.1 |
|
|
252
|
+
| Runtime | Node.js 20+ (stdio) |
|
|
253
|
+
| DeepSeek API | `/v1/tools/search`, `/v1/tools/fetch` |
|
|
254
|
+
|
|
255
|
+
Tested with Claude Code at the `docs/hosts.jsonc` lock on Linux over stdio: the keyless
|
|
256
|
+
Docker e2e verifies the installer's registration, tiers, and uninstall with a real
|
|
257
|
+
`claude mcp list` handshake, and the billed scenario (`bun run test:e2e claude-code`,
|
|
258
|
+
requires `DEEPSEEK_API_KEY`) runs headless prompts end to end with the deny kill switch
|
|
259
|
+
asserted for both capabilities.
|
|
260
|
+
|
|
261
|
+
## Uninstall
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
npx deepseek-web-retrieval uninstall --host claude-code --scope project
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
This removes the recorded `mcpServers.deepseek` entry and the installer-appended
|
|
268
|
+
`permissions.deny` entries only when their current values still match what the installer
|
|
269
|
+
wrote, removes the managed rules block, and deletes installer-created config files only
|
|
270
|
+
when left empty. Later user edits to configuration values are kept and reported. For a
|
|
271
|
+
manual installation, remove the `deepseek` entry from `.mcp.json` (or `~/.claude.json`),
|
|
272
|
+
any `deepseek-web-retrieval` rules block in your `CLAUDE.md`, and the deny entries the
|
|
273
|
+
setup added to `settings.json` if they were only for this integration.
|
package/docs/agents/codex.md
CHANGED
|
@@ -24,6 +24,8 @@ both the text and `structuredContent`.
|
|
|
24
24
|
|
|
25
25
|
For shared search/fetch limits, retries, and privacy behavior, see
|
|
26
26
|
[General behavior](../../README.md#general-behavior).
|
|
27
|
+
For configuration layers, the full knob mapping, and defaults, see the
|
|
28
|
+
[configuration guide](../configuration.md).
|
|
27
29
|
|
|
28
30
|
## Requirements
|
|
29
31
|
|
|
@@ -15,6 +15,8 @@ if you want all Markdown fetches to go through DeepSeek.
|
|
|
15
15
|
|
|
16
16
|
For shared search/fetch limits, retries, and privacy behavior, see
|
|
17
17
|
[General behavior](../../README.md#general-behavior).
|
|
18
|
+
For configuration layers, the full knob mapping, and defaults, see the
|
|
19
|
+
[configuration guide](../configuration.md).
|
|
18
20
|
|
|
19
21
|
## Requirements
|
|
20
22
|
|
package/docs/agents/pi.md
CHANGED
|
@@ -22,6 +22,8 @@ When loaded, it adds:
|
|
|
22
22
|
Pi 1.3.0 ships no built-in web tool, so the registered tools are the only web path. For
|
|
23
23
|
shared search/fetch limits, retries, and privacy behavior, see
|
|
24
24
|
[General behavior](../../README.md#general-behavior).
|
|
25
|
+
For configuration layers, the full knob mapping, and defaults, see the
|
|
26
|
+
[configuration guide](../configuration.md).
|
|
25
27
|
|
|
26
28
|
## Requirements
|
|
27
29
|
|