@mrciphersmith/keryx 0.2.21 → 0.2.23

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.
Files changed (2) hide show
  1. package/dist/cli.js +108 -21
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8045,6 +8045,23 @@ function isProviderPlatformSupported(provider, platform = process.platform) {
8045
8045
  }
8046
8046
  return provider.platforms.includes(platform);
8047
8047
  }
8048
+ function providerBaseUrlEnvKey(providerName) {
8049
+ return `KERYX_${providerName.replace(/[^a-zA-Z0-9]+/g, "_").toUpperCase()}_BASE_URL`;
8050
+ }
8051
+ function resolveProviderBaseUrl(provider, env = process.env) {
8052
+ const override = env[providerBaseUrlEnvKey(provider.name)]?.trim();
8053
+ if (override === undefined || override.length === 0)
8054
+ return provider.baseUrl;
8055
+ try {
8056
+ const url = new URL(override);
8057
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0) {
8058
+ return provider.baseUrl;
8059
+ }
8060
+ return override.replace(/\/+$/, "");
8061
+ } catch {
8062
+ return provider.baseUrl;
8063
+ }
8064
+ }
8048
8065
  function providerByName(name) {
8049
8066
  return OPENAI_COMPAT_PROVIDERS.find((p) => p.name === name);
8050
8067
  }
@@ -8091,7 +8108,7 @@ async function resolveModelsForPicker(fetchFn, provider, env = process.env, opts
8091
8108
  const envKey = provider.envKey ?? compat.envKey;
8092
8109
  const raw = envKey === undefined ? undefined : env[envKey];
8093
8110
  const apiKey = typeof raw === "string" && raw.length > 0 ? raw : undefined;
8094
- return fetchOpenAiCompatModelsDetailed(fetchFn, compat, apiKey, opts);
8111
+ return fetchOpenAiCompatModelsDetailed(fetchFn, { ...compat, ...provider.baseUrl !== undefined ? { baseUrl: provider.baseUrl } : {} }, apiKey, opts);
8095
8112
  }
8096
8113
  var DEFAULT_MODELS_PATH = "/v1/models", OPENAI_COMPAT_PROVIDERS, MODELS_FETCH_TIMEOUT_MS = 1e4;
8097
8114
  var init_providers = __esm(() => {
@@ -9867,7 +9884,7 @@ function makeProvider(name, _model, opts) {
9867
9884
  }
9868
9885
  const grant = {
9869
9886
  network: true,
9870
- baseUrl: opts.baseUrl ?? compat.baseUrl,
9887
+ baseUrl: opts.baseUrl ?? resolveProviderBaseUrl(compat, env),
9871
9888
  ...compat.allowLoopback === true ? { allowLoopback: true } : {},
9872
9889
  ...compat.chatPath !== undefined ? { chatPath: compat.chatPath } : {},
9873
9890
  ...apiKey !== undefined ? { apiKey } : {}
@@ -39576,6 +39593,7 @@ function reserveToolAttempt(state, name, input2, risk) {
39576
39593
  }
39577
39594
  async function runAgentTurn(io, deps, history, userLine, options = {}) {
39578
39595
  history.push({ role: "user", content: userLine, provenance: "project" });
39596
+ io.onHistoryChange?.("user");
39579
39597
  const signal = options.signal;
39580
39598
  const isAborted = () => signal?.aborted === true;
39581
39599
  if (isAborted()) {
@@ -39628,6 +39646,7 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
39628
39646
  };
39629
39647
  const request = signal === undefined ? { ...baseRequest } : { ...baseRequest, signal };
39630
39648
  let assistantText = "";
39649
+ let assistantMessage;
39631
39650
  let reasoningText = "";
39632
39651
  let reasoningFlushed = false;
39633
39652
  const flushReasoning = () => {
@@ -39655,6 +39674,13 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
39655
39674
  const text = event.text ?? "";
39656
39675
  io.write(text);
39657
39676
  assistantText += text;
39677
+ if (assistantMessage === undefined) {
39678
+ assistantMessage = { role: "assistant", content: text, provenance: "model" };
39679
+ history.push(assistantMessage);
39680
+ } else {
39681
+ assistantMessage.content += text;
39682
+ }
39683
+ io.onHistoryChange?.("assistant_delta");
39658
39684
  } else if (event.kind === "tool_call_start") {
39659
39685
  if (event.toolCallId !== undefined && event.toolName !== undefined) {
39660
39686
  nameById.set(event.toolCallId, event.toolName);
@@ -39695,8 +39721,8 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
39695
39721
  }
39696
39722
  flushReasoning();
39697
39723
  if (assistantText.length > 0) {
39698
- history.push({ role: "assistant", content: assistantText, provenance: "model" });
39699
39724
  io.onAssistantText?.(assistantText);
39725
+ io.onHistoryChange?.("assistant_final");
39700
39726
  }
39701
39727
  if (isAborted()) {
39702
39728
  system(`
@@ -39719,6 +39745,7 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
39719
39745
  content: "[system] You were asked to execute or inspect, but you replied with text and no tool call. " + "Resend a single compliant tool call now (with fully populated required arguments).",
39720
39746
  provenance: "project"
39721
39747
  });
39748
+ io.onHistoryChange?.("tool");
39722
39749
  continue;
39723
39750
  }
39724
39751
  if (shouldReprompt) {
@@ -39749,6 +39776,7 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
39749
39776
  const result2 = { output: reservation.reason, isError: true };
39750
39777
  io.onToolResult?.(call.name, result2);
39751
39778
  history.push({ role: "tool", content: result2.output, provenance: "tool" });
39779
+ io.onHistoryChange?.("tool");
39752
39780
  toolLog.push(`${call.name}: skipped (${reservation.reason.split(";")[0] ?? "budget"})`);
39753
39781
  if (reservation.kind === "total_budget") {
39754
39782
  exhaustedBudget = "total";
@@ -39763,6 +39791,7 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
39763
39791
  const result = await executeCall(call, toolByName, io.requestApproval);
39764
39792
  io.onToolResult?.(call.name, result);
39765
39793
  history.push({ role: "tool", content: redactSensitiveText(result.output), provenance: "tool" });
39794
+ io.onHistoryChange?.("tool");
39766
39795
  const shortIn = call.input.length > 80 ? `${call.input.slice(0, 77)}\u2026` : call.input;
39767
39796
  const riskUsage = risk === "read" ? `, read ${readBudgetUsed(budget)}/${maxReadToolCalls}` : `, non-read ${nonReadBudgetUsed(budget)}/${maxNonReadToolCalls}`;
39768
39797
  toolLog.push(`${call.name}(${shortIn}) \u2192 ${result.isError ? "error" : "ok"} [attempt ${reservation.attempt}/${maxAttempts}, unique ${budgetUsed(budget)}/${maxToolCalls}${riskUsage}]`);
@@ -39778,6 +39807,7 @@ async function runAgentTurn(io, deps, history, userLine, options = {}) {
39778
39807
  ${hint}
39779
39808
  `);
39780
39809
  history.push({ role: "user", content: hint, provenance: "project" });
39810
+ io.onHistoryChange?.("tool");
39781
39811
  }
39782
39812
  } else {
39783
39813
  lastErrorByHash.delete(reservation.hash);
@@ -44131,10 +44161,13 @@ async function launchTuiAgentShell(opts) {
44131
44161
  let liveSession;
44132
44162
  let history = [];
44133
44163
  let archive = [];
44164
+ let nextArchiveIndex = 0;
44165
+ let sessionPersistTimer;
44134
44166
  const applyOpened = (opened, previewHistory) => {
44135
44167
  liveSession = opened.handle;
44136
44168
  history = previewHistory === true ? opened.history.slice(-SESSION_PREVIEW_MESSAGE_COUNT) : opened.history;
44137
44169
  archive = opened.archive.length > 0 ? [...opened.archive] : [...opened.history];
44170
+ nextArchiveIndex = history.length;
44138
44171
  };
44139
44172
  const pickRecentSession = async () => {
44140
44173
  const rows = listSessions(sessionCwd);
@@ -44260,6 +44293,36 @@ async function launchTuiAgentShell(opts) {
44260
44293
  });
44261
44294
  paintSessionHeader();
44262
44295
  };
44296
+ const syncArchive = () => {
44297
+ while (nextArchiveIndex < history.length) {
44298
+ const message2 = history[nextArchiveIndex];
44299
+ if (message2 !== undefined) {
44300
+ archive.push(message2);
44301
+ }
44302
+ nextArchiveIndex += 1;
44303
+ }
44304
+ };
44305
+ const flushSessionCheckpoint = () => {
44306
+ if (sessionPersistTimer !== undefined) {
44307
+ clearTimeout(sessionPersistTimer);
44308
+ sessionPersistTimer = undefined;
44309
+ }
44310
+ syncArchive();
44311
+ saveSession();
44312
+ };
44313
+ io.onHistoryChange = (kind) => {
44314
+ syncArchive();
44315
+ if (kind === "assistant_delta") {
44316
+ if (sessionPersistTimer === undefined) {
44317
+ sessionPersistTimer = setTimeout(() => {
44318
+ sessionPersistTimer = undefined;
44319
+ saveSession();
44320
+ }, 300);
44321
+ }
44322
+ return;
44323
+ }
44324
+ flushSessionCheckpoint();
44325
+ };
44263
44326
  const startNewSession = (note2) => {
44264
44327
  liveSession = createSession({
44265
44328
  cwd: sessionCwd,
@@ -44268,6 +44331,7 @@ async function launchTuiAgentShell(opts) {
44268
44331
  });
44269
44332
  history = [];
44270
44333
  archive = [];
44334
+ nextArchiveIndex = 0;
44271
44335
  paintSessionHeader();
44272
44336
  if (note2 !== undefined && note2.length > 0) {
44273
44337
  io.onSystem?.(`${note2}
@@ -44558,6 +44622,7 @@ Staying in the current session.
44558
44622
  });
44559
44623
  liveSession = packed.handle;
44560
44624
  history = packed.context;
44625
+ nextArchiveIndex = history.length;
44561
44626
  paintSessionHeader();
44562
44627
  if (packed.result.noop) {
44563
44628
  io.onSystem?.(`Nothing to compact (context already small).
@@ -44779,7 +44844,6 @@ Staying in the current session.
44779
44844
  }
44780
44845
  prevOnSystem?.(text);
44781
44846
  };
44782
- const beforeLen = history.length;
44783
44847
  const controller = new AbortController;
44784
44848
  mainTurnAbortController = controller;
44785
44849
  runAgentTurn(io, deps, history, line, { signal: controller.signal }).finally(() => {
@@ -44787,14 +44851,8 @@ Staying in the current session.
44787
44851
  const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
44788
44852
  stopBusy();
44789
44853
  setMainAgent(turnFailed ? "failed" : "done", turnFailed ? "error" : "idle");
44790
- for (let i = beforeLen;i < history.length; i++) {
44791
- const m = history[i];
44792
- if (m !== undefined) {
44793
- archive.push(m);
44794
- }
44795
- }
44796
44854
  try {
44797
- saveSession();
44855
+ flushSessionCheckpoint();
44798
44856
  } catch {}
44799
44857
  transcript.add(new otui.TextRenderable(r, { id: `w${uid++}`, content: otui.t`${otui.dim(`worked for ${secs}s`)}`, marginTop: 1 }));
44800
44858
  if (!hasExactUsage) {
@@ -45137,7 +45195,7 @@ init_shell_config();
45137
45195
  // package.json
45138
45196
  var package_default = {
45139
45197
  name: "@mrciphersmith/keryx",
45140
- version: "0.2.21",
45198
+ version: "0.2.23",
45141
45199
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
45142
45200
  private: false,
45143
45201
  publishConfig: {
@@ -45277,7 +45335,7 @@ async function detectProviders(deps) {
45277
45335
  detected.push({
45278
45336
  name: p.name,
45279
45337
  models: [...p.models],
45280
- baseUrl: p.baseUrl,
45338
+ baseUrl: resolveProviderBaseUrl(p, deps.env),
45281
45339
  label: p.label,
45282
45340
  ...p.chatPath !== undefined ? { chatPath: p.chatPath } : {},
45283
45341
  ...p.modelsPath !== undefined ? { modelsPath: p.modelsPath } : {},
@@ -46048,6 +46106,8 @@ ${GUTTER}${style.cyan(`\u2699 ${call}`)}
46048
46106
  let live;
46049
46107
  let history = [];
46050
46108
  let archive = [];
46109
+ let nextArchiveIndex = 0;
46110
+ let sessionPersistTimer;
46051
46111
  if (sessionsOn) {
46052
46112
  try {
46053
46113
  let resumeId = sessionOpts?.resumeId;
@@ -46062,6 +46122,7 @@ ${GUTTER}${style.cyan(`\u2699 ${call}`)}
46062
46122
  live = opened.handle;
46063
46123
  history = opened.history;
46064
46124
  archive = opened.archive.length > 0 ? [...opened.archive] : [...opened.history];
46125
+ nextArchiveIndex = history.length;
46065
46126
  if (opened.resumed) {
46066
46127
  agentIo.onSystem?.(`Resumed session ${shortSessionId(live.summary.id)} \xB7 ${live.summary.title} (${history.length} context \xB7 archive ${archive.length})
46067
46128
  `);
@@ -46077,6 +46138,7 @@ ${GUTTER}${style.cyan(`\u2699 ${call}`)}
46077
46138
  });
46078
46139
  history = [];
46079
46140
  archive = [];
46141
+ nextArchiveIndex = 0;
46080
46142
  agentIo.onSystem?.(`${cause instanceof Error ? cause.message : String(cause)}
46081
46143
  New session ${shortSessionId(live.summary.id)}.
46082
46144
  `);
@@ -46094,6 +46156,36 @@ New session ${shortSessionId(live.summary.id)}.
46094
46156
  });
46095
46157
  } catch {}
46096
46158
  };
46159
+ const syncArchive = () => {
46160
+ while (nextArchiveIndex < history.length) {
46161
+ const message2 = history[nextArchiveIndex];
46162
+ if (message2 !== undefined) {
46163
+ archive.push(message2);
46164
+ }
46165
+ nextArchiveIndex += 1;
46166
+ }
46167
+ };
46168
+ const flushSessionCheckpoint = () => {
46169
+ if (sessionPersistTimer !== undefined) {
46170
+ clearTimeout(sessionPersistTimer);
46171
+ sessionPersistTimer = undefined;
46172
+ }
46173
+ syncArchive();
46174
+ save();
46175
+ };
46176
+ agentIo.onHistoryChange = (kind) => {
46177
+ syncArchive();
46178
+ if (kind === "assistant_delta") {
46179
+ if (sessionPersistTimer === undefined) {
46180
+ sessionPersistTimer = setTimeout(() => {
46181
+ sessionPersistTimer = undefined;
46182
+ save();
46183
+ }, 300);
46184
+ }
46185
+ return;
46186
+ }
46187
+ flushSessionCheckpoint();
46188
+ };
46097
46189
  for (;; ) {
46098
46190
  const line = await readLine();
46099
46191
  if (line === undefined) {
@@ -46127,6 +46219,7 @@ New session ${shortSessionId(live.summary.id)}.
46127
46219
  });
46128
46220
  history = [];
46129
46221
  archive = [];
46222
+ nextArchiveIndex = 0;
46130
46223
  agentIo.onSystem?.(`New session ${shortSessionId(live.summary.id)} (previous kept on disk)
46131
46224
  `);
46132
46225
  } else {
@@ -46148,6 +46241,7 @@ New session ${shortSessionId(live.summary.id)}.
46148
46241
  });
46149
46242
  live = packed.handle;
46150
46243
  history = packed.context;
46244
+ nextArchiveIndex = history.length;
46151
46245
  if (packed.result.noop) {
46152
46246
  agentIo.onSystem?.(`Nothing to compact (context already small).
46153
46247
  `);
@@ -46171,7 +46265,6 @@ New session ${shortSessionId(live.summary.id)}.
46171
46265
  ${GUTTER}${style.cyan("\u25CF")} ${style.bold("keryx")}
46172
46266
  `);
46173
46267
  lastUsage = undefined;
46174
- const before = history.length;
46175
46268
  startSpinner();
46176
46269
  try {
46177
46270
  await runAgentTurn(agentIo, deps, history, line);
@@ -46179,13 +46272,7 @@ ${GUTTER}${style.cyan("\u25CF")} ${style.bold("keryx")}
46179
46272
  endBlock();
46180
46273
  stopSpinner();
46181
46274
  }
46182
- for (let i = before;i < history.length; i++) {
46183
- const m = history[i];
46184
- if (m !== undefined) {
46185
- archive.push(m);
46186
- }
46187
- }
46188
- save();
46275
+ flushSessionCheckpoint();
46189
46276
  const usageLine = formatUsage(lastUsage);
46190
46277
  if (usageLine.length > 0) {
46191
46278
  out(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {