claude-mem-lite 6.22.1 → 6.23.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +40 -6
- package/README.zh-CN.md +33 -5
- package/cli/common.mjs +2 -0
- package/haiku-client.mjs +125 -21
- package/hook.mjs +5 -1
- package/lib/anthropic-base-url.mjs +97 -0
- package/lib/delete-core.mjs +5 -0
- package/lib/llm-provider-probe.mjs +45 -3
- package/lib/proxy-fetch.mjs +22 -2
- package/lib/search-telemetry.mjs +517 -0
- package/mem-cli.mjs +24 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -1
- package/schema.mjs +51 -0
- package/server.mjs +123 -11
- package/source-files.mjs +2 -0
- package/tool-schemas.mjs +36 -3
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-mem-lite",
|
|
12
|
-
"version": "6.
|
|
12
|
+
"version": "6.23.1",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"homepage": "https://github.com/sdsrss/claude-mem-lite",
|
|
15
15
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.23.1",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. FTS5 BM25 keyword search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "sdsrss"
|
package/README.md
CHANGED
|
@@ -194,7 +194,7 @@ Source files stay in the cloned repo. Update via `git pull && node install.mjs i
|
|
|
194
194
|
### What happens during installation
|
|
195
195
|
|
|
196
196
|
1. **Install dependencies** -- `npm install --omit=dev` (compiles native `better-sqlite3`)
|
|
197
|
-
2. **Register MCP server** -- `mem-lite` server with
|
|
197
|
+
2. **Register MCP server** -- `mem-lite` server with 19 tool definitions (10 core + 9 hidden-but-callable; `mem_search_feedback` is registered only when search telemetry is enabled). The pre-v2.78 generic server name `mem` is renamed to `mem-lite` for namespace hygiene; the tool names themselves (`mem_search`, `mem_recall`, ...) are unchanged.
|
|
198
198
|
3. **Configure hooks** -- all seven lifecycle events: `SessionStart`, `PreCompact`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `Stop`, `UserPromptSubmit`
|
|
199
199
|
4. **Create data directory** -- `~/.claude-mem-lite/` (hidden) for database, runtime, and managed resource files
|
|
200
200
|
5. **Auto-migrate** -- If `~/.claude-mem/` (original claude-mem) or `~/claude-mem-lite/` (pre-v0.5 unhidden) exists, migrates database and runtime files to `~/.claude-mem-lite/`, preserving the original untouched
|
|
@@ -237,6 +237,28 @@ rm -rf ~/claude-mem-lite/ # pre-v0.5 unhidden (if not auto-moved)
|
|
|
237
237
|
repos/ # Shallow-cloned source repos
|
|
238
238
|
```
|
|
239
239
|
|
|
240
|
+
## Upgrading to 6.23.1
|
|
241
|
+
|
|
242
|
+
**If you set `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY`, background LLM calls now go to that
|
|
243
|
+
gateway.** No schema-version change: 6.22.1 still opens the database after this release has.
|
|
244
|
+
To keep the old behaviour, stay on 6.22.1; a plugin install that already updated can follow the
|
|
245
|
+
rollback recipe under "Trust model per install path".
|
|
246
|
+
|
|
247
|
+
- **Who is affected.** The direct API leg used to post to `api.anthropic.com` with the built-in
|
|
248
|
+
model IDs whatever `ANTHROPIC_BASE_URL` said; only the `claude -p` fallback followed it. Now
|
|
249
|
+
the direct leg uses it too, together with `ANTHROPIC_DEFAULT_HAIKU_MODEL` /
|
|
250
|
+
`ANTHROPIC_DEFAULT_SONNET_MODEL`. Without `ANTHROPIC_BASE_URL` nothing changes, even when the
|
|
251
|
+
two model variables are set.
|
|
252
|
+
- **A value the plugin cannot use** (not a parseable URL, not http or https, plain http to a
|
|
253
|
+
host that is not loopback, credentials in the URL, a query or a fragment) skips the direct
|
|
254
|
+
leg: every background call falls back to `claude -p`, which reads `ANTHROPIC_BASE_URL` itself,
|
|
255
|
+
and `claude-mem-lite doctor` says why. `doctor` now probes the gateway's host and port instead
|
|
256
|
+
of `api.anthropic.com`.
|
|
257
|
+
- **Also in this release:** large stores no longer time out on every prompt. The database now
|
|
258
|
+
gets SQLite's planner statistics, which adds SQLite's own `sqlite_stat1` / `sqlite_stat4`
|
|
259
|
+
tables. And `CLAUDE_MEM_SEARCH_TELEMETRY=1` is a new, off-by-default switch that records MCP
|
|
260
|
+
searches for relevance feedback. Details in CHANGELOG.md.
|
|
261
|
+
|
|
240
262
|
## Upgrading to 6.21.0
|
|
241
263
|
|
|
242
264
|
**Projects whose names are not plain ASCII get a new id, and what they stored moves once.** No
|
|
@@ -496,8 +518,9 @@ are lost; only the derived vector index is.
|
|
|
496
518
|
|
|
497
519
|
### MCP Tools (used automatically by Claude)
|
|
498
520
|
|
|
499
|
-
|
|
500
|
-
|
|
521
|
+
The server defines 19 tools but registers `mem_search_feedback` only when
|
|
522
|
+
`CLAUDE_MEM_SEARCH_TELEMETRY=1`. The other 9 **core** tools appear in `tools/list`
|
|
523
|
+
by default. The 9 **hidden** tools remain callable at the
|
|
501
524
|
protocol layer (`tools/call` by exact name still routes normally); they're
|
|
502
525
|
omitted from the list response so Claude Code sessions don't load 9 extra
|
|
503
526
|
tool schemas at startup. (It read 20 / 11 until v5.0.0 removed the two skill-registry
|
|
@@ -506,11 +529,12 @@ tools — `tool-schemas.mjs` is the source of truth, and
|
|
|
506
529
|
`README.zh-CN.md`, `llms.txt` and `docs/ARCHITECTURE.md` to it.) Hidden tools are the maintenance / admin / browser
|
|
507
530
|
surface — reach them through the CLI column in the second table.
|
|
508
531
|
|
|
509
|
-
**Core (
|
|
532
|
+
**Core (10 definitions; `mem_search_feedback` requires search telemetry)**
|
|
510
533
|
|
|
511
534
|
| Tool | Description |
|
|
512
535
|
|------|-------------|
|
|
513
536
|
| `mem_search` | FTS5 full-text search with BM25 ranking. Filters by type, project, date range, importance level. |
|
|
537
|
+
| `mem_search_feedback` | Record sparse relevance labels for results from an existing `mem_search` Search ID. |
|
|
514
538
|
| `mem_recent` | Show most recent observations, ordered by time. Quick snapshot of latest activity. |
|
|
515
539
|
| `mem_recall` | Recall observations related to a file. Use before editing to surface past bugfixes and context. |
|
|
516
540
|
| `mem_timeline` | Browse observations chronologically around an anchor point. |
|
|
@@ -1064,7 +1088,10 @@ claude-mem-lite.
|
|
|
1064
1088
|
|----------|-------------|---------|
|
|
1065
1089
|
| `CLAUDE_MEM_DIR` | Custom data directory. All databases, runtime files, and managed resources are stored here. | `~/.claude-mem-lite/` |
|
|
1066
1090
|
| `CLAUDE_MEM_MODEL` | LLM model for background calls (episode extraction, session summaries). Accepts `haiku` or `sonnet`. | `haiku` |
|
|
1067
|
-
| `ANTHROPIC_API_KEY` | Anthropic API key. When set, all background LLM calls go directly to the Anthropic Messages API (with prompt caching). Highest priority. | _(unset → CLI)_ |
|
|
1091
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key. When set, all background LLM calls go directly to the Anthropic Messages API (with prompt caching) - or to the `ANTHROPIC_BASE_URL` gateway when that is set. Highest priority. | _(unset → CLI)_ |
|
|
1092
|
+
| `ANTHROPIC_BASE_URL` | Base URL for the direct Messages API when an Anthropic-compatible gateway serves the models (Azure AI Foundry, LiteLLM, Bedrock/Vertex proxies). No `/v1` suffix — the endpoint path is appended. The `claude -p` fallback reads the same variable, so one value covers both transports. Plain `http://` is accepted only for a loopback host; anything else would send the API key unencrypted. A value that is set but unusable skips the direct API leg, so background calls fall back to the `claude -p` CLI, and doctor warns. | `https://api.anthropic.com` |
|
|
1093
|
+
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | Model ID or gateway deployment name for the `haiku` tier, honored on the direct API **only when `ANTHROPIC_BASE_URL` is set**. Without a base URL it stays CLI-only, so the Bedrock/Vertex form of this alias is never sent to `api.anthropic.com`. Use it when the gateway routes on deployment names rather than Anthropic model IDs (Azure Foundry deployments). The `claude -p` fallback resolves its `--model haiku` alias through it too. | built-in `claude-haiku-4-5-…` |
|
|
1094
|
+
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | Same as above for the `sonnet` tier. | built-in `claude-sonnet-4-5-…` |
|
|
1068
1095
|
| `OPENROUTER_API_KEY` | OpenRouter API key (OpenAI-compatible). Used for background LLM calls when `ANTHROPIC_API_KEY` is **not** set. If neither key is set, calls fall back to the `claude -p` CLI. | _(unset)_ |
|
|
1069
1096
|
| `OPENROUTER_MODEL` | Overrides the OpenRouter model slug for **all** background calls (e.g. `openai/gpt-4o-mini`, `qwen/qwen-2.5-72b-instruct`). When unset, the `CLAUDE_MEM_MODEL` tier maps to `anthropic/claude-haiku-4.5` (haiku) or `anthropic/claude-sonnet-4.5` (sonnet). | _(tier default)_ |
|
|
1070
1097
|
| `CLAUDE_MEM_DEBUG` | Enable debug logging (`1` to enable). | _(disabled)_ |
|
|
@@ -1082,7 +1109,7 @@ claude-mem-lite.
|
|
|
1082
1109
|
|
|
1083
1110
|
| Variable | Description | Default |
|
|
1084
1111
|
|----------|-------------|---------|
|
|
1085
|
-
| `CLAUDE_MEM_ALL_TOOLS` | `1` exposes
|
|
1112
|
+
| `CLAUDE_MEM_ALL_TOOLS` | `1` exposes every registered MCP tool in `tools/list` instead of only the core ones (pre-v2.34.0 behavior). The 9 hidden tools stay callable by exact name either way; `mem_search_feedback` is registered only when search telemetry is enabled. | _(9 core)_ |
|
|
1086
1113
|
| `CLAUDE_MEM_FILE_INTEL` | `0` disables the file-intel block injected before `Read` (past observations about the file you are about to open). | _(on)_ |
|
|
1087
1114
|
| `CLAUDE_MEM_FILE_INTEL_MIN_TOKENS` | Files smaller than this stay silent — file-intel only pays for itself on large files. | `800` |
|
|
1088
1115
|
| `CLAUDE_MEM_REREAD_GUARD` | `0` disables the warning when the same file is read twice in a session. Never fires on `offset`/`limit` paging. | _(on)_ |
|
|
@@ -1141,6 +1168,13 @@ benchmark and A/B harness are calibrated against — changing them invalidates t
|
|
|
1141
1168
|
| `CLAUDE_MEM_CITATION_RELEVANCE_GATE` | Stop credits an `access_count` to a memory the session cited only when something made that memory relevant to the session — it was injected, or you typed its `#NN` yourself. `off` restores the pre-v3.84.0 behaviour of crediting every `#NN` the assistant wrote, which over-counts sessions that discuss memories in prose (release notes, audit reports): measured on real transcripts, 267 of 859 credited (id, session) pairs — 31.1% — were mentions nothing had put in front of the model. Superseded citations are redirected to their keeper on both settings. | _(on)_ |
|
|
1142
1169
|
| `CLAUDE_MEM_SUBAGENT_DECAY` | The `subagent` injection face feeds the decay loop: memories handed to a dispatched agent enter the denominator, and the citation that agent makes in its own transcript counts as the numerator. `0` returns the face to metered-but-never-decaying (v3.77–v3.82). | _(on)_ |
|
|
1143
1170
|
| `CLAUDE_MEM_METRICS` | `1` records feature-injection counters surfaced by `claude-mem-lite stats`. | _(off)_ |
|
|
1171
|
+
| `CLAUDE_MEM_SEARCH_TELEMETRY` | `1` records MCP `mem_search` queries and results, prints Search IDs for optional `mem_search_feedback`, and stamps each search with the running claude-mem-lite version. | _(off)_ |
|
|
1172
|
+
|
|
1173
|
+
Search telemetry rows are retained until you delete them. Inspect their volume with
|
|
1174
|
+
`claude-mem-lite stats --search-telemetry`; clear them from the configured SQLite
|
|
1175
|
+
database (normally `~/.claude-mem-lite/claude-mem-lite.db`) with
|
|
1176
|
+
`DELETE FROM search_results; DELETE FROM search_runs; VACUUM;`. Pre-delete snapshots
|
|
1177
|
+
beside that database retain their earlier contents until those `.bak` files are removed.
|
|
1144
1178
|
|
|
1145
1179
|
### Background work
|
|
1146
1180
|
|
package/README.zh-CN.md
CHANGED
|
@@ -152,7 +152,7 @@ node install.mjs install
|
|
|
152
152
|
### 安装过程
|
|
153
153
|
|
|
154
154
|
1. **安装依赖** -- `npm install --omit=dev`(编译原生 `better-sqlite3`)
|
|
155
|
-
2. **注册 MCP 服务器** -- `mem-lite`
|
|
155
|
+
2. **注册 MCP 服务器** -- `mem-lite` 服务器定义 19 个工具(10 个核心 + 9 个隐藏但可调;`mem_search_feedback` 仅在启用搜索遥测时注册)。v2.78 前服务器名为通用的 `mem`,现已改名为 `mem-lite` 避免与用户其它 `.mcp.json` 冲突;工具名(`mem_search`/`mem_recall` 等)保持不变。
|
|
156
156
|
|
|
157
157
|
> **自动 adopt 不再把托管块加进你项目的 `CLAUDE.md`(6.19.4 之后的下一个版本起)。** 每次 SessionStart,插件都会送达引导文本(提升 Claude 主动调用 `mem_recall` / `mem_save` 的触发表)。在还没有托管块的 git 仓库里,它把托管块写进仓库根目录的 `CLAUDE.local.md`(Claude Code 像加载 `CLAUDE.md` 一样加载它,加载项目指令的子代理也能看到;内置的 Explore 和 Plan 子代理不加载这类文件),并把这个文件加进仓库的 `.git/info/exclude`(你的 ignore 规则已覆盖时不加),所以 git 不列出、也不提交它;第一次写入时你会看到一条**一次性提示**。Claude Code 在插件的启动钩子运行之前就读取了指令文件,所以创建这个文件的那个会话改为在上下文里注入一次引导(该会话的子代理看不到)。Claude Code 把某个 `AGENTS.md` 当作项目指令读取时,会话目录或上层只要有 `CLAUDE.md` 或 `CLAUDE.local.md` 它就不再读 `AGENTS.md`,所以在这种仓库里什么都不写,改为**注入**引导(子代理看不到),并给一条点名那个 `AGENTS.md` 的一次性提示。设了 `CLAUDE_MEM_RULES_STEERING=1` 时,托管块改写进仓库根目录的 `.claude/rules/claude-mem-lite.md`:同样经 `.git/info/exclude` 排除在 git 之外,Claude Code 把它当作项目指令加载(加载项目指令的子代理也能看到),而且不会让 `AGENTS.md` 失效;第一次写入时有单独的一次性提示。它不是默认做法,因为在预先登记的 A/B 里它没有达到事先定下的、胜过注入的标准(`docs/audits/20261006-d212-ab.md`)。不在 git 仓库里、仓库根是 `$HOME`、`CLAUDE.local.md` 已被 git 跟踪或是符号链接,或者仓库根是一个 `npm publish` 会把它带上的 npm 包(没有 `"private": true`,没有把它排除在外的 `files` 列表,没有 `files` 列表时也没有列出它的 `.npmignore`)时,什么都不写,改为每个会话把引导**注入**上下文,并一次性提示可以运行 `/adopt`(在 `$HOME` 下不提示)。设了这个变量但 rules 文件写不了时(路径上有符号链接、同名文件被跟踪或是你自己的文件、npm 包会把它带上)同样改为注入。`AGENTS.md` 旁边的提示会说明是哪个 `AGENTS.md`、为什么没有文件,并只给出在这里行得通的、保留 `AGENTS.md` 又能写文件的做法:在 `/config` 里把 Project instructions 设为 `claude-md-and-agents-md`,之后就会写 `CLAUDE.local.md`(`CLAUDE.local.md` 也会被拒写时不提,比如 npm 包根,或你删过的文件);运行 `/adopt`,它写的 `CLAUDE.md` 会导入同目录的 `AGENTS.md`(上层目录或子目录里的 `AGENTS.md` 会因此失效时不提)。托管块指向的详情文件放在插件自己的数据目录,以 `~/` 路径书写。`.git/info/exclude` 只对 git 生效:docker 构建上下文、归档和其他打包工具都可能把 `CLAUDE.local.md` 打进去(rules 文件同样如此;可发布的 npm 包根目录不会保留 `CLAUDE.local.md`:之前写入的托管块会在下一次会话启动时移除;在这个文件会让 `AGENTS.md` 失效的项目里,托管块会被移除、改为注入;设了 `CLAUDE_MEM_RULES_STEERING=1` 时,在写得了的情况下移到 rules 文件)。旧版本会在你打开的每个项目里,未经询问就向项目自己的 `<cwd>/CLAUDE.md`(通常会进 git)写入托管块,外加 `<cwd>/.claude/plugin_claude_mem_lite.md` 详情文件;这些项目保留托管块,本版本的第一个会话会因为文本变化刷新它——想把某个项目迁到本地文件,在那里运行 `claude-mem-lite unadopt` 并提交这次删除。为什么不全部改成注入:沙箱实测中(一个项目,Claude Opus 5.5),8 个会话里模型主动记录的次数,注入时平均 1.5 次,写在 `CLAUDE.local.md` 或 `CLAUDE.md` 时 5.25 次;而且子代理完全看不到注入的文本。**任何安装路径都生效**(npm、npx、`/plugin`、手动);这些提示都可以用 `MEM_NO_ADOPT_HINT=1` 关闭。
|
|
158
158
|
>
|
|
@@ -199,6 +199,24 @@ rm -rf ~/claude-mem-lite/ # v0.5 前的非隐藏目录(如未自动迁移)
|
|
|
199
199
|
repos/ # 浅克隆的源代码仓库
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
## 升级到 6.23.1
|
|
203
|
+
|
|
204
|
+
**如果你设置了 `ANTHROPIC_BASE_URL` 和 `ANTHROPIC_API_KEY`,后台 LLM 调用现在会发往那个网关。**
|
|
205
|
+
没有 schema 版本变更:本版本打开过的数据库,6.22.1 仍能打开。想保持旧行为,请停留在 6.22.1;已经更新的插件安装可以按英文
|
|
206
|
+
README "Trust model per install path" 一节的回退步骤操作。
|
|
207
|
+
|
|
208
|
+
- **受影响的是谁。** 以前直连 API 这一路不管 `ANTHROPIC_BASE_URL` 是什么,都用内置模型 ID 发往
|
|
209
|
+
`api.anthropic.com`,只有 `claude -p` 兜底会跟随它。现在直连这一路也用它,同时采用
|
|
210
|
+
`ANTHROPIC_DEFAULT_HAIKU_MODEL` / `ANTHROPIC_DEFAULT_SONNET_MODEL`。没设 `ANTHROPIC_BASE_URL`
|
|
211
|
+
时一切不变,即使设了这两个模型变量也一样。
|
|
212
|
+
- **插件用不了的值**(无法解析成 URL、不是 http 或 https、对非回环主机用明文 http、URL 里带凭据、
|
|
213
|
+
带查询串或片段)会跳过直连这一路:所有后台调用改走 `claude -p`(它自己也会读取
|
|
214
|
+
`ANTHROPIC_BASE_URL`),`claude-mem-lite doctor` 会说明原因。`doctor` 现在探测的是网关的主机和端口,
|
|
215
|
+
而不是 `api.anthropic.com`。
|
|
216
|
+
- **本版本还有:** 数据量大的存储不再每次提示都超时。数据库现在会有 SQLite 的查询规划统计,因此会多出
|
|
217
|
+
SQLite 自己的 `sqlite_stat1` / `sqlite_stat4` 表。另外新增默认关闭的开关
|
|
218
|
+
`CLAUDE_MEM_SEARCH_TELEMETRY=1`,用来记录 MCP 搜索以便给相关性打分。详见 CHANGELOG.md。
|
|
219
|
+
|
|
202
220
|
## 升级到 6.21.0
|
|
203
221
|
|
|
204
222
|
**名字不是纯 ASCII 的项目会换一个新标识,它们存下的内容会搬一次。** 没有 schema 版本变更:
|
|
@@ -402,8 +420,8 @@ v48"*。想继续用向量臂,请在**升级之前**锁定 `claude-mem-lite@5.
|
|
|
402
420
|
|
|
403
421
|
### MCP 工具
|
|
404
422
|
|
|
405
|
-
v2.34.0 起服务端只把一部分工具暴露给 `tools/list
|
|
406
|
-
**核心**
|
|
423
|
+
v2.34.0 起服务端只把一部分工具暴露给 `tools/list`。服务端定义 19 个工具,其中 9 个
|
|
424
|
+
**核心** 工具默认出现在列表里,`mem_search_feedback` 仅在启用搜索遥测时加入;另外 9 个 **隐藏** 工具仍然注册在 MCP 层(按名
|
|
407
425
|
`tools/call` 仍命中),只是不出现在列表响应里,以避免 Claude Code 会话启动时
|
|
408
426
|
多加载 9 份工具 schema。隐藏工具走下面表格的 CLI 入口。
|
|
409
427
|
|
|
@@ -411,11 +429,12 @@ v2.34.0 起服务端只把一部分工具暴露给 `tools/list`。当前是 18
|
|
|
411
429
|
`tool-schemas.mjs` 是唯一事实来源,`tests/tool-count-docs.test.mjs` 现在把两份
|
|
412
430
|
README 和 `docs/ARCHITECTURE.md` 都钉在它上面。)
|
|
413
431
|
|
|
414
|
-
**核心(
|
|
432
|
+
**核心(10 个定义;`mem_search_feedback` 需要启用搜索遥测)**
|
|
415
433
|
|
|
416
434
|
| 工具 | 描述 |
|
|
417
435
|
|------|------|
|
|
418
436
|
| `mem_search` | 基于 BM25 排名的 FTS5 全文搜索。支持按类型、项目、日期范围、重要度过滤。 |
|
|
437
|
+
| `mem_search_feedback` | 为已有 `mem_search` Search ID 的结果记录稀疏相关性标签。 |
|
|
419
438
|
| `mem_recent` | 显示最近的观察,按时间排序。快速查看最新活动。 |
|
|
420
439
|
| `mem_recall` | 召回与文件相关的观察。编辑文件前使用,回顾过去的修复和上下文。 |
|
|
421
440
|
| `mem_timeline` | 围绕锚点按时间顺序浏览观察。 |
|
|
@@ -811,7 +830,10 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
|
|
|
811
830
|
|------|------|--------|
|
|
812
831
|
| `CLAUDE_MEM_DIR` | 自定义数据目录。所有数据库与运行时文件均存储在此。 | `~/.claude-mem-lite/` |
|
|
813
832
|
| `CLAUDE_MEM_MODEL` | 后台 LLM 调用模型(Episode 提取、会话总结、调度)。可选 `haiku` 或 `sonnet`。 | `haiku` |
|
|
814
|
-
| `ANTHROPIC_API_KEY` | Anthropic API key。设置后所有后台 LLM 调用直连 Anthropic Messages API(带 prompt caching
|
|
833
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key。设置后所有后台 LLM 调用直连 Anthropic Messages API(带 prompt caching);设了 `ANTHROPIC_BASE_URL` 时改发往那个网关。优先级最高。 | _(未设 → CLI)_ |
|
|
834
|
+
| `ANTHROPIC_BASE_URL` | 由兼容 Anthropic 的网关(Azure AI Foundry、LiteLLM、Bedrock/Vertex 代理)提供模型时,直连 Messages API 使用的基础 URL。不要带 `/v1` 后缀,端点路径会自动拼上。`claude -p` 兜底读取同一个变量,所以一个值同时作用于两条路径。明文 `http://` 只接受回环主机,否则 API key 会以明文发送。设了但不可用的值会跳过直连这一路,后台调用改走 `claude -p`,doctor 会提示。 | `https://api.anthropic.com` |
|
|
835
|
+
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | `haiku` 档的模型 ID 或网关部署名,**仅在设了 `ANTHROPIC_BASE_URL` 时**用于直连 API。没有基础 URL 时只给 CLI 用,所以这个别名的 Bedrock/Vertex 写法不会被发往 `api.anthropic.com`。网关按部署名而不是 Anthropic 模型 ID 路由时(Azure Foundry 部署)使用。`claude -p` 兜底的 `--model haiku` 别名也经由它解析。 | 内置 `claude-haiku-4-5-…` |
|
|
836
|
+
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | 同上,用于 `sonnet` 档。 | 内置 `claude-sonnet-4-5-…` |
|
|
815
837
|
| `OPENROUTER_API_KEY` | OpenRouter API key(OpenAI 兼容)。当**未设** `ANTHROPIC_API_KEY` 时用于后台 LLM 调用;两者都未设则回退到 `claude -p` CLI。 | _(未设)_ |
|
|
816
838
|
| `OPENROUTER_MODEL` | 覆盖**所有**后台调用的 OpenRouter 模型 slug(如 `openai/gpt-4o-mini`、`qwen/qwen-2.5-72b-instruct`)。未设时按 `CLAUDE_MEM_MODEL` 分层映射到 `anthropic/claude-haiku-4.5`(haiku)或 `anthropic/claude-sonnet-4.5`(sonnet)。 | _(分层默认)_ |
|
|
817
839
|
| `CLAUDE_MEM_DEBUG` | 启用调试日志(设为 `1` 启用)。 | _(禁用)_ |
|
|
@@ -824,6 +846,12 @@ npm run benchmark:gate # CI 门控:指标回退超过 5% 容差时失败
|
|
|
824
846
|
| `MEM_NO_AUTO_ADOPT` | auto-adopt 全局关闭开关(v2.82.0+)。设为 `1` 在**所有**项目停止自动 adopt——不注入引导文本、不新建本地文件(`CLAUDE.local.md` 或 `.claude/rules/claude-mem-lite.md`),也不同步已有的 `CLAUDE.md` 或本地托管块(这些托管块会保留并继续加载,直到 `claude-mem-lite unadopt`)。项目级关闭走 `claude-mem-lite adopt --disable`(写 `<memdir>/.mem-no-auto-adopt` 哨兵,存活于 marker 删除)。 | _(禁用)_ |
|
|
825
847
|
| `MEM_NO_ADOPT_HINT` | 静音项目第一次得到 `CLAUDE.local.md`、或第一次以注入方式被引导时给你的一次性提示(后者建议运行 `/adopt`,在 `$HOME` 下除外;在这个文件会让 `AGENTS.md` 失效的项目里,还会说明 `AGENTS.md` 的情况和相应设置)、引导写进 `.claude/rules/claude-mem-lite.md` 时的一次性提示,SessionStart 给只有托管块的 `CLAUDE.md` 补上 `AGENTS.md` 导入时的一次性提示,以及当前项目未 adopt 时 SessionStart 追加的那一行 "Invited-memory 未启用…" 提示。v2.82.1 起任何安装路径每次 SessionStart 都自动 adopt,所以该提示一般只在你显式 opt out(`MEM_NO_AUTO_ADOPT=1` 或 `claude-mem-lite adopt --disable`)的项目才会出现。 | _(禁用)_ |
|
|
826
848
|
| `CLAUDE_MEM_RULES_STEERING` | 设为 `1` 时,在 `CLAUDE.local.md` 会让 Claude Code 不再读项目 `AGENTS.md` 的仓库里,自动 adopt 把托管块写进 `.claude/rules/claude-mem-lite.md`(排除在 git 之外)而不是注入;子代理能看到它,`AGENTS.md` 也继续加载。开着时写下的文件之后一直是引导渠道。默认关闭:预先登记的 A/B 没有显示出达到标准的、胜过注入的效果(`docs/audits/20261006-d212-ab.md`)。 | _(注入)_ |
|
|
849
|
+
| `CLAUDE_MEM_SEARCH_TELEMETRY` | 设为 `1` 时记录 MCP `mem_search` 的查询和结果,输出 Search ID 供可选的 `mem_search_feedback` 打分,并给每次搜索标上当时运行的 claude-mem-lite 版本。 | _(关闭)_ |
|
|
850
|
+
|
|
851
|
+
搜索遥测记录会一直保留,直到你删除。用 `claude-mem-lite stats --search-telemetry` 查看数量;在所配置的
|
|
852
|
+
SQLite 数据库(通常是 `~/.claude-mem-lite/claude-mem-lite.db`)里执行
|
|
853
|
+
`DELETE FROM search_results; DELETE FROM search_runs; VACUUM;` 清除。数据库旁边删除前留下的 `.bak`
|
|
854
|
+
快照,在被删掉之前仍保有之前的内容。
|
|
827
855
|
|
|
828
856
|
## 许可证
|
|
829
857
|
|
package/cli/common.mjs
CHANGED
|
@@ -374,6 +374,7 @@ export const KNOWN_CLI_FLAGS = new Set([
|
|
|
374
374
|
'run',
|
|
375
375
|
'run-all',
|
|
376
376
|
'scope',
|
|
377
|
+
'search-telemetry',
|
|
377
378
|
'session-audit',
|
|
378
379
|
'sidechain',
|
|
379
380
|
'since',
|
|
@@ -431,6 +432,7 @@ export const COMMAND_SCOPED_FLAGS = new Map([
|
|
|
431
432
|
['chars', 'context'],
|
|
432
433
|
['digest', 'verify-apply'],
|
|
433
434
|
['print-project', 'verify-apply'],
|
|
435
|
+
['search-telemetry', 'stats'],
|
|
434
436
|
['undo', 'verify-apply'],
|
|
435
437
|
]);
|
|
436
438
|
|
package/haiku-client.mjs
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
// Provider priority: ANTHROPIC_API_KEY (direct Anthropic API) →
|
|
4
4
|
// OPENROUTER_API_KEY (OpenRouter, OpenAI-compatible) → claude CLI fallback
|
|
5
5
|
// Model configurable via CLAUDE_MEM_MODEL (haiku|sonnet); OpenRouter slug
|
|
6
|
-
// overridable via OPENROUTER_MODEL
|
|
6
|
+
// overridable via OPENROUTER_MODEL. The direct-API leg honours
|
|
7
|
+
// ANTHROPIC_BASE_URL (no /v1 suffix — the path is appended) and, when that base
|
|
8
|
+
// URL is set and usable, per-tier deployment names via
|
|
9
|
+
// ANTHROPIC_DEFAULT_{HAIKU,SONNET}_MODEL — the same vars the `claude` CLI leg
|
|
10
|
+
// resolves its --model aliases through, so one env set points both transports at
|
|
11
|
+
// a gateway (Azure AI Foundry, LiteLLM, Bedrock proxies). Without a base URL the
|
|
12
|
+
// tier vars stay CLI-only: they are also the Bedrock/Vertex aliases and must not
|
|
13
|
+
// leak to api.anthropic.com. Unset base URL → public Anthropic API, unchanged.
|
|
7
14
|
|
|
8
15
|
import { execFileSync, spawn } from 'child_process';
|
|
9
16
|
import { mkdirSync } from 'fs';
|
|
@@ -14,6 +21,7 @@ import { debugLog, debugCatch, parseJsonFromLLM } from './utils.mjs';
|
|
|
14
21
|
import { DB_DIR } from './schema.mjs';
|
|
15
22
|
import { resolveRuntimeDir } from './lib/resolve-data-dir.mjs';
|
|
16
23
|
import { httpConnectProxyFor, postViaConnectProxy } from './lib/proxy-fetch.mjs';
|
|
24
|
+
import { resolveAnthropicBaseUrl } from './lib/anthropic-base-url.mjs';
|
|
17
25
|
|
|
18
26
|
/**
|
|
19
27
|
* cwd for every `claude -p` spawn. R10 P2-13.
|
|
@@ -48,6 +56,34 @@ const MODEL_MAP = {
|
|
|
48
56
|
sonnet: 'claude-sonnet-4-5-20250929',
|
|
49
57
|
};
|
|
50
58
|
|
|
59
|
+
// Claude Code's tier-alias override vars, honored on the direct-API leg too:
|
|
60
|
+
// gateway deployments route on the DEPLOYMENT name (Azure Foundry rejects the
|
|
61
|
+
// Anthropic model ID when it differs), and one env set then covers both the
|
|
62
|
+
// keyed API call and the `claude -p` fallback.
|
|
63
|
+
const TIER_MODEL_ENV = {
|
|
64
|
+
haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
65
|
+
sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Model ID the direct Messages API should send for a tier.
|
|
70
|
+
*
|
|
71
|
+
* Scoped to a usable ANTHROPIC_BASE_URL on purpose: these vars are ALSO the
|
|
72
|
+
* Claude Code aliases on Bedrock/Vertex, where the value is a provider-specific
|
|
73
|
+
* ID. Someone who set them for the CLI and has an ANTHROPIC_API_KEY would
|
|
74
|
+
* otherwise send that ID straight to api.anthropic.com (a 404 + CLI fallback on
|
|
75
|
+
* every call), and pointing the haiku alias at a bigger model would silently
|
|
76
|
+
* change model and cost. No base URL → the built-in Anthropic ID, as before.
|
|
77
|
+
* @param {'haiku'|'sonnet'} tier
|
|
78
|
+
* @param {ReturnType<typeof resolveAnthropicBaseUrl>} [base] the resolved base URL;
|
|
79
|
+
* callModelAPI passes the one it also builds the request URL from
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
function apiModelId(tier, base = resolveAnthropicBaseUrl()) {
|
|
83
|
+
if (!base.configured || base.error) return MODEL_MAP[tier];
|
|
84
|
+
return (process.env[TIER_MODEL_ENV[tier]] || '').trim() || MODEL_MAP[tier];
|
|
85
|
+
}
|
|
86
|
+
|
|
51
87
|
// Every background LLM call here is fixed-schema extraction / classification
|
|
52
88
|
// (episode→JSON, type/merge classification, synonym + metadata extraction) whose
|
|
53
89
|
// output is consumed deterministically (JSON.parse, MinHash dedup). Pin temperature
|
|
@@ -81,12 +117,13 @@ export const BG_LLM_TIMEOUT_MS = 45000;
|
|
|
81
117
|
/**
|
|
82
118
|
* Resolve the LLM model to use for background calls.
|
|
83
119
|
* Reads CLAUDE_MEM_MODEL env var, defaults to 'haiku'.
|
|
84
|
-
* @returns {{ cli: string, api: string }} CLI name and API model ID
|
|
120
|
+
* @returns {{ cli: string, api: string }} CLI name and API model ID (tier
|
|
121
|
+
* deployment-name override when set, else the built-in Anthropic ID)
|
|
85
122
|
*/
|
|
86
123
|
export function resolveModel() {
|
|
87
124
|
const raw = (process.env.CLAUDE_MEM_MODEL || 'haiku').toLowerCase().trim();
|
|
88
125
|
const cli = MODEL_MAP[raw] ? raw : 'haiku';
|
|
89
|
-
const api =
|
|
126
|
+
const api = apiModelId(cli);
|
|
90
127
|
return { cli, api };
|
|
91
128
|
}
|
|
92
129
|
|
|
@@ -450,22 +487,52 @@ export async function callModelJSONAsync(
|
|
|
450
487
|
return res?.text ? parseJsonFromLLM(res.text) : null;
|
|
451
488
|
}
|
|
452
489
|
|
|
490
|
+
// Models that refused `temperature` with a 400 (see the retry in callModelAPI).
|
|
491
|
+
// One name after the first rejection, so the cost is one failed request per
|
|
492
|
+
// model per process rather than one per call.
|
|
493
|
+
const _temperatureDeprecated = new Set();
|
|
494
|
+
|
|
495
|
+
/** @internal test hook — module-level compat state must not leak across cases. */
|
|
496
|
+
export function _resetTemperatureCompat() {
|
|
497
|
+
_temperatureDeprecated.clear();
|
|
498
|
+
}
|
|
499
|
+
|
|
453
500
|
async function callModelAPI(prompt, model, { timeout, maxTokens, temperature = DEFAULT_LLM_TEMPERATURE }) {
|
|
454
501
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
455
502
|
if (!apiKey) return null;
|
|
456
503
|
|
|
457
|
-
|
|
504
|
+
// Read the base URL once: the request URL and the tier model must come from the
|
|
505
|
+
// same verdict, and a set-but-unusable value must not quietly move the key to
|
|
506
|
+
// the public endpoint (the cross-origin hop the redirect guard exists to stop).
|
|
507
|
+
// Skip the direct leg; the CLI fallback reads ANTHROPIC_BASE_URL itself.
|
|
508
|
+
const base = resolveAnthropicBaseUrl();
|
|
509
|
+
if (base.configured && base.error) {
|
|
510
|
+
debugLog('WARN', `${model}-api`, `ANTHROPIC_BASE_URL unusable (${base.error}); skipping direct API call`);
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const modelId = apiModelId(model, base);
|
|
458
515
|
const controller = new AbortController();
|
|
459
516
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
517
|
+
// One deadline for the whole call. The AbortController bounds the fetch path
|
|
518
|
+
// across the temperature retry; the proxy tunnel takes what is LEFT of the
|
|
519
|
+
// deadline per send, so a retry cannot buy a second full timeout (a 1000ms
|
|
520
|
+
// budget that took 1633ms on the tunnel path). (pre-tag review on #33)
|
|
521
|
+
const deadline = Date.now() + timeout;
|
|
460
522
|
|
|
461
523
|
try {
|
|
462
524
|
const { system, user } = splitPrompt(prompt);
|
|
463
525
|
const body = {
|
|
464
526
|
model: modelId,
|
|
465
527
|
max_tokens: maxTokens,
|
|
466
|
-
temperature,
|
|
467
528
|
messages: [{ role: 'user', content: user }],
|
|
468
529
|
};
|
|
530
|
+
// The 0-pin below is for JSON determinism, but newer models deprecate the
|
|
531
|
+
// field outright (Azure Foundry's claude-sonnet-5 answers 400
|
|
532
|
+
// "`temperature` is deprecated for this model."). Keep sending it until a
|
|
533
|
+
// model refuses, then drop it for that model only - the retry at the bottom
|
|
534
|
+
// pays for itself once.
|
|
535
|
+
if (!_temperatureDeprecated.has(modelId)) body.temperature = temperature;
|
|
469
536
|
// System slot is constant per call type (instructions, schema, type taxonomy)
|
|
470
537
|
// — mark it cache_control:ephemeral so repeated calls within the 5-min cache
|
|
471
538
|
// window pay the cached-input rate (~0.10× base). Sub-1024-token systems still
|
|
@@ -480,25 +547,60 @@ async function callModelAPI(prompt, model, { timeout, maxTokens, temperature = D
|
|
|
480
547
|
// fetch — a silent outage behind a proxy, and one the new doctor check would
|
|
481
548
|
// have certified as healthy because it probes the hop this code was ASSUMED
|
|
482
549
|
// to use. (pre-tag review SHOULD-FIX 3)
|
|
483
|
-
const apiUrl =
|
|
550
|
+
const apiUrl = `${base.url}/v1/messages`;
|
|
484
551
|
const apiHeaders = {
|
|
485
552
|
'Content-Type': 'application/json',
|
|
486
553
|
'x-api-key': apiKey,
|
|
487
554
|
'anthropic-version': '2023-06-01',
|
|
488
555
|
};
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
556
|
+
const send = (payload) => {
|
|
557
|
+
const json = JSON.stringify(payload);
|
|
558
|
+
const apiProxy = httpConnectProxyFor(apiUrl);
|
|
559
|
+
return apiProxy
|
|
560
|
+
? postViaConnectProxy(apiProxy, apiUrl, {
|
|
561
|
+
headers: apiHeaders,
|
|
562
|
+
body: json,
|
|
563
|
+
timeout: Math.max(1, deadline - Date.now()),
|
|
564
|
+
})
|
|
565
|
+
: fetch(apiUrl, {
|
|
566
|
+
method: 'POST',
|
|
567
|
+
headers: apiHeaders,
|
|
568
|
+
body: json,
|
|
569
|
+
signal: controller.signal,
|
|
570
|
+
// A gateway must not be able to walk the x-api-key to another origin
|
|
571
|
+
// via a redirect. The tunnel path already surfaces a 3xx without
|
|
572
|
+
// following it; reject here so the native path behaves the same.
|
|
573
|
+
redirect: 'error',
|
|
574
|
+
});
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
let res = await send(body);
|
|
578
|
+
|
|
579
|
+
// Temperature-deprecation compat. Unlike the claude-CLI flag retry, which
|
|
580
|
+
// caches the negative only after the retry succeeds, this pins the model the
|
|
581
|
+
// moment the 400 matches: the 400 itself proves the model refuses the field,
|
|
582
|
+
// so every later call omits it from the start. The body must name the
|
|
583
|
+
// BACKTICKED field — matching bare "temperature" also catches unrelated 400s
|
|
584
|
+
// that merely mention it, and a false match costs a request plus the 0-pin
|
|
585
|
+
// for that model for the rest of the process. A 400 that does not name the
|
|
586
|
+
// field keeps the old single-attempt path.
|
|
587
|
+
if (res.status === 400 && body.temperature !== undefined) {
|
|
588
|
+
let detail = '';
|
|
589
|
+
try {
|
|
590
|
+
detail = (await res.text?.()) || '';
|
|
591
|
+
} catch {
|
|
592
|
+
/* body already gone - treat as a non-matching 400 */
|
|
593
|
+
}
|
|
594
|
+
if (/`temperature`/i.test(detail) && /deprecat|unsupported|not support/i.test(detail)) {
|
|
595
|
+
_temperatureDeprecated.add(modelId);
|
|
596
|
+
// Only spend the second request if real budget is left; otherwise the
|
|
597
|
+
// gateway bills a POST the client aborts before reading it.
|
|
598
|
+
if (deadline - Date.now() >= RETRY_MIN_BUDGET_MS) {
|
|
599
|
+
delete body.temperature;
|
|
600
|
+
res = await send(body);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
502
604
|
|
|
503
605
|
if (!res.ok) {
|
|
504
606
|
debugLog('WARN', `${model}-api`, `HTTP ${res.status}`);
|
|
@@ -606,8 +708,10 @@ const FLAG_TOKEN = /no-session-persistence/;
|
|
|
606
708
|
const PARSE_REJECTION =
|
|
607
709
|
/(unknown|unrecognized|unsupported|invalid|unexpected)[^\n]{0,40}(option|argument|flag|switch)/i;
|
|
608
710
|
|
|
609
|
-
// Below this many ms left, a retry
|
|
610
|
-
//
|
|
711
|
+
// Below this many ms left, a retry is worse than returning the original failure:
|
|
712
|
+
// the CLI retry can only spawn a process and immediately kill it, and the direct
|
|
713
|
+
// API's temperature retry would send a request (billed by the gateway) whose
|
|
714
|
+
// reply nobody waits for.
|
|
611
715
|
const RETRY_MIN_BUDGET_MS = 500;
|
|
612
716
|
|
|
613
717
|
export function _isUnknownFlagError(diagnostic) {
|
package/hook.mjs
CHANGED
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
} from './hook-episode.mjs';
|
|
77
77
|
// CODE_DIR, not DB_DIR: the schema-skew notice asks which CODE homes exist, and those are
|
|
78
78
|
// always homedir-rooted even when CLAUDE_MEM_DIR relocates the data.
|
|
79
|
-
import { DB_DIR, DB_PATH, CODE_DIR } from './schema.mjs';
|
|
79
|
+
import { DB_DIR, DB_PATH, CODE_DIR, refreshPlannerStats } from './schema.mjs';
|
|
80
80
|
import { cleanupClaudeMdLegacyBlock, buildSessionContextLines } from './hook-context.mjs';
|
|
81
81
|
import { entry as preCompactEntry } from './hook-precompact.mjs';
|
|
82
82
|
import {
|
|
@@ -2237,6 +2237,10 @@ function runSessionStartAutoMaintain(db, project) {
|
|
|
2237
2237
|
debugCatch(e, 'auto-maintain-handoff-gc');
|
|
2238
2238
|
}
|
|
2239
2239
|
|
|
2240
|
+
// Planner statistics, the "once a day" half of SQLite's recommendation (#41). Last, so
|
|
2241
|
+
// the purge/dedup above have already moved the row counts it compares. Never throws.
|
|
2242
|
+
refreshPlannerStats(db);
|
|
2243
|
+
|
|
2240
2244
|
// Mark maintenance as done (24h gate) — even though compression runs in background
|
|
2241
2245
|
writeFileSync(maintainFile, JSON.stringify({ epoch: Date.now() }));
|
|
2242
2246
|
// Weekly summary grouping runs in background to avoid blocking SessionStart
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// lib/anthropic-base-url.mjs - one resolver for ANTHROPIC_BASE_URL.
|
|
2
|
+
//
|
|
3
|
+
// Two callers need the same answer and used to derive it differently:
|
|
4
|
+
// haiku-client's callModelAPI builds the request URL from it, and
|
|
5
|
+
// lib/llm-provider-probe.mjs picks the host + port doctor probes. When the two
|
|
6
|
+
// disagree, doctor certifies a hop the product never uses - the false-green
|
|
7
|
+
// shape this seam exists to remove. So the trim, the URL parse, and the
|
|
8
|
+
// validity verdict live here and both sites read the same result.
|
|
9
|
+
//
|
|
10
|
+
// ANTHROPIC_BASE_URL is the Claude Code / Anthropic SDK convention: an origin
|
|
11
|
+
// (+ optional /anthropic path on Azure Foundry) with NO /v1 suffix - callers
|
|
12
|
+
// append the endpoint path. Unset or blank keeps the public API, which is the
|
|
13
|
+
// behaviour before the gateway override existed.
|
|
14
|
+
|
|
15
|
+
import net from 'node:net';
|
|
16
|
+
|
|
17
|
+
const DEFAULT_ANTHROPIC_BASE_URL = 'https://api.anthropic.com';
|
|
18
|
+
|
|
19
|
+
// Plain http:// is allowed only to loopback. Anywhere else it puts the
|
|
20
|
+
// x-api-key on the wire unencrypted (and prompts in the clear), so that value
|
|
21
|
+
// is refused rather than silently honoured.
|
|
22
|
+
function isLoopbackHost(host) {
|
|
23
|
+
return (
|
|
24
|
+
host === 'localhost' ||
|
|
25
|
+
host.endsWith('.localhost') ||
|
|
26
|
+
host === '::1' ||
|
|
27
|
+
host === '[::1]' ||
|
|
28
|
+
// A real dotted-quad loopback, not a DNS name that merely starts "127."
|
|
29
|
+
// ("127.attacker.example" resolves wherever its owner says).
|
|
30
|
+
(net.isIP(host) === 4 && host.startsWith('127.'))
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the configured base URL into a usable origin + path.
|
|
36
|
+
*
|
|
37
|
+
* Never throws and always returns a well-formed `url` a caller can append
|
|
38
|
+
* `/v1/messages` to: blank / unset keeps the public default, and a value that is
|
|
39
|
+
* set but unusable (unparseable, non-http(s) scheme, userinfo, a query string or
|
|
40
|
+
* fragment, or plain http to a non-loopback host) also returns the default so
|
|
41
|
+
* callers always get a parseable value. `error` names why; the direct API leg
|
|
42
|
+
* ignores the fallback and skips itself when a configured value is unusable, and
|
|
43
|
+
* doctor surfaces the same reason instead of probing a hop nothing uses.
|
|
44
|
+
*
|
|
45
|
+
* @returns {{url: string, configured: boolean, error: string|null}}
|
|
46
|
+
* url normalized origin+path, no trailing slash
|
|
47
|
+
* configured ANTHROPIC_BASE_URL was set to a non-blank value
|
|
48
|
+
* error why a configured value is unusable, or null when it is fine
|
|
49
|
+
*/
|
|
50
|
+
export function resolveAnthropicBaseUrl() {
|
|
51
|
+
const raw = (process.env.ANTHROPIC_BASE_URL || '').trim();
|
|
52
|
+
if (!raw) return { url: DEFAULT_ANTHROPIC_BASE_URL, configured: false, error: null };
|
|
53
|
+
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = new URL(raw);
|
|
57
|
+
} catch {
|
|
58
|
+
return {
|
|
59
|
+
url: DEFAULT_ANTHROPIC_BASE_URL,
|
|
60
|
+
configured: true,
|
|
61
|
+
error: 'ANTHROPIC_BASE_URL is not a valid URL',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (parsed.username || parsed.password) {
|
|
65
|
+
// fetch() refuses to build a request with credentials in the URL, so every
|
|
66
|
+
// call would fall back to the CLI while doctor reported the host reachable.
|
|
67
|
+
return {
|
|
68
|
+
url: DEFAULT_ANTHROPIC_BASE_URL,
|
|
69
|
+
configured: true,
|
|
70
|
+
error: 'ANTHROPIC_BASE_URL must not include userinfo (user:pass@)',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
74
|
+
return {
|
|
75
|
+
url: DEFAULT_ANTHROPIC_BASE_URL,
|
|
76
|
+
configured: true,
|
|
77
|
+
error: `ANTHROPIC_BASE_URL must use http or https (got ${parsed.protocol})`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Raw string, not URL.search/hash: a bare trailing "?" or "#" leaves both
|
|
81
|
+
// empty while still swallowing the appended path into the query.
|
|
82
|
+
if (/[?#]/.test(raw)) {
|
|
83
|
+
return {
|
|
84
|
+
url: DEFAULT_ANTHROPIC_BASE_URL,
|
|
85
|
+
configured: true,
|
|
86
|
+
error: 'ANTHROPIC_BASE_URL must not include a query string or fragment',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {
|
|
90
|
+
return {
|
|
91
|
+
url: DEFAULT_ANTHROPIC_BASE_URL,
|
|
92
|
+
configured: true,
|
|
93
|
+
error: `ANTHROPIC_BASE_URL uses plain http to a non-loopback host (${parsed.hostname}); the direct API leg will not send the API key there unencrypted, but the claude CLI fallback reads the same variable and still does`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { url: raw.replace(/\/+$/, ''), configured: true, error: null };
|
|
97
|
+
}
|
package/lib/delete-core.mjs
CHANGED
|
@@ -86,6 +86,11 @@ export function deleteObservations(db, ids, { snapshotTag = 'pre-delete' } = {})
|
|
|
86
86
|
WHERE superseded_by IN (${placeholders}) AND id NOT IN (${placeholders})`,
|
|
87
87
|
)
|
|
88
88
|
.run(...ids, ...ids).changes;
|
|
89
|
+
if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'search_results'").get()) {
|
|
90
|
+
db.prepare(
|
|
91
|
+
`UPDATE search_results SET snapshot_label = NULL WHERE source = 'obs' AND result_id IN (${placeholders})`,
|
|
92
|
+
).run(...ids);
|
|
93
|
+
}
|
|
89
94
|
// Execute deletion (FTS5 cleanup handled by the observations_ad trigger).
|
|
90
95
|
const deleted = db.prepare(`DELETE FROM observations WHERE id IN (${placeholders})`).run(...ids);
|
|
91
96
|
return { changes: deleted.changes, recovered, restoredSuperseded };
|