@livx.cc/agentx 0.99.58 → 0.99.59

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as AgentOptions, H as Hooks, i as RunResult, A as Agent } from './Agent-CXkPfYX2.js';
2
- export { C as ChatFragment, b as CompactResult, D as DEFAULT_MUTATING, c as Decision, P as PermissionOptions, d as PermissionPolicy, e as PermissionRule, f as PreToolUseDecision, R as ReasoningEffort, g as RecordingHooks, h as RecordingLifecycle, T as ToolUse, j as ToolUseMeta, k as composeHooks, l as estimateTokens, p as planMode, r as reasoningToChatFragment } from './Agent-CXkPfYX2.js';
1
+ import { a as AgentOptions, H as Hooks, n as RunResult, A as Agent } from './Agent-DqFJubs5.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-DqFJubs5.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). fast/standard run the main session; premium is only ever an offload. */
10476
+ models = { fast: "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 "fast";
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.fast ? "fast" : "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
+ fast: "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,