Skip to content

Commit 1335f89

Browse files
committed
chore(release): stabilize v2.0.138
1 parent 452871c commit 1335f89

18 files changed

Lines changed: 448 additions & 41 deletions

.env.example

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ API_KEY=
55
# Directory for persisted JSON state and logs (Docker 推荐用 /data)
66
DATA_DIR=
77

8+
# In-memory duplicate response cache. Max bytes accepts b/k/kb/kib/m/mb/mib/g/gb/gib.
9+
# RESPONSE_CACHE_ENABLED=1
10+
# RESPONSE_CACHE_MAX_BYTES=16m
11+
812
# ========== Codeium Auth ==========
913
# Option 1: Direct API key from Windsurf
1014
CODEIUM_API_KEY=
@@ -61,10 +65,12 @@ LS_PORT=42100
6165
# Native Cascade tool bridge. Default off because Cascade executes native
6266
# Read/Bash-style tools in the remote Windsurf workspace, while most clients
6367
# expect local execution.
64-
# v2.0.123 production canary scope is intentionally narrow: without an
68+
# v2.0.138 production canary scope is intentionally narrow: without an
6569
# explicit tool allowlist, only Bash / shell_command / run_command can route
6670
# through the native bridge. Read/Grep/Glob/WebSearch/WebFetch are protocol
67-
# matrix work, not production defaults.
71+
# matrix work, not production defaults. For local IDE tools (Claude Code,
72+
# Cline, Codex, opencode), keep the default emulation path unless you are
73+
# explicitly testing remote execution semantics.
6874
# WINDSURFAPI_NATIVE_TOOL_BRIDGE=all_mapped
6975
# Optional gray gates for real testing:
7076
# WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS=Bash

.github/workflows/ci.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ jobs:
3434
done
3535
3636
test:
37-
name: Unit tests
37+
name: Unit tests shard ${{ matrix.shard }}
3838
runs-on: ubuntu-latest
39-
timeout-minutes: 10
39+
timeout-minutes: 20
40+
strategy:
41+
fail-fast: false
42+
matrix:
43+
shard: [0, 1, 2, 3]
4044
steps:
4145
- uses: actions/checkout@v4
4246

@@ -46,4 +50,4 @@ jobs:
4650
node-version: '20'
4751

4852
- name: Run tests
49-
run: npm run test:release
53+
run: npm run test:shard -- ${{ matrix.shard }} 4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ scripts/*
1111
!scripts/lsp-capacity-matrix.mjs
1212
!scripts/web-search-direct-probe.mjs
1313
!scripts/secret-scan.mjs
14+
!scripts/run-test-shard.mjs
1415
src/get-token.js
1516
src/test-cascade.js
1617
src/runtime-config.json

README.en.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,11 @@ In your client's settings for **Custom OpenAI Compatible**:
281281
| `LS_PREWARM_DEFAULT` | `1` | Set to `0` to skip startup default-LS prewarm and start LS lazily on first request. Useful for low-memory, all-proxy pools. |
282282
| `LS_PREWARM_PROXIES` | `0` | Set to `1` to prewarm all proxy LS instances on startup. Scheduled probes and predictive prewarm only reuse idle resident LS instances. |
283283
| `LS_PREWARM_ON_ACCOUNT_ADD` | `0` | Set to `1` to prewarm LS immediately after dashboard/import/OAuth account add. Default avoids memory spikes during bulk import. |
284-
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE` | empty | `all_mapped` enables native bridge only when every tool is in the Read/Bash/Grep/Glob semantic families; `1` enables partition mode for mixed toolsets. WebSearch/WebFetch stay on prompt emulation unless explicitly allowlisted. |
285-
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS` | Read/Bash/Grep/Glob families | Tool allowlist for native bridge. Defaults include aliases such as `read_file`, `view_file`, `shell_command`, `run_command`, `grep_search_v2`, and `find`; excludes WebSearch/WebFetch. |
284+
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE` | empty | Lab/remote-execution opt-in only. `all_mapped` enables native bridge only when every declared function tool is allowlisted and mappable; `1` enables partition mode for mapped subsets. Do not treat this as a general fix for local IDE tools. |
285+
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS` | `Bash/shell_command/run_command` families | Tool allowlist for native bridge. The default intentionally contains only the command path. `Read` / `Grep` / `Glob` / `WebSearch` / `WebFetch` require explicit allowlisting plus model/account/API-key gates and are still protocol-lab scope, not production defaults. |
286286
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_MODELS` / `PROVIDERS` / `ROUTES` / `CALLERS` / `ACCOUNTS` / `API_KEYS` | empty | Optional native-bridge gray gates. Empty means unrestricted; when set, the request must match. `ACCOUNTS` accepts upstream account id/email. `API_KEYS` matches caller API keys without passing plaintext tokens into chat logic. |
287287
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF` | empty | Set to `1` to force native bridge off. |
288-
| `WINDSURFAPI_SPECIAL_AGENT_BACKEND` | empty | Optional special-agent backend. Set `devin-cli` to route `swe-1.6`, `swe-1.6-fast`, `adaptive`, and `arena-*` through Devin CLI instead of direct Cascade. |
288+
| `WINDSURFAPI_SPECIAL_AGENT_BACKEND` | empty | Optional lab-only special-agent backend. Set `devin-cli` to test `swe-1.6`, `swe-1.6-fast`, `adaptive`, and `arena-*` through Devin CLI instead of direct Cascade. This is not a normal catalog-model fix. |
289289
| `DEVIN_CLI_PATH` | `devin` | Devin CLI executable path. Docker/macOS deployments must install or mount it themselves. |
290290
| `DEVIN_CLI_MODE` | `print` | `print` uses conservative `devin -p`; `acp` is an experimental ACP stdio backend using upstream Windsurf account-pool apiKeys. |
291291
| `DEVIN_MAX_PROCS` | `1` | Maximum concurrent Devin CLI processes. |

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,11 @@ curl http://localhost:3003/v1/messages \
276276
| `LS_PREWARM_DEFAULT` | `1` | 设为 `0` 可跳过启动时 default LS 预热,低内存/全 proxy 池改为首个真实请求再懒启动 |
277277
| `LS_PREWARM_PROXIES` | `0` | 设为 `1` 才在启动时预热所有 proxy LS;默认按需启动。后台 scheduled probe / 预测 prewarm 只复用空闲常驻 LS,不会为了探测新开/等待/驱逐 LS |
278278
| `LS_PREWARM_ON_ACCOUNT_ADD` | `0` | 设为 `1` 才在 Dashboard/批量导入/OAuth 添加账号后立即预热对应 LS;默认避免批量录入打爆内存 |
279-
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE` || `all_mapped` 仅在已 allowlist 的工具全部可映射时走 native bridge;`1` 为混合工具 partition 模式。WebSearch/WebFetch 默认仍走 prompt emulation,需显式加入工具 allowlist |
280-
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS` | `Bash/shell_command/run_command` 语义族 | native bridge 工具 allowlist。默认只包含成熟的 Bash/run_command 路径;Read/Grep/Glob 和 WebSearch/WebFetch 需显式加入 allowlist 后再用灰度账号/API key 实测 |
279+
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE` || 仅用于 lab/远程执行灰度。`all_mapped` 仅在已 allowlist 的工具全部可映射时走 native bridge;`1` 为混合工具 partition 模式。不要把它当成本地 IDE 工具调用的通用修复 |
280+
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS` | `Bash/shell_command/run_command` 语义族 | native bridge 工具 allowlist。默认只包含成熟的 Bash/run_command 路径;Read/Grep/Glob 和 WebSearch/WebFetch 必须显式加入 allowlist,再配合模型/账号/API key gate 小流量实测,仍不是生产默认 |
281281
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_MODELS` / `PROVIDERS` / `ROUTES` / `CALLERS` / `ACCOUNTS` / `API_KEYS` || native bridge 灰度门。为空表示不限;设置后必须匹配才启用。`ACCOUNTS` 可填账号 id/email,`API_KEYS` 匹配调用方 API key 但不会把明文 key 传进 chat 逻辑 |
282282
| `WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF` || 设为 `1` 强制关闭 native tool bridge,优先级高于上面的开关 |
283-
| `WINDSURFAPI_SPECIAL_AGENT_BACKEND` || 可选 special-agent 后端。设为 `devin-cli` 后,`swe-1.6` / `swe-1.6-fast` / `adaptive` / `arena-*` 不再走 direct Cascade,而是走 Devin CLI PoC |
283+
| `WINDSURFAPI_SPECIAL_AGENT_BACKEND` || 可选 lab-only special-agent 后端。设为 `devin-cli` 后,`swe-1.6` / `swe-1.6-fast` / `adaptive` / `arena-*` 不再走 direct Cascade,而是走 Devin CLI PoC;这不是普通 catalog 模型修复 |
284284
| `DEVIN_CLI_PATH` | `devin` | Devin CLI 可执行文件路径;Docker/macOS 都需要自己安装或挂载,不是基础镜像硬依赖 |
285285
| `DEVIN_CLI_MODE` | `print` | `print``devin -p` 保守模式;`acp` 为实验 ACP stdio 后端,使用账号池上游 Windsurf apiKey 认证,默认不全量启用 |
286286
| `DEVIN_MAX_PROCS` | `1` | Devin CLI 最大并发进程数,避免 special-agent 路径把内存打爆 |

docs/native-bridge-protocol-notes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ allowlist. To test them, set
1414
`WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS=Read,Bash,Grep,Glob` or a narrower list
1515
for a gated account/API key/model.
1616

17+
For Claude Code, Cline, Codex, Cursor-style local agents, the safe production
18+
default is still prompt/tool emulation. Native bridge means Cascade may execute
19+
the built-in tool in the remote Windsurf workspace, not on the caller's local
20+
machine. Do not recommend `all_mapped` as a general fix for "tools not called";
21+
use it only for narrow canaries with explicit model/account/API-key gates.
22+
1723
Do not add `WebSearch` / `WebFetch` to a production allowlist yet. v2.0.126
1824
confirmed their tool-config fields and subconfig enums, but live LS canaries
1925
still return a `permission_denied` Cascade error step before any web oneof is
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# v2.0.138 - release gate, dashboard pagination, and memory guardrails
2+
3+
## What changed
4+
5+
- CI now runs the full top-level test suite through deterministic shards instead of reusing the bounded release gate.
6+
- The release gate remains bounded, but now covers the files touched by this stabilization pass: response cache, dashboard syntax, native bridge docs, shard runner, proto trace, secret scan, release workflow, and version metadata.
7+
- Modern dashboard proxy-account and abnormal-account tables now use paged account summaries instead of loading hundreds or thousands of rows in one request. The experimental sketch skin now uses lightweight summary rows for those two panels; its main account table still uses the full account payload because the inline detail editor depends on full fields.
8+
- Native bridge documentation now states the safe default more explicitly: command tools remain the only mature production default; Read, Grep, Glob, WebSearch, and WebFetch stay protocol-lab gated until trace evidence is complete.
9+
- SWE-1.6 is documented as a special-agent / ACP route, not a normal catalog-model fix.
10+
- Response cache now has a byte budget (`RESPONSE_CACHE_MAX_BYTES` / `WINDSURFAPI_RESPONSE_CACHE_MAX_BYTES`, default 16 MiB). Values accept bytes or units such as `16m` / `1g`. Oversized entries are skipped, and old entries are evicted when total cached response bytes exceed the budget.
11+
12+
## Not changed
13+
14+
- Read `type=14 / field=19` reverse engineering is still not declared complete.
15+
- WebFetch/WebSearch native LS executor support is still not production-open.
16+
- SSE drain/backpressure handling remains a separate follow-up because it touches the streaming hot path.
17+
18+
## Validation
19+
20+
- `node --test test/cache.test.js`
21+
- `node --test test/dashboard-syntax.test.js`
22+
- `node --test test/native-bridge-docs.test.js`
23+
- `node --test test/test-shard-script.test.js test/release-workflow.test.js`
24+
- `npm.cmd run test:release`

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
22
"name": "windsurf-api",
3-
"version": "2.0.137",
3+
"version": "2.0.138",
44
"description": "Windsurf to OpenAI + Anthropic compatible API proxy. Turns Windsurf's 107 AI models (Claude, GPT, Gemini, DeepSeek, Grok, Qwen, Kimi, GLM, SWE) into dual-protocol API endpoints. Zero npm deps.",
55
"type": "module",
66
"main": "src/index.js",
77
"scripts": {
88
"start": "node src/index.js",
99
"dev": "node --watch src/index.js",
1010
"test": "node --test --test-force-exit test/*.test.js",
11-
"test:release": "node --test --test-force-exit test/dashboard-api.test.js test/proto-trace.test.js test/secret-scan.test.js test/release-workflow.test.js test/version.test.js",
11+
"test:shard": "node scripts/run-test-shard.mjs",
12+
"test:release": "node --test --test-force-exit test/cache.test.js test/dashboard-api.test.js test/dashboard-syntax.test.js test/native-bridge-docs.test.js test/proto-trace.test.js test/secret-scan.test.js test/test-shard-script.test.js test/release-workflow.test.js test/version.test.js",
1213
"smoke:native-bridge": "node scripts/native-bridge-smoke.mjs",
1314
"smoke:special-agent": "node scripts/special-agent-smoke.mjs",
1415
"smoke:lsp-matrix": "node scripts/lsp-capacity-matrix.mjs",

scripts/run-test-shard.mjs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env node
2+
import { spawn } from 'node:child_process';
3+
import { readdirSync } from 'node:fs';
4+
import { resolve, join } from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
7+
const DEFAULT_TIMEOUT_MS = 90_000;
8+
9+
export function parseArgs(argv) {
10+
const positional = [];
11+
let timeoutMs = Number(process.env.TEST_FILE_TIMEOUT_MS || DEFAULT_TIMEOUT_MS);
12+
for (let i = 0; i < argv.length; i++) {
13+
const arg = argv[i];
14+
if (arg === '--timeout-ms') {
15+
timeoutMs = Number(argv[++i]);
16+
continue;
17+
}
18+
if (arg?.startsWith('--timeout-ms=')) {
19+
timeoutMs = Number(arg.slice('--timeout-ms='.length));
20+
continue;
21+
}
22+
positional.push(arg);
23+
}
24+
25+
const shardIndex = Number(positional[0] ?? process.env.TEST_SHARD_INDEX ?? 0);
26+
const shardTotal = Number(positional[1] ?? process.env.TEST_SHARD_TOTAL ?? 1);
27+
if (!Number.isInteger(shardIndex) || shardIndex < 0) {
28+
throw new Error(`Invalid shard index: ${positional[0] ?? process.env.TEST_SHARD_INDEX ?? ''}`);
29+
}
30+
if (!Number.isInteger(shardTotal) || shardTotal < 1) {
31+
throw new Error(`Invalid shard total: ${positional[1] ?? process.env.TEST_SHARD_TOTAL ?? ''}`);
32+
}
33+
if (shardIndex >= shardTotal) {
34+
throw new Error(`Shard index ${shardIndex} must be smaller than shard total ${shardTotal}`);
35+
}
36+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1_000) {
37+
throw new Error(`Invalid per-file timeout: ${timeoutMs}`);
38+
}
39+
return { shardIndex, shardTotal, timeoutMs };
40+
}
41+
42+
export function listTopLevelTestFiles(root = process.cwd()) {
43+
const testDir = join(root, 'test');
44+
return readdirSync(testDir)
45+
.filter(name => name.endsWith('.test.js'))
46+
.sort((a, b) => a.localeCompare(b))
47+
.map(name => join('test', name).replace(/\\/g, '/'));
48+
}
49+
50+
export function selectShard(files, shardIndex, shardTotal) {
51+
return files.filter((_, i) => i % shardTotal === shardIndex);
52+
}
53+
54+
function runOne(file, timeoutMs) {
55+
return new Promise(resolveRun => {
56+
const child = spawn(process.execPath, ['--test', '--test-force-exit', file], {
57+
cwd: process.cwd(),
58+
env: process.env,
59+
stdio: ['ignore', 'pipe', 'pipe'],
60+
});
61+
const prefix = `[${file}] `;
62+
child.stdout.on('data', chunk => process.stdout.write(prefix + String(chunk).replace(/\n/g, `\n${prefix}`)));
63+
child.stderr.on('data', chunk => process.stderr.write(prefix + String(chunk).replace(/\n/g, `\n${prefix}`)));
64+
65+
let timedOut = false;
66+
const timer = setTimeout(() => {
67+
timedOut = true;
68+
child.kill('SIGTERM');
69+
setTimeout(() => child.kill('SIGKILL'), 5_000).unref?.();
70+
}, timeoutMs);
71+
72+
child.on('close', code => {
73+
clearTimeout(timer);
74+
resolveRun({
75+
file,
76+
ok: !timedOut && code === 0,
77+
code,
78+
timedOut,
79+
});
80+
});
81+
});
82+
}
83+
84+
export async function runShard({ shardIndex, shardTotal, timeoutMs, root = process.cwd() }) {
85+
const files = listTopLevelTestFiles(root);
86+
const selected = selectShard(files, shardIndex, shardTotal);
87+
console.log(`Running test shard ${shardIndex + 1}/${shardTotal}: ${selected.length}/${files.length} files`);
88+
for (const file of selected) console.log(`- ${file}`);
89+
90+
const failures = [];
91+
for (const file of selected) {
92+
const result = await runOne(file, timeoutMs);
93+
if (!result.ok) failures.push(result);
94+
}
95+
96+
if (failures.length) {
97+
console.error(`Test shard ${shardIndex + 1}/${shardTotal} failed:`);
98+
for (const failure of failures) {
99+
const suffix = failure.timedOut ? `timed out after ${timeoutMs}ms` : `exit ${failure.code}`;
100+
console.error(`- ${failure.file}: ${suffix}`);
101+
}
102+
return 1;
103+
}
104+
return 0;
105+
}
106+
107+
if (resolve(process.argv[1] || '') === resolve(fileURLToPath(import.meta.url))) {
108+
try {
109+
const opts = parseArgs(process.argv.slice(2));
110+
process.exitCode = await runShard(opts);
111+
} catch (err) {
112+
console.error(err?.message || err);
113+
process.exitCode = 2;
114+
}
115+
}

src/cache.js

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { log } from './config.js';
1212

1313
const TTL_MS = 5 * 60 * 1000;
1414
const MAX_ENTRIES = 500;
15+
const DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
1516

1617
function isCacheEnabled() {
1718
const raw = String(process.env.RESPONSE_CACHE_ENABLED ?? process.env.WINDSURFAPI_RESPONSE_CACHE ?? '1')
@@ -22,7 +23,49 @@ function isCacheEnabled() {
2223

2324
// Map preserves insertion order → we evict the oldest when over capacity.
2425
const _store = new Map();
25-
const _stats = { hits: 0, misses: 0, stores: 0, evictions: 0 };
26+
const _stats = { hits: 0, misses: 0, stores: 0, evictions: 0, skips: 0 };
27+
let _bytes = 0;
28+
29+
function bytesEnv(names, fallback) {
30+
for (const name of names) {
31+
const raw = String(process.env[name] || '').trim();
32+
if (!raw) continue;
33+
const m = raw.match(/^(\d+(?:\.\d+)?)\s*(b|kb|kib|k|mb|mib|m|gb|gib|g)?$/i);
34+
if (!m) continue;
35+
const n = Number(m[1]);
36+
if (!Number.isFinite(n) || n <= 0) continue;
37+
const unit = (m[2] || 'b').toLowerCase();
38+
const mul = unit === 'gb' || unit === 'gib' || unit === 'g' ? 1024 ** 3
39+
: unit === 'mb' || unit === 'mib' || unit === 'm' ? 1024 ** 2
40+
: unit === 'kb' || unit === 'kib' || unit === 'k' ? 1024
41+
: 1;
42+
return Math.floor(n * mul);
43+
}
44+
return fallback;
45+
}
46+
47+
function maxBytes() {
48+
return bytesEnv(
49+
['RESPONSE_CACHE_MAX_BYTES', 'WINDSURFAPI_RESPONSE_CACHE_MAX_BYTES'],
50+
DEFAULT_MAX_BYTES
51+
);
52+
}
53+
54+
function valueBytes(value) {
55+
try {
56+
return Buffer.byteLength(JSON.stringify(value), 'utf8');
57+
} catch {
58+
return Number.POSITIVE_INFINITY;
59+
}
60+
}
61+
62+
function deleteEntry(key) {
63+
const entry = _store.get(key);
64+
if (!entry) return false;
65+
_store.delete(key);
66+
_bytes = Math.max(0, _bytes - (Number(entry.bytes) || 0));
67+
return true;
68+
}
2669

2770
function digestBase64Data(data = '', mime = '') {
2871
const compact = String(data).replace(/\s/g, '');
@@ -94,7 +137,7 @@ export function cacheGet(key) {
94137
const entry = _store.get(key);
95138
if (!entry) { _stats.misses++; return null; }
96139
if (entry.expiresAt < Date.now()) {
97-
_store.delete(key);
140+
deleteEntry(key);
98141
_stats.misses++;
99142
return null;
100143
}
@@ -109,11 +152,20 @@ export function cacheSet(key, value) {
109152
if (!isCacheEnabled()) return;
110153
// Don't cache empty or partial results
111154
if (!value || (!value.text && !(value.chunks && value.chunks.length))) return;
112-
_store.set(key, { value, expiresAt: Date.now() + TTL_MS });
155+
const bytes = valueBytes(value);
156+
const limit = maxBytes();
157+
deleteEntry(key);
158+
if (!Number.isFinite(bytes) || bytes > limit) {
159+
_stats.skips++;
160+
return;
161+
}
162+
_store.set(key, { value, expiresAt: Date.now() + TTL_MS, bytes });
163+
_bytes += bytes;
113164
_stats.stores++;
114-
while (_store.size > MAX_ENTRIES) {
165+
while (_store.size > MAX_ENTRIES || _bytes > limit) {
115166
const oldest = _store.keys().next().value;
116-
_store.delete(oldest);
167+
if (oldest === undefined) break;
168+
deleteEntry(oldest);
117169
_stats.evictions++;
118170
}
119171
}
@@ -124,17 +176,21 @@ export function cacheStats() {
124176
enabled: isCacheEnabled(),
125177
size: _store.size,
126178
maxSize: MAX_ENTRIES,
179+
bytes: _bytes,
180+
maxBytes: maxBytes(),
127181
ttlMs: TTL_MS,
128182
hits: _stats.hits,
129183
misses: _stats.misses,
130184
stores: _stats.stores,
131185
evictions: _stats.evictions,
186+
skips: _stats.skips,
132187
hitRate: total > 0 ? ((_stats.hits / total) * 100).toFixed(1) : '0.0',
133188
};
134189
}
135190

136191
export function cacheClear() {
137192
_store.clear();
138-
_stats.hits = 0; _stats.misses = 0; _stats.stores = 0; _stats.evictions = 0;
193+
_bytes = 0;
194+
_stats.hits = 0; _stats.misses = 0; _stats.stores = 0; _stats.evictions = 0; _stats.skips = 0;
139195
log.info('Response cache cleared');
140196
}

0 commit comments

Comments
 (0)