evrex-mcp 0.8.0 → 0.8.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/dist/capture.js +15 -2
- package/dist/import.js +52 -2
- package/dist/index.js +374 -75
- package/package.json +16 -15
package/dist/capture.js
CHANGED
|
@@ -180,7 +180,7 @@ function commitMeta(repoPath, sha) {
|
|
|
180
180
|
const line = git(repoPath, [
|
|
181
181
|
"show",
|
|
182
182
|
"-s",
|
|
183
|
-
`--format=%H${FIELD_SEP}%an${FIELD_SEP}%ae${FIELD_SEP}%aI`,
|
|
183
|
+
`--format=%H${FIELD_SEP}%an${FIELD_SEP}%ae${FIELD_SEP}%aI${FIELD_SEP}%P`,
|
|
184
184
|
sha
|
|
185
185
|
]).trim();
|
|
186
186
|
const parts = line.split(FIELD_SEP);
|
|
@@ -190,6 +190,7 @@ function commitMeta(repoPath, sha) {
|
|
|
190
190
|
author: parts[1] ?? "",
|
|
191
191
|
authorEmail: parts[2] ?? "",
|
|
192
192
|
ts: parts[3] ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
193
|
+
parents: (parts[4] ?? "").split(" ").filter(Boolean),
|
|
193
194
|
message
|
|
194
195
|
};
|
|
195
196
|
}
|
|
@@ -238,6 +239,10 @@ function trailerValue(trailers, key) {
|
|
|
238
239
|
const k = key.toLowerCase();
|
|
239
240
|
return trailers.find((t) => t.key.toLowerCase() === k)?.value ?? null;
|
|
240
241
|
}
|
|
242
|
+
function originOf(trailers) {
|
|
243
|
+
const v = trailerValue(trailers, EVREX_ORIGIN_TRAILER_KEY);
|
|
244
|
+
return v?.trim().toLowerCase() === "human" ? "human" : null;
|
|
245
|
+
}
|
|
241
246
|
function agentTrailersOf(trailers) {
|
|
242
247
|
const out = [];
|
|
243
248
|
for (const t of trailers) {
|
|
@@ -319,15 +324,17 @@ function parseGitLog(repoPath, repoId, known) {
|
|
|
319
324
|
authorEmail: meta.authorEmail,
|
|
320
325
|
ts: meta.ts,
|
|
321
326
|
message: meta.message,
|
|
327
|
+
parents: meta.parents,
|
|
322
328
|
branch: commitBranch(repoPath, sha),
|
|
323
329
|
evrexSessionTrailer: trailerValue(trailers, EVREX_SESSION_TRAILER_KEY),
|
|
330
|
+
origin: originOf(trailers),
|
|
324
331
|
agentTrailers: agentTrailersOf(trailers),
|
|
325
332
|
statedInsights: statedInsightsOf(trailers),
|
|
326
333
|
files: commitFiles(repoPath, sha)
|
|
327
334
|
};
|
|
328
335
|
});
|
|
329
336
|
}
|
|
330
|
-
var DIFF_CAP, FIELD_SEP, EVREX_SESSION_TRAILER_KEY;
|
|
337
|
+
var DIFF_CAP, FIELD_SEP, EVREX_SESSION_TRAILER_KEY, EVREX_ORIGIN_TRAILER_KEY;
|
|
331
338
|
var init_git_history = __esm({
|
|
332
339
|
"../../packages/ingest-core/src/git-history.ts"() {
|
|
333
340
|
"use strict";
|
|
@@ -335,6 +342,7 @@ var init_git_history = __esm({
|
|
|
335
342
|
DIFF_CAP = 2e4;
|
|
336
343
|
FIELD_SEP = "";
|
|
337
344
|
EVREX_SESSION_TRAILER_KEY = "Evrex-Session";
|
|
345
|
+
EVREX_ORIGIN_TRAILER_KEY = "Evrex-Origin";
|
|
338
346
|
}
|
|
339
347
|
});
|
|
340
348
|
|
|
@@ -811,6 +819,7 @@ function parseSessionFile(filePath, repoPath, repoId = deriveRepoId(repoPath), a
|
|
|
811
819
|
let totalRedactions = 0;
|
|
812
820
|
let branch = null;
|
|
813
821
|
let agentId = null;
|
|
822
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
814
823
|
for (const line of lines) {
|
|
815
824
|
let record;
|
|
816
825
|
try {
|
|
@@ -826,6 +835,8 @@ function parseSessionFile(filePath, repoPath, repoId = deriveRepoId(repoPath), a
|
|
|
826
835
|
const id = record.uuid;
|
|
827
836
|
const ts = record.timestamp;
|
|
828
837
|
if (!id || !ts) continue;
|
|
838
|
+
if (seenIds.has(id)) continue;
|
|
839
|
+
seenIds.add(id);
|
|
829
840
|
sessionId ??= record.sessionId ?? record.session_id ?? null;
|
|
830
841
|
if (typeof record.gitBranch === "string" && record.gitBranch) branch = record.gitBranch;
|
|
831
842
|
if (typeof record.agentId === "string" && record.agentId) agentId ??= record.agentId;
|
|
@@ -2928,6 +2939,7 @@ var src_exports = {};
|
|
|
2928
2939
|
__export(src_exports, {
|
|
2929
2940
|
CONVERSATION_KINDS: () => CONVERSATION_KINDS,
|
|
2930
2941
|
EMPTY_USAGE: () => EMPTY_USAGE,
|
|
2942
|
+
EVREX_ORIGIN_TRAILER_KEY: () => EVREX_ORIGIN_TRAILER_KEY,
|
|
2931
2943
|
EVREX_SESSION_TRAILER_KEY: () => EVREX_SESSION_TRAILER_KEY,
|
|
2932
2944
|
JIRA_FIELDS: () => JIRA_FIELDS,
|
|
2933
2945
|
JIRA_SEARCH_PATH: () => JIRA_SEARCH_PATH,
|
|
@@ -2986,6 +2998,7 @@ __export(src_exports, {
|
|
|
2986
2998
|
measureLineSurvival: () => measureLineSurvival,
|
|
2987
2999
|
normalizeRepoRemote: () => normalizeRepoRemote,
|
|
2988
3000
|
opencodeDbPath: () => opencodeDbPath,
|
|
3001
|
+
originOf: () => originOf,
|
|
2989
3002
|
parseAllCodexSessions: () => parseAllCodexSessions,
|
|
2990
3003
|
parseAllCopilotSessions: () => parseAllCopilotSessions,
|
|
2991
3004
|
parseAllCursorSessions: () => parseAllCursorSessions,
|
package/dist/import.js
CHANGED
|
@@ -180,7 +180,7 @@ function commitMeta(repoPath, sha) {
|
|
|
180
180
|
const line = git(repoPath, [
|
|
181
181
|
"show",
|
|
182
182
|
"-s",
|
|
183
|
-
`--format=%H${FIELD_SEP}%an${FIELD_SEP}%ae${FIELD_SEP}%aI`,
|
|
183
|
+
`--format=%H${FIELD_SEP}%an${FIELD_SEP}%ae${FIELD_SEP}%aI${FIELD_SEP}%P`,
|
|
184
184
|
sha
|
|
185
185
|
]).trim();
|
|
186
186
|
const parts = line.split(FIELD_SEP);
|
|
@@ -190,6 +190,7 @@ function commitMeta(repoPath, sha) {
|
|
|
190
190
|
author: parts[1] ?? "",
|
|
191
191
|
authorEmail: parts[2] ?? "",
|
|
192
192
|
ts: parts[3] ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
193
|
+
parents: (parts[4] ?? "").split(" ").filter(Boolean),
|
|
193
194
|
message
|
|
194
195
|
};
|
|
195
196
|
}
|
|
@@ -238,6 +239,10 @@ function trailerValue(trailers, key) {
|
|
|
238
239
|
const k = key.toLowerCase();
|
|
239
240
|
return trailers.find((t) => t.key.toLowerCase() === k)?.value ?? null;
|
|
240
241
|
}
|
|
242
|
+
function originOf(trailers) {
|
|
243
|
+
const v = trailerValue(trailers, EVREX_ORIGIN_TRAILER_KEY);
|
|
244
|
+
return v?.trim().toLowerCase() === "human" ? "human" : null;
|
|
245
|
+
}
|
|
241
246
|
function agentTrailersOf(trailers) {
|
|
242
247
|
const out = [];
|
|
243
248
|
for (const t of trailers) {
|
|
@@ -319,15 +324,17 @@ function parseGitLog(repoPath, repoId, known) {
|
|
|
319
324
|
authorEmail: meta.authorEmail,
|
|
320
325
|
ts: meta.ts,
|
|
321
326
|
message: meta.message,
|
|
327
|
+
parents: meta.parents,
|
|
322
328
|
branch: commitBranch(repoPath, sha),
|
|
323
329
|
evrexSessionTrailer: trailerValue(trailers, EVREX_SESSION_TRAILER_KEY),
|
|
330
|
+
origin: originOf(trailers),
|
|
324
331
|
agentTrailers: agentTrailersOf(trailers),
|
|
325
332
|
statedInsights: statedInsightsOf(trailers),
|
|
326
333
|
files: commitFiles(repoPath, sha)
|
|
327
334
|
};
|
|
328
335
|
});
|
|
329
336
|
}
|
|
330
|
-
var DIFF_CAP, FIELD_SEP, EVREX_SESSION_TRAILER_KEY;
|
|
337
|
+
var DIFF_CAP, FIELD_SEP, EVREX_SESSION_TRAILER_KEY, EVREX_ORIGIN_TRAILER_KEY;
|
|
331
338
|
var init_git_history = __esm({
|
|
332
339
|
"../../packages/ingest-core/src/git-history.ts"() {
|
|
333
340
|
"use strict";
|
|
@@ -335,6 +342,7 @@ var init_git_history = __esm({
|
|
|
335
342
|
DIFF_CAP = 2e4;
|
|
336
343
|
FIELD_SEP = "";
|
|
337
344
|
EVREX_SESSION_TRAILER_KEY = "Evrex-Session";
|
|
345
|
+
EVREX_ORIGIN_TRAILER_KEY = "Evrex-Origin";
|
|
338
346
|
}
|
|
339
347
|
});
|
|
340
348
|
|
|
@@ -811,6 +819,7 @@ function parseSessionFile(filePath, repoPath, repoId = deriveRepoId(repoPath), a
|
|
|
811
819
|
let totalRedactions = 0;
|
|
812
820
|
let branch = null;
|
|
813
821
|
let agentId = null;
|
|
822
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
814
823
|
for (const line of lines) {
|
|
815
824
|
let record;
|
|
816
825
|
try {
|
|
@@ -826,6 +835,8 @@ function parseSessionFile(filePath, repoPath, repoId = deriveRepoId(repoPath), a
|
|
|
826
835
|
const id = record.uuid;
|
|
827
836
|
const ts = record.timestamp;
|
|
828
837
|
if (!id || !ts) continue;
|
|
838
|
+
if (seenIds.has(id)) continue;
|
|
839
|
+
seenIds.add(id);
|
|
829
840
|
sessionId ??= record.sessionId ?? record.session_id ?? null;
|
|
830
841
|
if (typeof record.gitBranch === "string" && record.gitBranch) branch = record.gitBranch;
|
|
831
842
|
if (typeof record.agentId === "string" && record.agentId) agentId ??= record.agentId;
|
|
@@ -2928,6 +2939,7 @@ var src_exports = {};
|
|
|
2928
2939
|
__export(src_exports, {
|
|
2929
2940
|
CONVERSATION_KINDS: () => CONVERSATION_KINDS,
|
|
2930
2941
|
EMPTY_USAGE: () => EMPTY_USAGE,
|
|
2942
|
+
EVREX_ORIGIN_TRAILER_KEY: () => EVREX_ORIGIN_TRAILER_KEY,
|
|
2931
2943
|
EVREX_SESSION_TRAILER_KEY: () => EVREX_SESSION_TRAILER_KEY,
|
|
2932
2944
|
JIRA_FIELDS: () => JIRA_FIELDS,
|
|
2933
2945
|
JIRA_SEARCH_PATH: () => JIRA_SEARCH_PATH,
|
|
@@ -2986,6 +2998,7 @@ __export(src_exports, {
|
|
|
2986
2998
|
measureLineSurvival: () => measureLineSurvival,
|
|
2987
2999
|
normalizeRepoRemote: () => normalizeRepoRemote,
|
|
2988
3000
|
opencodeDbPath: () => opencodeDbPath,
|
|
3001
|
+
originOf: () => originOf,
|
|
2989
3002
|
parseAllCodexSessions: () => parseAllCodexSessions,
|
|
2990
3003
|
parseAllCopilotSessions: () => parseAllCopilotSessions,
|
|
2991
3004
|
parseAllCursorSessions: () => parseAllCursorSessions,
|
|
@@ -3544,7 +3557,44 @@ async function durability(target, argv) {
|
|
|
3544
3557
|
const surviving = measurements.reduce((n, m) => n + m.survivingLines, 0);
|
|
3545
3558
|
console.error(` ${body.updated} commits recorded \xB7 ${surviving}/${added} added lines still at HEAD (${added ? Math.round(100 * surviving / added) : 0}%)`);
|
|
3546
3559
|
}
|
|
3560
|
+
async function parents(target) {
|
|
3561
|
+
const { deriveRepoId: deriveRepoId2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
3562
|
+
const { execFileSync: execFileSync5 } = await import("node:child_process");
|
|
3563
|
+
const { evrexApi: evrexApi2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3564
|
+
const token = process.env.EVREX_TOKEN ?? await new (await Promise.resolve().then(() => (init_credential_store(), credential_store_exports))).CredentialStore().retrieve().catch(() => null);
|
|
3565
|
+
if (!token) {
|
|
3566
|
+
console.error("evrex: this machine is not enrolled.\n Run `npx -y evrex-mcp enrol` first, or set EVREX_TOKEN.");
|
|
3567
|
+
process.exit(1);
|
|
3568
|
+
}
|
|
3569
|
+
const repoId = deriveRepoId2(target);
|
|
3570
|
+
const rows = execFileSync5("git", ["rev-list", "--parents", "--all"], { cwd: target, maxBuffer: 64 * 1024 * 1024 }).toString("utf-8").split("\n").filter(Boolean).map((line) => {
|
|
3571
|
+
const [sha, ...parents2] = line.split(" ");
|
|
3572
|
+
return { sha, parents: parents2 };
|
|
3573
|
+
});
|
|
3574
|
+
console.error(`Reporting parents for ${rows.length} commits in ${target} (${rows.filter((r) => r.parents.length > 1).length} merges)`);
|
|
3575
|
+
let updated = 0;
|
|
3576
|
+
let inherited = 0;
|
|
3577
|
+
for (let i = 0; i < rows.length; i += 500) {
|
|
3578
|
+
const res = await fetch(`${evrexApi2.baseUrl}/ingest/commits/parents`, {
|
|
3579
|
+
method: "POST",
|
|
3580
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
3581
|
+
body: JSON.stringify({ repoId, commits: rows.slice(i, i + 500) })
|
|
3582
|
+
});
|
|
3583
|
+
if (!res.ok) {
|
|
3584
|
+
console.error(` /ingest/commits/parents -> ${res.status}`);
|
|
3585
|
+
process.exit(1);
|
|
3586
|
+
}
|
|
3587
|
+
const body = await res.json();
|
|
3588
|
+
updated += body.updated;
|
|
3589
|
+
inherited = body.inherited;
|
|
3590
|
+
}
|
|
3591
|
+
console.error(` ${updated} commits updated \xB7 merges now inherit ${inherited} link(s)`);
|
|
3592
|
+
}
|
|
3547
3593
|
async function main() {
|
|
3594
|
+
if (process.argv[2] === "parents") {
|
|
3595
|
+
await parents(resolve(process.argv[3] && !process.argv[3].startsWith("--") ? process.argv[3] : process.cwd()));
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3548
3598
|
if (process.argv[2] === "durability") {
|
|
3549
3599
|
await durability(resolve(process.argv[3] && !process.argv[3].startsWith("--") ? process.argv[3] : process.cwd()), process.argv.slice(3));
|
|
3550
3600
|
return;
|
package/dist/index.js
CHANGED
|
@@ -646,6 +646,29 @@ ${request2.user}`;
|
|
|
646
646
|
// ../../packages/llm-core/src/provider-clients.ts
|
|
647
647
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
648
648
|
var NOOP = { warn: () => void 0, error: () => void 0 };
|
|
649
|
+
var BATCH_POLL_MS = 5e3;
|
|
650
|
+
var BATCH_TIMEOUT_MS = 6 * 60 * 60 * 1e3;
|
|
651
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
652
|
+
function totalize(items, results) {
|
|
653
|
+
for (const item of items) if (!results.has(item.id)) results.set(item.id, null);
|
|
654
|
+
return results;
|
|
655
|
+
}
|
|
656
|
+
function totalizeIds(ids, results) {
|
|
657
|
+
for (const id of ids) if (!results.has(id)) results.set(id, null);
|
|
658
|
+
return results;
|
|
659
|
+
}
|
|
660
|
+
async function pollToCompletion(handle, resolve, sleepFn, onTimeout) {
|
|
661
|
+
const startedAt = Date.now();
|
|
662
|
+
for (; ; ) {
|
|
663
|
+
const resolved = await resolve(handle);
|
|
664
|
+
if (resolved) return resolved;
|
|
665
|
+
if (Date.now() - startedAt > BATCH_TIMEOUT_MS) {
|
|
666
|
+
onTimeout(handle.id);
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
await sleepFn(BATCH_POLL_MS);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
649
672
|
var realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
650
673
|
var MAX_RETRIES = 4;
|
|
651
674
|
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
@@ -653,12 +676,12 @@ function isExhaustedStatus(status, retriesSpent) {
|
|
|
653
676
|
if (status === 401 || status === 403) return true;
|
|
654
677
|
return status === 429 && retriesSpent;
|
|
655
678
|
}
|
|
656
|
-
async function waitBeforeRetry(response, attempt,
|
|
679
|
+
async function waitBeforeRetry(response, attempt, sleep2) {
|
|
657
680
|
const header = response.headers.get("retry-after") ?? response.headers.get("x-ratelimit-reset-tokens");
|
|
658
681
|
const seconds = header ? parseDuration(header) : null;
|
|
659
682
|
const backoff = Math.min(8 * 2 ** attempt, 60);
|
|
660
683
|
const wait = Math.min(seconds ?? backoff, MAX_RETRY_WAIT_SECONDS);
|
|
661
|
-
await
|
|
684
|
+
await sleep2(wait * 1e3);
|
|
662
685
|
}
|
|
663
686
|
var MAX_RETRY_WAIT_SECONDS = 90;
|
|
664
687
|
function parseDuration(value) {
|
|
@@ -678,7 +701,7 @@ function parseJsonBody(text) {
|
|
|
678
701
|
return null;
|
|
679
702
|
}
|
|
680
703
|
}
|
|
681
|
-
var AnthropicClient = class {
|
|
704
|
+
var AnthropicClient = class _AnthropicClient {
|
|
682
705
|
constructor(key, model, logger) {
|
|
683
706
|
this.model = model;
|
|
684
707
|
this.logger = logger;
|
|
@@ -687,37 +710,47 @@ var AnthropicClient = class {
|
|
|
687
710
|
provider = "anthropic";
|
|
688
711
|
exhausted = false;
|
|
689
712
|
sdk;
|
|
713
|
+
// The one place a ModelRequest becomes Anthropic's message params, so the
|
|
714
|
+
// single and batch paths cannot drift on the cache breakpoint or the
|
|
715
|
+
// json_schema wiring.
|
|
716
|
+
messageParams(request2) {
|
|
717
|
+
return {
|
|
718
|
+
model: request2.model ?? this.model,
|
|
719
|
+
max_tokens: request2.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
720
|
+
// The breakpoint sits at the end of the system prompt, which is the
|
|
721
|
+
// only part of these requests that repeats. Rendering order is
|
|
722
|
+
// tools -> system -> messages, so a marker here caches everything
|
|
723
|
+
// ahead of the transcript.
|
|
724
|
+
//
|
|
725
|
+
// Deliberately NOT on the user block. That block is a different
|
|
726
|
+
// transcript chunk on every call, so a breakpoint there would write a
|
|
727
|
+
// fresh cache entry per request and read none of them back — paying
|
|
728
|
+
// the write premium for nothing, which is worse than not caching.
|
|
729
|
+
system: [
|
|
730
|
+
{
|
|
731
|
+
type: "text",
|
|
732
|
+
text: request2.system,
|
|
733
|
+
cache_control: { type: "ephemeral" }
|
|
734
|
+
}
|
|
735
|
+
],
|
|
736
|
+
messages: [{ role: "user", content: request2.user }],
|
|
737
|
+
...request2.schema ? {
|
|
738
|
+
output_config: {
|
|
739
|
+
format: { type: "json_schema", schema: request2.schema }
|
|
740
|
+
}
|
|
741
|
+
} : {}
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
static messageText(message) {
|
|
745
|
+
return message.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
746
|
+
}
|
|
690
747
|
async completeJson(request2) {
|
|
691
748
|
try {
|
|
692
|
-
const message = await this.sdk.messages.create(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
// The breakpoint sits at the end of the system prompt, which is the
|
|
696
|
-
// only part of these requests that repeats. Rendering order is
|
|
697
|
-
// tools -> system -> messages, so a marker here caches everything
|
|
698
|
-
// ahead of the transcript.
|
|
699
|
-
//
|
|
700
|
-
// Deliberately NOT on the user block. That block is a different
|
|
701
|
-
// transcript chunk on every call, so a breakpoint there would write a
|
|
702
|
-
// fresh cache entry per request and read none of them back — paying
|
|
703
|
-
// the write premium for nothing, which is worse than not caching.
|
|
704
|
-
system: [
|
|
705
|
-
{
|
|
706
|
-
type: "text",
|
|
707
|
-
text: request2.system,
|
|
708
|
-
cache_control: { type: "ephemeral" }
|
|
709
|
-
}
|
|
710
|
-
],
|
|
711
|
-
messages: [{ role: "user", content: request2.user }],
|
|
712
|
-
...request2.schema ? {
|
|
713
|
-
output_config: {
|
|
714
|
-
format: { type: "json_schema", schema: request2.schema }
|
|
715
|
-
}
|
|
716
|
-
} : {}
|
|
717
|
-
});
|
|
749
|
+
const message = await this.sdk.messages.create(
|
|
750
|
+
this.messageParams(request2)
|
|
751
|
+
);
|
|
718
752
|
this.reportCacheUsage(message.usage);
|
|
719
|
-
|
|
720
|
-
return parseJsonBody(text);
|
|
753
|
+
return parseJsonBody(_AnthropicClient.messageText(message));
|
|
721
754
|
} catch (err) {
|
|
722
755
|
const status = err?.status;
|
|
723
756
|
if (typeof status === "number" && isExhaustedStatus(status, true)) {
|
|
@@ -727,6 +760,58 @@ var AnthropicClient = class {
|
|
|
727
760
|
return null;
|
|
728
761
|
}
|
|
729
762
|
}
|
|
763
|
+
async submitBatch(items) {
|
|
764
|
+
if (items.length === 0) return null;
|
|
765
|
+
try {
|
|
766
|
+
const created = await this.sdk.messages.batches.create({
|
|
767
|
+
requests: items.map((item) => ({
|
|
768
|
+
custom_id: item.id,
|
|
769
|
+
params: this.messageParams(item.request)
|
|
770
|
+
}))
|
|
771
|
+
});
|
|
772
|
+
return { provider: "anthropic", id: created.id, itemIds: items.map((i) => i.id) };
|
|
773
|
+
} catch (err) {
|
|
774
|
+
const status = err?.status;
|
|
775
|
+
if (typeof status === "number" && isExhaustedStatus(status, true)) {
|
|
776
|
+
this.exhausted = true;
|
|
777
|
+
}
|
|
778
|
+
this.logger.warn(`anthropic batch submit failed: ${describeError(err)}`);
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async resolveBatch(handle) {
|
|
783
|
+
try {
|
|
784
|
+
const batch = await this.sdk.messages.batches.retrieve(handle.id);
|
|
785
|
+
if (batch.processing_status !== "ended") return null;
|
|
786
|
+
const results = /* @__PURE__ */ new Map();
|
|
787
|
+
for await (const entry of await this.sdk.messages.batches.results(
|
|
788
|
+
handle.id
|
|
789
|
+
)) {
|
|
790
|
+
results.set(
|
|
791
|
+
entry.custom_id,
|
|
792
|
+
entry.result.type === "succeeded" ? parseJsonBody(_AnthropicClient.messageText(entry.result.message)) : null
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
return totalizeIds(handle.itemIds, results);
|
|
796
|
+
} catch (err) {
|
|
797
|
+
this.logger.warn(`anthropic batch resolve failed: ${describeError(err)}`);
|
|
798
|
+
return totalizeIds(handle.itemIds, /* @__PURE__ */ new Map());
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
async completeJsonBatch(items) {
|
|
802
|
+
if (items.length === 0) return /* @__PURE__ */ new Map();
|
|
803
|
+
const handle = await this.submitBatch(items);
|
|
804
|
+
if (!handle) return totalize(items, /* @__PURE__ */ new Map());
|
|
805
|
+
const resolved = await pollToCompletion(
|
|
806
|
+
handle,
|
|
807
|
+
(h) => this.resolveBatch(h),
|
|
808
|
+
sleep,
|
|
809
|
+
(id) => this.logger.warn(
|
|
810
|
+
`anthropic batch ${id} did not finish within the cap; the sessions it covers stay unextracted until the next pass`
|
|
811
|
+
)
|
|
812
|
+
);
|
|
813
|
+
return resolved ?? totalize(items, /* @__PURE__ */ new Map());
|
|
814
|
+
}
|
|
730
815
|
/**
|
|
731
816
|
* Says out loud whether the cache was actually used.
|
|
732
817
|
*
|
|
@@ -753,12 +838,12 @@ var AnthropicClient = class {
|
|
|
753
838
|
);
|
|
754
839
|
}
|
|
755
840
|
};
|
|
756
|
-
var GoogleClient = class {
|
|
757
|
-
constructor(key, model, logger,
|
|
841
|
+
var GoogleClient = class _GoogleClient {
|
|
842
|
+
constructor(key, model, logger, sleep2 = realSleep) {
|
|
758
843
|
this.key = key;
|
|
759
844
|
this.model = model;
|
|
760
845
|
this.logger = logger;
|
|
761
|
-
this.sleep =
|
|
846
|
+
this.sleep = sleep2;
|
|
762
847
|
}
|
|
763
848
|
provider = "google";
|
|
764
849
|
exhausted = false;
|
|
@@ -801,56 +886,129 @@ ${JSON.stringify(request2.schema)}` : "";
|
|
|
801
886
|
return null;
|
|
802
887
|
}
|
|
803
888
|
}
|
|
889
|
+
static GEMINI_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
890
|
+
async submitBatch(items) {
|
|
891
|
+
if (items.length === 0) return null;
|
|
892
|
+
const model = items[0].request.model ?? this.model;
|
|
893
|
+
try {
|
|
894
|
+
const create = await fetch(
|
|
895
|
+
`${_GoogleClient.GEMINI_BASE}/models/${encodeURIComponent(model)}:batchGenerateContent`,
|
|
896
|
+
{
|
|
897
|
+
method: "POST",
|
|
898
|
+
headers: {
|
|
899
|
+
"Content-Type": "application/json",
|
|
900
|
+
"x-goog-api-key": this.key
|
|
901
|
+
},
|
|
902
|
+
body: JSON.stringify({
|
|
903
|
+
batch: {
|
|
904
|
+
display_name: "evrex-extraction",
|
|
905
|
+
input_config: {
|
|
906
|
+
requests: {
|
|
907
|
+
requests: items.map((item) => {
|
|
908
|
+
const shape = item.request.schema ? `
|
|
909
|
+
|
|
910
|
+
Respond with JSON matching exactly this schema:
|
|
911
|
+
${JSON.stringify(item.request.schema)}` : "";
|
|
912
|
+
return {
|
|
913
|
+
request: {
|
|
914
|
+
systemInstruction: {
|
|
915
|
+
parts: [{ text: item.request.system + shape }]
|
|
916
|
+
},
|
|
917
|
+
contents: [
|
|
918
|
+
{ role: "user", parts: [{ text: item.request.user }] }
|
|
919
|
+
],
|
|
920
|
+
generationConfig: { responseMimeType: "application/json" }
|
|
921
|
+
},
|
|
922
|
+
metadata: { key: item.id }
|
|
923
|
+
};
|
|
924
|
+
})
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
})
|
|
929
|
+
}
|
|
930
|
+
);
|
|
931
|
+
if (!create.ok) {
|
|
932
|
+
if (isExhaustedStatus(create.status, true)) this.exhausted = true;
|
|
933
|
+
this.logger.warn(
|
|
934
|
+
`google batch submit failed: ${create.status} ${(await create.text()).slice(0, 200)}`
|
|
935
|
+
);
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
const name = (await create.json()).name;
|
|
939
|
+
if (!name) {
|
|
940
|
+
this.logger.warn("google batch: no job name returned");
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
return { provider: "google", id: name, itemIds: items.map((i) => i.id) };
|
|
944
|
+
} catch (err) {
|
|
945
|
+
this.logger.warn(`google batch submit failed: ${describeError(err)}`);
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async resolveBatch(handle) {
|
|
950
|
+
try {
|
|
951
|
+
const poll = await fetch(`${_GoogleClient.GEMINI_BASE}/${handle.id}`, {
|
|
952
|
+
headers: { "x-goog-api-key": this.key }
|
|
953
|
+
});
|
|
954
|
+
if (!poll.ok) {
|
|
955
|
+
this.logger.warn(`google batch poll failed: ${poll.status}`);
|
|
956
|
+
return totalizeIds(handle.itemIds, /* @__PURE__ */ new Map());
|
|
957
|
+
}
|
|
958
|
+
const job = await poll.json();
|
|
959
|
+
const state = job.state ?? "";
|
|
960
|
+
if (state === "JOB_STATE_FAILED" || state === "JOB_STATE_CANCELLED" || state === "JOB_STATE_EXPIRED") {
|
|
961
|
+
this.logger.warn(`google batch ${handle.id} ended ${state}`);
|
|
962
|
+
return totalizeIds(handle.itemIds, /* @__PURE__ */ new Map());
|
|
963
|
+
}
|
|
964
|
+
if (state !== "JOB_STATE_SUCCEEDED") return null;
|
|
965
|
+
const inlined = job.response?.inlinedResponses ?? [];
|
|
966
|
+
const results = /* @__PURE__ */ new Map();
|
|
967
|
+
handle.itemIds.forEach((id, i) => {
|
|
968
|
+
const text = inlined[i]?.response?.candidates?.[0]?.content?.parts?.map((p) => p.text ?? "").join("");
|
|
969
|
+
results.set(id, text ? parseJsonBody(text) : null);
|
|
970
|
+
});
|
|
971
|
+
return totalizeIds(handle.itemIds, results);
|
|
972
|
+
} catch (err) {
|
|
973
|
+
this.logger.warn(`google batch resolve failed: ${describeError(err)}`);
|
|
974
|
+
return totalizeIds(handle.itemIds, /* @__PURE__ */ new Map());
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
async completeJsonBatch(items) {
|
|
978
|
+
if (items.length === 0) return /* @__PURE__ */ new Map();
|
|
979
|
+
const handle = await this.submitBatch(items);
|
|
980
|
+
if (!handle) return totalize(items, /* @__PURE__ */ new Map());
|
|
981
|
+
const resolved = await pollToCompletion(
|
|
982
|
+
handle,
|
|
983
|
+
(h) => this.resolveBatch(h),
|
|
984
|
+
this.sleep,
|
|
985
|
+
(id) => this.logger.warn(
|
|
986
|
+
`google batch ${id} did not finish within the cap; leaving its sessions for a later pass`
|
|
987
|
+
)
|
|
988
|
+
);
|
|
989
|
+
return resolved ?? totalize(items, /* @__PURE__ */ new Map());
|
|
990
|
+
}
|
|
804
991
|
};
|
|
805
992
|
var OpenAiCompatibleClient = class {
|
|
806
|
-
constructor(provider, baseUrl, key, model, logger,
|
|
993
|
+
constructor(provider, baseUrl, key, model, logger, sleep2 = realSleep) {
|
|
807
994
|
this.provider = provider;
|
|
808
995
|
this.baseUrl = baseUrl;
|
|
809
996
|
this.key = key;
|
|
810
997
|
this.model = model;
|
|
811
998
|
this.logger = logger;
|
|
812
|
-
this.sleep =
|
|
999
|
+
this.sleep = sleep2;
|
|
1000
|
+
if (this.provider === "openai") {
|
|
1001
|
+
this.completeJsonBatch = (items) => this.runBatch(items);
|
|
1002
|
+
}
|
|
813
1003
|
}
|
|
814
1004
|
exhausted = false;
|
|
1005
|
+
completeJsonBatch;
|
|
815
1006
|
async completeJson(request2, attempt = 0) {
|
|
816
|
-
const strict = this.provider === "openai";
|
|
817
|
-
const shape = request2.schema && !strict ? `
|
|
818
|
-
|
|
819
|
-
Respond with JSON matching exactly this schema:
|
|
820
|
-
${JSON.stringify(request2.schema)}` : "";
|
|
821
1007
|
try {
|
|
822
1008
|
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
823
1009
|
method: "POST",
|
|
824
|
-
headers:
|
|
825
|
-
|
|
826
|
-
// A local server does not want one, and some reject an empty bearer
|
|
827
|
-
// token outright.
|
|
828
|
-
...this.key ? { Authorization: `Bearer ${this.key}` } : {}
|
|
829
|
-
},
|
|
830
|
-
body: JSON.stringify({
|
|
831
|
-
model: request2.model ?? this.model,
|
|
832
|
-
messages: [
|
|
833
|
-
{ role: "system", content: request2.system + shape },
|
|
834
|
-
{ role: "user", content: request2.user }
|
|
835
|
-
],
|
|
836
|
-
...request2.schema && strict ? {
|
|
837
|
-
response_format: {
|
|
838
|
-
type: "json_schema",
|
|
839
|
-
json_schema: {
|
|
840
|
-
name: "evrex_result",
|
|
841
|
-
strict: true,
|
|
842
|
-
schema: request2.schema
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
} : { response_format: { type: "json_object" } },
|
|
846
|
-
// Extraction is a reading task with a right answer, not a writing
|
|
847
|
-
// task, and a default sampling temperature makes it a different
|
|
848
|
-
// answer each run: one model scored 53% and then 20% on the same ten
|
|
849
|
-
// sessions before this was pinned. Indexing twice must not produce
|
|
850
|
-
// two different histories.
|
|
851
|
-
temperature: 0,
|
|
852
|
-
stream: false
|
|
853
|
-
})
|
|
1010
|
+
headers: this.authHeaders(),
|
|
1011
|
+
body: JSON.stringify(this.chatBody(request2))
|
|
854
1012
|
});
|
|
855
1013
|
if (RETRYABLE_STATUS.has(res.status) && attempt < MAX_RETRIES) {
|
|
856
1014
|
await waitBeforeRetry(res, attempt, this.sleep);
|
|
@@ -871,6 +1029,147 @@ ${JSON.stringify(request2.schema)}` : "";
|
|
|
871
1029
|
return null;
|
|
872
1030
|
}
|
|
873
1031
|
}
|
|
1032
|
+
authHeaders() {
|
|
1033
|
+
return {
|
|
1034
|
+
"Content-Type": "application/json",
|
|
1035
|
+
// A local server does not want one, and some reject an empty bearer token.
|
|
1036
|
+
...this.key ? { Authorization: `Bearer ${this.key}` } : {}
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
// One place a ModelRequest becomes the chat body, so completeJson and the
|
|
1040
|
+
// batch path cannot drift on response_format or the pinned temperature.
|
|
1041
|
+
chatBody(request2) {
|
|
1042
|
+
const strict = this.provider === "openai";
|
|
1043
|
+
const shape = request2.schema && !strict ? `
|
|
1044
|
+
|
|
1045
|
+
Respond with JSON matching exactly this schema:
|
|
1046
|
+
${JSON.stringify(request2.schema)}` : "";
|
|
1047
|
+
return {
|
|
1048
|
+
model: request2.model ?? this.model,
|
|
1049
|
+
messages: [
|
|
1050
|
+
{ role: "system", content: request2.system + shape },
|
|
1051
|
+
{ role: "user", content: request2.user }
|
|
1052
|
+
],
|
|
1053
|
+
...request2.schema && strict ? {
|
|
1054
|
+
response_format: {
|
|
1055
|
+
type: "json_schema",
|
|
1056
|
+
json_schema: {
|
|
1057
|
+
name: "evrex_result",
|
|
1058
|
+
strict: true,
|
|
1059
|
+
schema: request2.schema
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
} : { response_format: { type: "json_object" } },
|
|
1063
|
+
// A reading task with a right answer: temperature pinned so indexing
|
|
1064
|
+
// twice is the same history.
|
|
1065
|
+
temperature: 0,
|
|
1066
|
+
stream: false
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
async runBatch(items) {
|
|
1070
|
+
const results = /* @__PURE__ */ new Map();
|
|
1071
|
+
if (items.length === 0) return results;
|
|
1072
|
+
const auth = this.key ? { Authorization: `Bearer ${this.key}` } : {};
|
|
1073
|
+
try {
|
|
1074
|
+
const jsonl = items.map(
|
|
1075
|
+
(item) => JSON.stringify({
|
|
1076
|
+
custom_id: item.id,
|
|
1077
|
+
method: "POST",
|
|
1078
|
+
url: "/v1/chat/completions",
|
|
1079
|
+
body: this.chatBody(item.request)
|
|
1080
|
+
})
|
|
1081
|
+
).join("\n");
|
|
1082
|
+
const form = new FormData();
|
|
1083
|
+
form.append("purpose", "batch");
|
|
1084
|
+
form.append(
|
|
1085
|
+
"file",
|
|
1086
|
+
new Blob([jsonl], { type: "application/jsonl" }),
|
|
1087
|
+
"evrex-batch.jsonl"
|
|
1088
|
+
);
|
|
1089
|
+
const upload = await fetch(`${this.baseUrl}/files`, {
|
|
1090
|
+
method: "POST",
|
|
1091
|
+
headers: auth,
|
|
1092
|
+
body: form
|
|
1093
|
+
});
|
|
1094
|
+
if (!upload.ok) {
|
|
1095
|
+
if (isExhaustedStatus(upload.status, true)) this.exhausted = true;
|
|
1096
|
+
this.logger.warn(
|
|
1097
|
+
`openai batch upload failed: ${upload.status} ${(await upload.text()).slice(0, 200)}`
|
|
1098
|
+
);
|
|
1099
|
+
return totalize(items, results);
|
|
1100
|
+
}
|
|
1101
|
+
const fileId = (await upload.json()).id;
|
|
1102
|
+
if (!fileId) return totalize(items, results);
|
|
1103
|
+
const created = await fetch(`${this.baseUrl}/batches`, {
|
|
1104
|
+
method: "POST",
|
|
1105
|
+
headers: this.authHeaders(),
|
|
1106
|
+
body: JSON.stringify({
|
|
1107
|
+
input_file_id: fileId,
|
|
1108
|
+
endpoint: "/v1/chat/completions",
|
|
1109
|
+
completion_window: "24h"
|
|
1110
|
+
})
|
|
1111
|
+
});
|
|
1112
|
+
if (!created.ok) {
|
|
1113
|
+
this.logger.warn(
|
|
1114
|
+
`openai batch create failed: ${created.status} ${(await created.text()).slice(0, 200)}`
|
|
1115
|
+
);
|
|
1116
|
+
return totalize(items, results);
|
|
1117
|
+
}
|
|
1118
|
+
const batchId = (await created.json()).id;
|
|
1119
|
+
if (!batchId) return totalize(items, results);
|
|
1120
|
+
let status = "";
|
|
1121
|
+
let outputFileId;
|
|
1122
|
+
const startedAt = Date.now();
|
|
1123
|
+
while (status !== "completed") {
|
|
1124
|
+
if (Date.now() - startedAt > BATCH_TIMEOUT_MS) {
|
|
1125
|
+
this.logger.warn(
|
|
1126
|
+
`openai batch ${batchId} did not finish within the cap; leaving its sessions for a later pass`
|
|
1127
|
+
);
|
|
1128
|
+
return totalize(items, results);
|
|
1129
|
+
}
|
|
1130
|
+
await this.sleep(BATCH_POLL_MS);
|
|
1131
|
+
const poll = await fetch(`${this.baseUrl}/batches/${batchId}`, {
|
|
1132
|
+
headers: auth
|
|
1133
|
+
});
|
|
1134
|
+
if (!poll.ok) {
|
|
1135
|
+
this.logger.warn(`openai batch poll failed: ${poll.status}`);
|
|
1136
|
+
return totalize(items, results);
|
|
1137
|
+
}
|
|
1138
|
+
const job = await poll.json();
|
|
1139
|
+
status = job.status ?? "";
|
|
1140
|
+
outputFileId = job.output_file_id;
|
|
1141
|
+
if (status === "failed" || status === "expired" || status === "cancelled") {
|
|
1142
|
+
this.logger.warn(`openai batch ${batchId} ended ${status}`);
|
|
1143
|
+
return totalize(items, results);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (!outputFileId) return totalize(items, results);
|
|
1147
|
+
const out = await fetch(`${this.baseUrl}/files/${outputFileId}/content`, {
|
|
1148
|
+
headers: auth
|
|
1149
|
+
});
|
|
1150
|
+
if (!out.ok) {
|
|
1151
|
+
this.logger.warn(`openai batch results fetch failed: ${out.status}`);
|
|
1152
|
+
return totalize(items, results);
|
|
1153
|
+
}
|
|
1154
|
+
for (const line of (await out.text()).split("\n")) {
|
|
1155
|
+
if (!line.trim()) continue;
|
|
1156
|
+
try {
|
|
1157
|
+
const row = JSON.parse(line);
|
|
1158
|
+
const content = row.response?.body?.choices?.[0]?.message?.content;
|
|
1159
|
+
if (row.custom_id) {
|
|
1160
|
+
results.set(
|
|
1161
|
+
row.custom_id,
|
|
1162
|
+
typeof content === "string" ? parseJsonBody(content) : null
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
} catch {
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
} catch (err) {
|
|
1169
|
+
this.logger.warn(`openai batch failed: ${describeError(err)}`);
|
|
1170
|
+
}
|
|
1171
|
+
return totalize(items, results);
|
|
1172
|
+
}
|
|
874
1173
|
};
|
|
875
1174
|
function describeError(err) {
|
|
876
1175
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -882,9 +1181,9 @@ function createModelClient(key, options = {}) {
|
|
|
882
1181
|
const model = options.model ?? DEFAULT_MODELS[provider];
|
|
883
1182
|
const trimmed = key.trim();
|
|
884
1183
|
if (provider === "anthropic") return new AnthropicClient(trimmed, model, logger);
|
|
885
|
-
const
|
|
1184
|
+
const sleep2 = options.sleep ?? realSleep;
|
|
886
1185
|
if (provider === "google")
|
|
887
|
-
return new GoogleClient(trimmed, model, logger,
|
|
1186
|
+
return new GoogleClient(trimmed, model, logger, sleep2);
|
|
888
1187
|
const cli = CLI_SPECS[provider];
|
|
889
1188
|
if (cli) return new CliClient(cli, model, logger);
|
|
890
1189
|
if (provider === "local") {
|
|
@@ -894,7 +1193,7 @@ function createModelClient(key, options = {}) {
|
|
|
894
1193
|
null,
|
|
895
1194
|
model,
|
|
896
1195
|
logger,
|
|
897
|
-
|
|
1196
|
+
sleep2
|
|
898
1197
|
);
|
|
899
1198
|
}
|
|
900
1199
|
const baseUrl = options.baseUrl ?? OPENAI_COMPATIBLE_BASE_URLS[provider];
|
|
@@ -905,7 +1204,7 @@ function createModelClient(key, options = {}) {
|
|
|
905
1204
|
trimmed,
|
|
906
1205
|
model,
|
|
907
1206
|
logger,
|
|
908
|
-
|
|
1207
|
+
sleep2
|
|
909
1208
|
);
|
|
910
1209
|
}
|
|
911
1210
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evrex-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "MCP server that gives coding agents the recorded reasoning behind a repo: prior decisions, hard constraints, and approaches already rejected.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -36,27 +36,28 @@
|
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=20"
|
|
38
38
|
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"bundle": "node scripts/bundle.mjs",
|
|
42
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
43
|
+
"lint": "eslint .",
|
|
44
|
+
"check-types": "tsc --noEmit",
|
|
45
|
+
"test": "tsc -p tsconfig.json && node --test dist/*.test.js",
|
|
46
|
+
"prepack": "pnpm run bundle"
|
|
47
|
+
},
|
|
39
48
|
"dependencies": {
|
|
40
49
|
"@anthropic-ai/sdk": "^0.72.0",
|
|
41
50
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
42
51
|
"zod": "^3.24.1"
|
|
43
52
|
},
|
|
44
53
|
"devDependencies": {
|
|
54
|
+
"@repo/eslint-config": "workspace:*",
|
|
55
|
+
"@repo/ingest-core": "workspace:*",
|
|
56
|
+
"@repo/llm-core": "workspace:*",
|
|
57
|
+
"@repo/typescript-config": "workspace:*",
|
|
45
58
|
"@types/node": "^24.0.0",
|
|
46
59
|
"esbuild": "^0.25.12",
|
|
47
60
|
"eslint": "^9.39.1",
|
|
48
|
-
"typescript": "^5.9.2"
|
|
49
|
-
"@repo/eslint-config": "0.0.0",
|
|
50
|
-
"@repo/ingest-core": "0.0.0",
|
|
51
|
-
"@repo/llm-core": "0.0.0",
|
|
52
|
-
"@repo/typescript-config": "0.0.0"
|
|
53
|
-
},
|
|
54
|
-
"scripts": {
|
|
55
|
-
"build": "tsc -p tsconfig.json",
|
|
56
|
-
"bundle": "node scripts/bundle.mjs",
|
|
57
|
-
"dev": "tsc -p tsconfig.json --watch",
|
|
58
|
-
"lint": "eslint .",
|
|
59
|
-
"check-types": "tsc --noEmit",
|
|
60
|
-
"test": "tsc -p tsconfig.json && node --test dist/*.test.js"
|
|
61
|
+
"typescript": "^5.9.2"
|
|
61
62
|
}
|
|
62
|
-
}
|
|
63
|
+
}
|