@livx.cc/agentx 0.99.58 → 0.99.60
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/{Agent-CXkPfYX2.d.ts → Agent-DbqXQy5q.d.ts} +104 -2
- package/dist/cli.d.ts +16 -3
- package/dist/cli.js +359 -87
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +250 -0
- package/dist/index.js.map +1 -1
- package/dist/models.js +1 -0
- package/dist/models.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as AgentOptions, H as Hooks,
|
|
2
|
-
export { C as ChatFragment, b as CompactResult, D as DEFAULT_MUTATING,
|
|
1
|
+
import { a as AgentOptions, H as Hooks, n as RunResult, A as Agent } from './Agent-DbqXQy5q.js';
|
|
2
|
+
export { C as ChatFragment, b as Classification, c as Classifier, d as CompactResult, D as DEFAULT_MUTATING, e as Decision, M as ModelRouter, f as ModelRouterOptions, P as PermissionOptions, g as PermissionPolicy, h as PermissionRule, i as PreToolUseDecision, R as ReasoningEffort, j as RecordingHooks, k as RecordingLifecycle, l as RouteDecision, m as RouterState, T as Tier, o as ToolUse, p as ToolUseMeta, q as composeHooks, r as estimateTokens, s as jevAvailable, t as jevClassifier, u as pickTier, v as planMode, w as reasoningToChatFragment } from './Agent-DbqXQy5q.js';
|
|
3
3
|
export { MODEL_ALIASES, ModelSwitch, ResolveModelSwitchOpts, modelShortLabel, resolveModelAlias, resolveModelSwitch } from './models.js';
|
|
4
4
|
import { IFilesystem, FileMetadata } from '@livx.cc/wcli/core';
|
|
5
5
|
export { CommandExecutor, FileMetadata, IFilesystem, IndexedDbFilesystem, MemFilesystem, registerHeadlessCommands } from '@livx.cc/wcli/core';
|
package/dist/index.js
CHANGED
|
@@ -3391,6 +3391,7 @@ function resolveModelAlias(input) {
|
|
|
3391
3391
|
const slash = input.indexOf("/");
|
|
3392
3392
|
const prefix = slash === -1 ? "" : input.slice(0, slash + 1);
|
|
3393
3393
|
const rest = slash === -1 ? input : input.slice(slash + 1);
|
|
3394
|
+
if (prefix === "claude-code/") return input;
|
|
3394
3395
|
return prefix + (MODEL_ALIASES[rest.toLowerCase()] ?? rest);
|
|
3395
3396
|
}
|
|
3396
3397
|
function modelProvider(model) {
|
|
@@ -10465,6 +10466,250 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10465
10466
|
}
|
|
10466
10467
|
};
|
|
10467
10468
|
|
|
10469
|
+
// src/modelRouter.ts
|
|
10470
|
+
init_llm();
|
|
10471
|
+
init_logging();
|
|
10472
|
+
var log22 = forComponent("ModelRouter");
|
|
10473
|
+
var ModelRouterOptions = class {
|
|
10474
|
+
classifier = jevClassifier();
|
|
10475
|
+
/** Capability tiers → model id (any provider). basic/standard run the main session; premium is only ever an offload. */
|
|
10476
|
+
models = { basic: "anthropic/claude-haiku-4-5-20251001", standard: "anthropic/claude-sonnet-4-6", premium: "anthropic/claude-opus-5-5" };
|
|
10477
|
+
/** Escalate to a stronger tier when it holds at least this much probability mass (upward bias). */
|
|
10478
|
+
threshold = 0.25;
|
|
10479
|
+
/** P(same thread) at or above which the previous premium side-conversation is resumed. */
|
|
10480
|
+
sameThreadAt = 0.5;
|
|
10481
|
+
classifyTimeoutMs = 1e4;
|
|
10482
|
+
/** Host-supplied pricing (model, usage) → USD, so mixed-model turns are costed per model. */
|
|
10483
|
+
costOf = () => 0;
|
|
10484
|
+
/** Step budget for the premium sub-agent. */
|
|
10485
|
+
offloadMaxSteps = 40;
|
|
10486
|
+
/** Host-supplied provider options per model (e.g. claude-code's subscription env); applied on every tier switch + the offload child. */
|
|
10487
|
+
providerOptionsFor;
|
|
10488
|
+
};
|
|
10489
|
+
function pickTier(c, th) {
|
|
10490
|
+
const o = c.probs.premium ?? 0, s = c.probs.standard ?? 0;
|
|
10491
|
+
if (o >= th || c.choice === "premium") return "premium";
|
|
10492
|
+
if (c.choice === "standard" || s + o >= th) return "standard";
|
|
10493
|
+
return "basic";
|
|
10494
|
+
}
|
|
10495
|
+
function textTurns(msgs) {
|
|
10496
|
+
const out = [];
|
|
10497
|
+
for (const m of msgs) {
|
|
10498
|
+
if (m.role === "user") {
|
|
10499
|
+
const t = contentText(m.content).trim();
|
|
10500
|
+
if (t) out.push({ user: t, assistant: "" });
|
|
10501
|
+
} else if (m.role === "assistant" && out.length) {
|
|
10502
|
+
const t = contentText(m.content).trim();
|
|
10503
|
+
if (t) out[out.length - 1].assistant = (out[out.length - 1].assistant + "\n" + t).trim();
|
|
10504
|
+
}
|
|
10505
|
+
}
|
|
10506
|
+
return out;
|
|
10507
|
+
}
|
|
10508
|
+
var fmtTurns = (ts) => ts.map((t) => `USER: ${t.user}
|
|
10509
|
+
ASSISTANT: ${t.assistant || "(no text reply)"}`).join("\n\n");
|
|
10510
|
+
var addUsage = (a, b) => {
|
|
10511
|
+
if (!a) return b;
|
|
10512
|
+
if (!b) return a;
|
|
10513
|
+
return {
|
|
10514
|
+
promptTokens: a.promptTokens + b.promptTokens,
|
|
10515
|
+
completionTokens: a.completionTokens + b.completionTokens,
|
|
10516
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
10517
|
+
cacheCreationTokens: (a.cacheCreationTokens ?? 0) + (b.cacheCreationTokens ?? 0),
|
|
10518
|
+
cacheReadTokens: (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0)
|
|
10519
|
+
};
|
|
10520
|
+
};
|
|
10521
|
+
var ModelRouter = class {
|
|
10522
|
+
constructor(options) {
|
|
10523
|
+
this.options = options;
|
|
10524
|
+
this.options = { ...new ModelRouterOptions(), ...options };
|
|
10525
|
+
}
|
|
10526
|
+
options;
|
|
10527
|
+
state = {};
|
|
10528
|
+
get o() {
|
|
10529
|
+
return this.options;
|
|
10530
|
+
}
|
|
10531
|
+
/** Classify one turn. Never throws: classifier failure/timeout ⇒ standard. */
|
|
10532
|
+
async decide(transcript, next) {
|
|
10533
|
+
const o = this.o;
|
|
10534
|
+
const main = this.state.main === o.models.basic ? "basic" : "standard";
|
|
10535
|
+
const input = {
|
|
10536
|
+
previousModel: main,
|
|
10537
|
+
history: textTurns(transcript).slice(-4).map((t) => ({ user: t.user.slice(0, 1500), assistant: t.assistant.slice(0, 1500) })),
|
|
10538
|
+
next,
|
|
10539
|
+
lastOffload: this.state.offload?.question
|
|
10540
|
+
};
|
|
10541
|
+
const t0 = performance.now();
|
|
10542
|
+
let d;
|
|
10543
|
+
try {
|
|
10544
|
+
let timer;
|
|
10545
|
+
const c = await Promise.race([
|
|
10546
|
+
o.classifier(input),
|
|
10547
|
+
new Promise((_, rej) => {
|
|
10548
|
+
timer = setTimeout(() => rej(new Error(`classifier timeout ${o.classifyTimeoutMs}ms`)), o.classifyTimeoutMs);
|
|
10549
|
+
})
|
|
10550
|
+
]).finally(() => clearTimeout(timer));
|
|
10551
|
+
const tier = pickTier(c, o.threshold);
|
|
10552
|
+
d = { tier, model: o.models[tier], probs: c.probs, choice: c.choice, sameThread: c.sameThread, classifyMs: Math.round(performance.now() - t0) };
|
|
10553
|
+
if (tier === "premium") d.offload = this.state.offload && (c.sameThread ?? 0) >= o.sameThreadAt ? "continue" : "fresh";
|
|
10554
|
+
} catch (e) {
|
|
10555
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
10556
|
+
log22.warn(`classifier failed, falling back to standard: ${msg}`);
|
|
10557
|
+
d = { tier: "standard", model: o.models.standard, probs: {}, fallback: msg, classifyMs: Math.round(performance.now() - t0) };
|
|
10558
|
+
}
|
|
10559
|
+
log22.info(`route ${d.tier}${d.offload ? `(${d.offload})` : ""} \xB7 argmax=${d.choice ?? "-"} \xB7 p=${JSON.stringify(d.probs)}${d.sameThread != null ? ` \xB7 same=${d.sameThread.toFixed(2)}` : ""} \xB7 ${d.classifyMs}ms`);
|
|
10560
|
+
return d;
|
|
10561
|
+
}
|
|
10562
|
+
/** Run one user turn on `agent` with routing. Returns a RunResult carrying `costUsd` + `routing`. */
|
|
10563
|
+
async send(agent, content) {
|
|
10564
|
+
const o = this.o;
|
|
10565
|
+
const text = contentText(content);
|
|
10566
|
+
const d = await this.decide(agent.transcript, text);
|
|
10567
|
+
if (d.tier !== "premium") {
|
|
10568
|
+
this.use(agent, this.state.main = d.model);
|
|
10569
|
+
const res = await agent.send(content);
|
|
10570
|
+
return { ...res, costUsd: o.costOf(d.model, res.usage), routing: d };
|
|
10571
|
+
}
|
|
10572
|
+
this.use(agent, this.state.main ??= o.models.standard);
|
|
10573
|
+
return this.offload(agent, content, text, d);
|
|
10574
|
+
}
|
|
10575
|
+
async offload(agent, content, text, d) {
|
|
10576
|
+
const o = this.o, main = this.state.main;
|
|
10577
|
+
const prev = this.state.offload;
|
|
10578
|
+
const cont = d.offload === "continue" && prev && prev.mainLen <= agent.transcript.length;
|
|
10579
|
+
let briefUsage, prompt;
|
|
10580
|
+
if (cont) {
|
|
10581
|
+
const delta = textTurns(agent.transcript.slice(prev.mainLen));
|
|
10582
|
+
prompt = `Follow-up in the same thread.${delta.length ? ` Since your last answer the main conversation continued:
|
|
10583
|
+
|
|
10584
|
+
${fmtTurns(delta)}
|
|
10585
|
+
` : ""}
|
|
10586
|
+
|
|
10587
|
+
New request:
|
|
10588
|
+
${text}`;
|
|
10589
|
+
} else {
|
|
10590
|
+
const b = await this.brief(agent, text);
|
|
10591
|
+
briefUsage = b.usage;
|
|
10592
|
+
const last2 = textTurns(agent.transcript).slice(-2);
|
|
10593
|
+
prompt = `You are handling a hard turn delegated by the main agent of an ongoing session. You do NOT see its conversation \u2014 only this brief. Do the work with your tools, then reply to the user directly (your reply is shown as the session's answer).
|
|
10594
|
+
|
|
10595
|
+
--- brief from the main agent ---
|
|
10596
|
+
${b.text}
|
|
10597
|
+
|
|
10598
|
+
${last2.length ? `--- last turns (verbatim) ---
|
|
10599
|
+
${fmtTurns(last2)}
|
|
10600
|
+
|
|
10601
|
+
` : ""}--- current user request (verbatim) ---
|
|
10602
|
+
${text}`;
|
|
10603
|
+
}
|
|
10604
|
+
const child = this.spawn(agent);
|
|
10605
|
+
const res = cont ? (child.transcript = prev.messages, await child.send(prompt)) : await child.run(prompt);
|
|
10606
|
+
this.state.offloads = (this.state.offloads ?? 0) + 1;
|
|
10607
|
+
if (cont) this.state.reuses = (this.state.reuses ?? 0) + 1;
|
|
10608
|
+
const answer = res.text || `(premium sub-agent finished with no reply; finishReason=${res.finishReason})`;
|
|
10609
|
+
agent.transcript.push({ role: "user", content }, { role: "assistant", content: answer });
|
|
10610
|
+
this.state.offload = { messages: child.transcript, mainLen: agent.transcript.length, question: text };
|
|
10611
|
+
log22.info(`offload ${cont ? "continue" : "fresh"} done \xB7 ${res.finishReason} \xB7 ${res.steps} steps`);
|
|
10612
|
+
const costUsd = o.costOf(main, briefUsage) + o.costOf(o.models.premium, res.usage);
|
|
10613
|
+
return { ...res, text: answer, messages: agent.transcript, usage: addUsage(briefUsage, res.usage), costUsd, routing: d };
|
|
10614
|
+
}
|
|
10615
|
+
/** The main model writes the task brief on its own (cached) context, tools off; the transcript is restored after. */
|
|
10616
|
+
async brief(agent, text) {
|
|
10617
|
+
const snap = agent.transcript.slice();
|
|
10618
|
+
const { host, toolChoice, maxSteps } = agent.options;
|
|
10619
|
+
Object.assign(agent.options, { host: void 0, toolChoice: "none", maxSteps: 1 });
|
|
10620
|
+
try {
|
|
10621
|
+
const r = await agent.send(`[router] The next request is being delegated to a stronger sub-agent that will NOT see this conversation. Write the self-contained brief you would hand it (<=200 words): the task, the relevant facts/files/decisions established so far, constraints. Output only the brief.
|
|
10622
|
+
|
|
10623
|
+
The request:
|
|
10624
|
+
${text}`);
|
|
10625
|
+
return { text: r.text.trim() || text, usage: r.usage };
|
|
10626
|
+
} catch (e) {
|
|
10627
|
+
log22.warn(`brief failed, sending the raw request: ${e instanceof Error ? e.message : String(e)}`);
|
|
10628
|
+
return { text };
|
|
10629
|
+
} finally {
|
|
10630
|
+
agent.transcript = snap;
|
|
10631
|
+
Object.assign(agent.options, { host, toolChoice, maxSteps });
|
|
10632
|
+
}
|
|
10633
|
+
}
|
|
10634
|
+
use(agent, model) {
|
|
10635
|
+
agent.options.model = model;
|
|
10636
|
+
if (this.o.providerOptionsFor) agent.options.providerOptions = this.o.providerOptionsFor(model);
|
|
10637
|
+
}
|
|
10638
|
+
spawn(agent) {
|
|
10639
|
+
const a = agent.options;
|
|
10640
|
+
const co = childOptionsFor(
|
|
10641
|
+
{ ai: a.ai, model: a.model, fs: a.fs, tools: a.tools, systemPrompt: a.systemPrompt, hooks: a.hooks, maxSteps: this.o.offloadMaxSteps, traceDir: a.traceDir, providerOptionsFor: this.o.providerOptionsFor },
|
|
10642
|
+
a.fs,
|
|
10643
|
+
a.depth ?? 0,
|
|
10644
|
+
a.maxDepth ?? 2,
|
|
10645
|
+
void 0,
|
|
10646
|
+
this.o.models.premium
|
|
10647
|
+
);
|
|
10648
|
+
if (typeof co === "string") throw new Error(co);
|
|
10649
|
+
co.signal = a.signal;
|
|
10650
|
+
return new Agent(co);
|
|
10651
|
+
}
|
|
10652
|
+
};
|
|
10653
|
+
var JEV_KEYS = ["AI_GATEWAY_API_KEY", "OPENROUTER_API_KEY", "TYPESAFE_API_KEY"];
|
|
10654
|
+
async function loadJev(opts) {
|
|
10655
|
+
const mod = await import(opts.jevModule ?? process.env.AGENTX_JEV_MODULE ?? "@livx.cc/mcp-jev/jev");
|
|
10656
|
+
const env = { ...process.env };
|
|
10657
|
+
const file = opts.envFile ?? process.env.AGENTX_JEV_ENV;
|
|
10658
|
+
if (file) {
|
|
10659
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
10660
|
+
for (const l of readFileSync3(file, "utf8").split("\n")) {
|
|
10661
|
+
const m = l.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
|
|
10662
|
+
if (m && !env[m[1]]) env[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10665
|
+
return { mod, env };
|
|
10666
|
+
}
|
|
10667
|
+
async function jevAvailable(opts = {}) {
|
|
10668
|
+
try {
|
|
10669
|
+
const { env } = await loadJev(opts);
|
|
10670
|
+
return JEV_KEYS.some((k) => env[k]?.trim());
|
|
10671
|
+
} catch (e) {
|
|
10672
|
+
log22.debug(`jev unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
10673
|
+
return false;
|
|
10674
|
+
}
|
|
10675
|
+
}
|
|
10676
|
+
function jevClassifier(opts = {}) {
|
|
10677
|
+
let router;
|
|
10678
|
+
const load = async () => {
|
|
10679
|
+
const { mod, env } = await loadJev(opts);
|
|
10680
|
+
return new mod.JevRouter({ env });
|
|
10681
|
+
};
|
|
10682
|
+
return async (input) => {
|
|
10683
|
+
const jev = await (router ??= load().catch((e) => {
|
|
10684
|
+
router = void 0;
|
|
10685
|
+
throw e;
|
|
10686
|
+
}));
|
|
10687
|
+
const questions = [{
|
|
10688
|
+
id: "tier",
|
|
10689
|
+
type: "choice",
|
|
10690
|
+
instructions: "Pick the CHEAPEST model that will handle the NEXT assistant turn correctly for this coding-agent conversation. Staying on previousModel keeps its prompt cache warm; switch only when the next turn clearly needs more (or much less) capability.",
|
|
10691
|
+
criteria: {
|
|
10692
|
+
basic: "Trivial: commands, lookups, acknowledgements, commits, mechanical single-file edits, short rewrites.",
|
|
10693
|
+
standard: "Standard engineering: features, bounded refactors, reviews, tests, following a clear trace, summarising.",
|
|
10694
|
+
premium: "Hard: root-causing subtle/intermittent bugs, concurrency, security semantics, architecture trade-offs, quantitative reasoning."
|
|
10695
|
+
}
|
|
10696
|
+
}];
|
|
10697
|
+
if (input.lastOffload) questions.push({
|
|
10698
|
+
id: "sameThread",
|
|
10699
|
+
type: "boolean",
|
|
10700
|
+
instructions: "Does nextUserMessage continue the same line of work as lastOffload (the previous hard request, handled by a specialist) \u2014 a follow-up, refinement or next step of it \u2014 rather than a new, unrelated topic?"
|
|
10701
|
+
});
|
|
10702
|
+
const state = { previousModel: input.previousModel, history: input.history, nextUserMessage: input.next, ...input.lastOffload ? { lastOffload: input.lastOffload } : {} };
|
|
10703
|
+
const r = await jev.evaluate({ state, questions }, opts.provider);
|
|
10704
|
+
const a = r.answers.tier;
|
|
10705
|
+
const map = (k) => k;
|
|
10706
|
+
const probs = {};
|
|
10707
|
+
for (const [k, v] of Object.entries(a.probabilities ?? {})) probs[map(k)] = v;
|
|
10708
|
+
const st = r.answers.sameThread;
|
|
10709
|
+
return { choice: map(a.choice), probs, sameThread: st ? st.probability ?? (st.value ? 1 : 0) : void 0 };
|
|
10710
|
+
};
|
|
10711
|
+
}
|
|
10712
|
+
|
|
10468
10713
|
// src/mcp.ts
|
|
10469
10714
|
function toResult(result) {
|
|
10470
10715
|
if (result == null) return { text: "" };
|
|
@@ -10730,6 +10975,8 @@ export {
|
|
|
10730
10975
|
MEMORY_PROMPT,
|
|
10731
10976
|
MODEL_ALIASES,
|
|
10732
10977
|
MemFilesystem3 as MemFilesystem,
|
|
10978
|
+
ModelRouter,
|
|
10979
|
+
ModelRouterOptions,
|
|
10733
10980
|
MountFilesystem,
|
|
10734
10981
|
NodeDiskFilesystem,
|
|
10735
10982
|
OverlayFilesystem,
|
|
@@ -10791,6 +11038,8 @@ export {
|
|
|
10791
11038
|
imagePart,
|
|
10792
11039
|
imageRefPart,
|
|
10793
11040
|
imageRefResult,
|
|
11041
|
+
jevAvailable,
|
|
11042
|
+
jevClassifier,
|
|
10794
11043
|
lessonCapture,
|
|
10795
11044
|
loadAgents,
|
|
10796
11045
|
loadCommands,
|
|
@@ -10825,6 +11074,7 @@ export {
|
|
|
10825
11074
|
parseCron,
|
|
10826
11075
|
parseDdgHtml,
|
|
10827
11076
|
parseImageRef,
|
|
11077
|
+
pickTier,
|
|
10828
11078
|
planMode,
|
|
10829
11079
|
raceAttempts,
|
|
10830
11080
|
readTool,
|