codex-dev-mcp-suite 1.1.0 → 1.3.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.
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * LLM reranker via any OpenAI-compatible /v1/chat/completions model.
3
- * Used when embeddings are unavailable: keyword prefilter -> LLM picks the
4
- * most relevant candidates. Degrades gracefully: returns null on any failure
5
- * so callers fall back to keyword ordering. Never throws.
3
+ * Tries provider chain slots in order, skipping any currently cooling down
4
+ * after a recent failure. Degrades gracefully: returns null on any failure so
5
+ * callers fall back to keyword ordering. Never throws. Never logs keys/bodies.
6
6
  */
7
7
  import http from "http";
8
8
  import https from "https";
9
9
  import { URL } from "url";
10
10
  import { deterministicEnabled } from "./env.js";
11
- import { providerChainConfig } from "./provider-chain.js";
11
+ import { providerChainConfig, isCoolingDown, recordOutcome } from "./provider-chain.js";
12
12
 
13
13
  const TIMEOUT_MS = Number(process.env.RERANK_TIMEOUT_MS || 30000);
14
14
  const ENABLED = process.env.RERANK_ENABLED !== "0";
@@ -25,10 +25,19 @@ export function rerankConfig() {
25
25
  };
26
26
  }
27
27
 
28
+ function providerKey(provider) {
29
+ return `${provider.label}|${provider.base}|${provider.model}`;
30
+ }
31
+
32
+ /**
33
+ * Resolves { content, retryable }. retryable=true means try the next provider
34
+ * (timeout, network error, 429, or 5xx). retryable=false with null content
35
+ * means a definitive non-retryable response (e.g. 4xx auth).
36
+ */
28
37
  function chatWithProvider(provider, messages) {
29
38
  return new Promise((resolve) => {
30
39
  let u;
31
- try { u = new URL("/v1/chat/completions", provider.base); } catch { return resolve(null); }
40
+ try { u = new URL("/v1/chat/completions", provider.base); } catch { return resolve({ content: null, retryable: false }); }
32
41
  const payload = Buffer.from(JSON.stringify({ model: provider.model, stream: false, temperature: 0, max_tokens: 200, messages }));
33
42
  const lib = u.protocol === "https:" ? https : http;
34
43
  const req = lib.request(
@@ -44,10 +53,14 @@ function chatWithProvider(provider, messages) {
44
53
  let body = "";
45
54
  res.on("data", (c) => (body += c));
46
55
  res.on("end", () => {
47
- if (res.statusCode !== 200) return resolve(null);
56
+ const status = res.statusCode || 0;
57
+ if (status !== 200) {
58
+ const retryable = status === 429 || status >= 500;
59
+ return resolve({ content: null, retryable });
60
+ }
48
61
  try {
49
62
  const j = JSON.parse(body);
50
- return resolve(j?.choices?.[0]?.message?.content ?? null);
63
+ return resolve({ content: j?.choices?.[0]?.message?.content ?? null, retryable: false });
51
64
  } catch { /* maybe SSE */ }
52
65
  try {
53
66
  let acc = "";
@@ -59,13 +72,13 @@ function chatWithProvider(provider, messages) {
59
72
  const j = JSON.parse(d);
60
73
  acc += j?.choices?.[0]?.delta?.content || j?.choices?.[0]?.message?.content || "";
61
74
  }
62
- return resolve(acc || null);
63
- } catch { return resolve(null); }
75
+ return resolve({ content: acc || null, retryable: false });
76
+ } catch { return resolve({ content: null, retryable: true }); }
64
77
  });
65
78
  }
66
79
  );
67
- req.on("error", () => resolve(null));
68
- req.on("timeout", () => { req.destroy(); resolve(null); });
80
+ req.on("error", () => resolve({ content: null, retryable: true }));
81
+ req.on("timeout", () => { req.destroy(); resolve({ content: null, retryable: true }); });
69
82
  req.write(payload);
70
83
  req.end();
71
84
  });
@@ -75,8 +88,11 @@ async function chat(messages) {
75
88
  if (!ENABLED || deterministicEnabled()) return null;
76
89
  const { providers } = providerChainConfig();
77
90
  for (const provider of providers) {
78
- const out = await chatWithProvider(provider, messages);
79
- if (out) return out;
91
+ const key = providerKey(provider);
92
+ if (isCoolingDown(key)) continue;
93
+ const { content, retryable } = await chatWithProvider(provider, messages);
94
+ if (content) { recordOutcome(key, true); return content; }
95
+ if (retryable) recordOutcome(key, false);
80
96
  }
81
97
  return null;
82
98
  }
@@ -29,6 +29,7 @@ import crypto from "crypto";
29
29
  import { embed, embedOne, cosine, embeddingConfig } from "./embedding.js";
30
30
  import { rerank, rerankConfig } from "./rerank.js";
31
31
  import { deterministicEnabled } from "./env.js";
32
+ import { computeStats, formatText, formatJson } from "../lib/stats.js";
32
33
 
33
34
  const VAULT_ROOT =
34
35
  process.env.MEMORY_VAULT_DIR ||
@@ -220,6 +221,18 @@ class ProjectMemoryServer {
220
221
  },
221
222
  },
222
223
  },
224
+ {
225
+ name: "memory_stats",
226
+ description: "Summarize local memory storage across vault / journal / checkpoints: totals, top projects, recent activity, and temp-slug cleanup candidates. Returns the same text as the `stats` CLI.",
227
+ inputSchema: {
228
+ type: "object",
229
+ properties: {
230
+ root: { type: "string", description: "Storage root (default: ~/.codex/memories or first env var of MEMORY_VAULT_DIR / JOURNAL_DIR / CHECKPOINT_DIR)" },
231
+ top: { type: "number", description: "How many entries in top-project / recent-activity lists (default 10)", default: 10 },
232
+ json: { type: "boolean", description: "Return machine-readable JSON instead of human text", default: false },
233
+ },
234
+ },
235
+ },
223
236
  ],
224
237
  }));
225
238
 
@@ -233,6 +246,7 @@ class ProjectMemoryServer {
233
246
  case "memory_get": return await this.get(args || {});
234
247
  case "memory_delete": return await this.del(args || {});
235
248
  case "memory_reindex": return await this.reindex(args || {});
249
+ case "memory_stats": return await this.stats(args || {});
236
250
  default: throw new Error(`Unknown tool: ${name}`);
237
251
  }
238
252
  } catch (error) {
@@ -439,6 +453,21 @@ class ProjectMemoryServer {
439
453
  return { content: [{ type: "text", text: `Reindex ${p.slug}: embedded ${done}, failed ${failed} (embeddings ${embeddingConfig().enabled ? "configured" : "not configured"}). ${failed ? "Failures usually mean the embedding model is unavailable right now." : ""}` }] };
440
454
  }
441
455
 
456
+ async stats({ root: explicitRoot, top = 10, json = false } = {}) {
457
+ const root = explicitRoot
458
+ ? path.resolve(explicitRoot)
459
+ : (process.env.MEMORY_VAULT_DIR
460
+ ? path.dirname(process.env.MEMORY_VAULT_DIR)
461
+ : process.env.JOURNAL_DIR
462
+ ? path.dirname(process.env.JOURNAL_DIR)
463
+ : process.env.CHECKPOINT_DIR
464
+ ? path.dirname(process.env.CHECKPOINT_DIR)
465
+ : path.join(os.homedir(), ".codex", "memories"));
466
+ const stats = computeStats({ root, topLimit: top });
467
+ const text = json ? formatJson(stats) : formatText(stats);
468
+ return { content: [{ type: "text", text }] };
469
+ }
470
+
442
471
  async run() {
443
472
  const transport = new StdioServerTransport();
444
473
  await this.server.connect(transport);