archymedes-cli 3.3.0 → 3.4.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.
@@ -8446,7 +8446,48 @@ function repeatedFailureHint(toolName, failures, missingPathStreak) {
8446
8446
  return null;
8447
8447
  }
8448
8448
  var MAX_VERIFICATION_NUDGES = 1;
8449
- var MAX_UNAVAILABLE_TOOL_RECOVERIES = 1;
8449
+ var MAX_UNAVAILABLE_TOOL_RECOVERIES = 3;
8450
+ function suggestTools(name, tools) {
8451
+ const words = name.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 2);
8452
+ const scored = tools.map((tool) => {
8453
+ const own = tool.name.toLowerCase();
8454
+ const ownWords = own.split(/[^a-z0-9]+/);
8455
+ const description = tool.description.toLowerCase();
8456
+ let score = 0;
8457
+ for (const word of words) {
8458
+ if (ownWords.includes(word))
8459
+ score += 3;
8460
+ else if (own.includes(word))
8461
+ score += 2;
8462
+ else if (new RegExp(`\\b${word}`).test(description))
8463
+ score += 1;
8464
+ }
8465
+ if (score === 0 && editDistance(name.toLowerCase(), own) <= 2)
8466
+ score = 2;
8467
+ return { name: tool.name, score };
8468
+ });
8469
+ return scored.filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, 2).map((entry) => entry.name);
8470
+ }
8471
+ function editDistance(a, b) {
8472
+ if (Math.abs(a.length - b.length) > 2)
8473
+ return 3;
8474
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
8475
+ for (let i = 1;i <= a.length; i += 1) {
8476
+ const current = [i];
8477
+ for (let j = 1;j <= b.length; j += 1) {
8478
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
8479
+ }
8480
+ previous = current;
8481
+ }
8482
+ return previous[b.length];
8483
+ }
8484
+ function unavailableToolMessage(name, exists, available) {
8485
+ const names = available.map((tool) => tool.name);
8486
+ const closest = suggestTools(name, available);
8487
+ const head = exists ? `${name} is not allowed in this run (the current mode does not include it).` : `There is no tool named "${name}".`;
8488
+ const hint = closest.length > 0 ? ` The closest: ${closest.join(" or ")}.` : "";
8489
+ return `${head}${hint} Tools you can call: ${names.join(", ")}. Use one of them, or answer without a tool.`;
8490
+ }
8450
8491
  var PROVIDER_BACKOFF_BASE_MS = 1000;
8451
8492
  var PROVIDER_BACKOFF_CAP_MS = 30000;
8452
8493
  var PROVIDER_RETRY_AFTER_CAP_MS = 60000;
@@ -8774,6 +8815,7 @@ class BoundedAgentRuntime {
8774
8815
  let workspaceNeedsVerification = false;
8775
8816
  let verificationNudges = 0;
8776
8817
  let unavailableToolRecoveries = 0;
8818
+ let lastUnavailable = "";
8777
8819
  let toolTurnRecoveries = 0;
8778
8820
  let lengthContinuations = 0;
8779
8821
  let strongestEvidence = 0;
@@ -8964,6 +9006,7 @@ ${compactedToolResult(entry.name, 0, undefined, images)}` : compactedToolResult(
8964
9006
  return false;
8965
9007
  toolTurnRecoveries += 1;
8966
9008
  messages.push({ role: "user", content: `${reason} ${instruction}`, internal: true });
9009
+ lastOutcomes = [{ tool: "(malformed call)", isError: true, shallow: true }];
8967
9010
  return true;
8968
9011
  };
8969
9012
  if (turn.finishReason !== "tool_calls" || turn.toolCalls.length === 0) {
@@ -8995,10 +9038,12 @@ ${compactedToolResult(entry.name, 0, undefined, images)}` : compactedToolResult(
8995
9038
  return !tool || !capabilities.has(tool.capabilityId);
8996
9039
  });
8997
9040
  if (unavailable) {
8998
- if (unavailableToolRecoveries >= MAX_UNAVAILABLE_TOOL_RECOVERIES) {
9041
+ if (unavailable.name === lastUnavailable || unavailableToolRecoveries >= MAX_UNAVAILABLE_TOOL_RECOVERIES) {
8999
9042
  return stop("failed", `Tool ${unavailable.name} is outside the run capability scope.`, iteration);
9000
9043
  }
9001
9044
  unavailableToolRecoveries += 1;
9045
+ lastUnavailable = unavailable.name;
9046
+ lastOutcomes = [{ tool: unavailable.name, isError: true, shallow: true }];
9002
9047
  messages.push({ role: "assistant", content: turn.content, toolCalls: turn.toolCalls });
9003
9048
  for (const call of turn.toolCalls) {
9004
9049
  messages.push({
@@ -9006,11 +9051,13 @@ ${compactedToolResult(entry.name, 0, undefined, images)}` : compactedToolResult(
9006
9051
  toolCallId: call.id,
9007
9052
  name: call.name,
9008
9053
  internal: true,
9009
- content: call.id === unavailable.id ? `Tool ${call.name} is unavailable in the current mode. Continue with the listed tools, or answer without it.` : "This batch was not executed because another call was unavailable. Retry any still-needed available call separately."
9054
+ content: call.id === unavailable.id ? unavailableToolMessage(call.name, this.toolsByName.has(call.name), tools) : "This batch was not executed because another call was unavailable. Retry any still-needed available call separately."
9010
9055
  });
9011
9056
  }
9012
9057
  continue;
9013
9058
  }
9059
+ unavailableToolRecoveries = 0;
9060
+ lastUnavailable = "";
9014
9061
  const prepared = [];
9015
9062
  for (const call of turn.toolCalls) {
9016
9063
  const tool = this.toolsByName.get(call.name);
@@ -9091,7 +9138,7 @@ ${compactedToolResult(entry.name, 0, undefined, images)}` : compactedToolResult(
9091
9138
  const signature = `${call.name}:${JSON.stringify(call.arguments ?? {})}`;
9092
9139
  const failures = (failedCalls.get(signature) ?? 0) + 1;
9093
9140
  failedCalls.set(signature, failures);
9094
- missingPathStreak = /does not exist|No such file|not found/i.test(raw) ? missingPathStreak + 1 : 0;
9141
+ missingPathStreak = /does not exist|No such file or directory|ENOENT/i.test(raw) ? missingPathStreak + 1 : 0;
9095
9142
  const hint = repeatedFailureHint(call.name, failures, missingPathStreak);
9096
9143
  if (hint)
9097
9144
  content = `${content}
@@ -9105,6 +9152,7 @@ ${hint}`;
9105
9152
  if (!result.isError && effect === "workspace") {
9106
9153
  workspaceNeedsVerification = true;
9107
9154
  greenSinceEdit = false;
9155
+ failedCalls.clear();
9108
9156
  }
9109
9157
  lastOutcomes.push({
9110
9158
  tool: call.name,
@@ -44,7 +44,7 @@ import {
44
44
  wanderGradeCounts,
45
45
  wanderPlannerInstructions,
46
46
  withOpenRouterCacheControl
47
- } from "./archymedes-j0vrbr99.mjs";
47
+ } from "./archymedes-ds224xzf.mjs";
48
48
  import {
49
49
  __require
50
50
  } from "./archymedes-x67v3n58.mjs";
@@ -8028,7 +8028,7 @@ var TEST_COMMAND = /\b(tests?|pytest|jest|vitest|mocha|jasmine|karma|ava|rspec|m
8028
8028
  var CHECK_COMMAND = /\b(typecheck|tsc|lint|eslint|pylint|ruff|flake8|mypy|pyright|check|build|compile|py_compile|vet|clippy|rubocop|audit|fmt|format)\b/i;
8029
8029
  var BEHAVIOR_COMMAND = /\b(e2e|end-to-end|playwright|cypress|puppeteer|selenium|webdriver|testcafe|nightwatch|integration|behave|cucumber|supertest|testcontainers)\b/i;
8030
8030
  var SMOKE_COMMAND = /\b(smoke|healthcheck|health[-_ ]?check|curl|wget|httpie|\bhttp\s+(?:get|post)\b|playwright\s+screenshot)\b/i;
8031
- var PERSISTENT_COMMAND = /(?:^|(?:&&|;|\|)\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve)(?:\s|$)|(?:vite|next\s+dev|webpack\s+serve)(?:\s|$)|bun\s+run\s+--hot\b)|(?:^|\s)--watch(?:\s|$)|\b(?:tsc|jest|vitest|mocha|sass|tailwindcss|esbuild|webpack|rollup|nodemon|parcel|tsup|swc)\b[^|;&\n]*\s-w(?:\s|$)/i;
8031
+ var PERSISTENT_COMMAND = /(?:^|(?:&&|;|\|)\s*)(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve)(?:\s|$)|(?:vite|next\s+dev|webpack\s+serve)(?:\s|$)|bun\s+run\s+--hot\b)|(?:^|\s)--watch(?:\s|$)|\b(?:tsc|vitest|mocha|tailwindcss|webpack|rollup|nodemon|tsup|swc)\b[^|;&\n]*\s-w(?:\s|$)/i;
8032
8032
  var CD_PREFIX = /^\s*cd\s+(?:[^&\n|;]|\&(?!\&))+&&\s*/;
8033
8033
  function verificationTarget(command) {
8034
8034
  let target = command;
@@ -10386,10 +10386,12 @@ class ArchymedesAgent {
10386
10386
  get todos() {
10387
10387
  return this.todoList.list();
10388
10388
  }
10389
- diffPatch() {
10389
+ async diffPatch() {
10390
+ await this.pendingCheckpoint;
10390
10391
  return this.checkpoints.diffPatch();
10391
10392
  }
10392
- diffStat() {
10393
+ async diffStat() {
10394
+ await this.pendingCheckpoint;
10393
10395
  return this.checkpoints.diffStat();
10394
10396
  }
10395
10397
  listFiles(pattern = "**/*") {
@@ -10501,6 +10503,7 @@ class ArchymedesAgent {
10501
10503
  if (this.turnAbort)
10502
10504
  throw new Error("Cannot undo while a turn is running");
10503
10505
  await this.acquireOwnership();
10506
+ await this.pendingCheckpoint;
10504
10507
  const checkpoint = this.checkpoints.latest();
10505
10508
  if (!checkpoint)
10506
10509
  return;
@@ -10652,6 +10655,7 @@ ${requested}` : requested;
10652
10655
  }).catch(() => {
10653
10656
  return;
10654
10657
  });
10658
+ this.pendingCheckpoint = checkpointReady;
10655
10659
  }
10656
10660
  const compaction = await this.compactIfNeeded(turnId, objective, turnAbort.signal, tokenSaver);
10657
10661
  compactionActualRwf = compaction.actualRwf;
@@ -10660,10 +10664,10 @@ ${requested}` : requested;
10660
10664
  const runtime = new BoundedAgentRuntime({
10661
10665
  model: this.recovery.wrap(this.options.model, () => this.session, this.options.prices, "main"),
10662
10666
  ...this.options.reasoning === "auto" ? { effortPolicy: (signals) => predictEffort({ ...signals, objective, ...this.jevDifficulty ? { difficultyHint: this.jevDifficulty } : {} }) } : {},
10663
- tools: scoped.map((tool) => tool.effect === "none" ? tool : { ...tool, execute: async (args, context) => {
10667
+ tools: scoped.map((tool) => ({ ...tool, execute: async (args, context) => {
10664
10668
  await checkpointReady;
10665
10669
  return tool.execute(args, context);
10666
- } }),
10670
+ } })),
10667
10671
  prices: this.options.prices,
10668
10672
  artifacts: this.artifacts,
10669
10673
  control: {
@@ -10824,6 +10828,7 @@ ${hookWarnings.join(`
10824
10828
  return (verdict.outcomeProbabilities[verdict.outcome] ?? 0) >= 0.5;
10825
10829
  }
10826
10830
  jevCorrectionDepth = 0;
10831
+ pendingCheckpoint = Promise.resolve();
10827
10832
  jevDifficulty;
10828
10833
  difficultyStamp = 0;
10829
10834
  startDifficultyReading(objective, signal) {
@@ -12491,6 +12496,8 @@ async function readBody(response) {
12491
12496
  }
12492
12497
 
12493
12498
  // ../core/src/providers/request-pacer.ts
12499
+ var MIN_LEARNABLE_CAP = 8;
12500
+ var LEARNED_CAP_LIFETIME_WINDOWS = 5;
12494
12501
  var DEFAULTS = { baseGapMs: 2000, maxGapMs: 120000, releaseMs: 500 };
12495
12502
 
12496
12503
  class RequestPacer {
@@ -12501,6 +12508,8 @@ class RequestPacer {
12501
12508
  releaseMs;
12502
12509
  windowMs;
12503
12510
  cap;
12511
+ configuredCap;
12512
+ learnedAt = 0;
12504
12513
  starts = [];
12505
12514
  now;
12506
12515
  sleep;
@@ -12510,6 +12519,7 @@ class RequestPacer {
12510
12519
  this.releaseMs = options.releaseMs ?? DEFAULTS.releaseMs;
12511
12520
  this.windowMs = options.windowMs ?? 60000;
12512
12521
  this.cap = options.windowCap !== undefined && options.windowCap >= 1 ? Math.floor(options.windowCap) : undefined;
12522
+ this.configuredCap = this.cap;
12513
12523
  this.now = options.now ?? Date.now;
12514
12524
  this.sleep = options.sleep ?? ((delayMs, signal) => new Promise((resolve, reject) => {
12515
12525
  if (signal?.aborted) {
@@ -12546,6 +12556,10 @@ class RequestPacer {
12546
12556
  }
12547
12557
  async wait(signal) {
12548
12558
  signal?.throwIfAborted();
12559
+ if (this.learnedAt > 0 && this.now() - this.learnedAt > LEARNED_CAP_LIFETIME_WINDOWS * this.windowMs) {
12560
+ this.cap = this.configuredCap;
12561
+ this.learnedAt = 0;
12562
+ }
12549
12563
  if (this.cap !== undefined) {
12550
12564
  for (;; ) {
12551
12565
  const now = this.now();
@@ -12573,8 +12587,10 @@ class RequestPacer {
12573
12587
  reportRateLimited(retryAfterMs) {
12574
12588
  this.prune(this.now());
12575
12589
  const admitted = this.starts.length - 1;
12576
- if (admitted >= 2)
12577
- this.cap = Math.min(this.cap ?? Number.POSITIVE_INFINITY, admitted);
12590
+ if (admitted >= MIN_LEARNABLE_CAP && admitted < (this.cap ?? Number.POSITIVE_INFINITY)) {
12591
+ this.cap = admitted;
12592
+ this.learnedAt = this.now();
12593
+ }
12578
12594
  const doubled = this.gapMs <= 0 ? this.baseGapMs : Math.min(this.maxGapMs, this.gapMs * 2);
12579
12595
  const asked = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? retryAfterMs : 0;
12580
12596
  this.gapMs = Math.min(this.maxGapMs, Math.max(doubled, Math.min(this.maxGapMs, asked)));
@@ -15438,65 +15454,6 @@ async function showFreePrivacyNoticeOnce(options) {
15438
15454
  return next;
15439
15455
  }
15440
15456
 
15441
- // src/app/acp-setup.ts
15442
- var KEY_URLS = {
15443
- free: OPENROUTER_KEYS_URL2,
15444
- openrouter: OPENROUTER_KEYS_URL2,
15445
- anthropic: "https://console.anthropic.com/settings/keys",
15446
- openai: "https://platform.openai.com/api-keys",
15447
- google: "https://aistudio.google.com/apikey",
15448
- xai: "https://console.x.ai",
15449
- deepseek: "https://platform.deepseek.com/api_keys",
15450
- mistral: "https://console.mistral.ai/api-keys",
15451
- groq: "https://console.groq.com/keys"
15452
- };
15453
- var ORDER = ["free", ...PROVIDER_IDS.filter((id) => id !== "free")];
15454
- function setupStatus(environment, requested = {}) {
15455
- const resolved = resolveProvider(environment, requested);
15456
- const providers = ORDER.map((id) => {
15457
- const info = PROVIDER_INFO[id];
15458
- const keyOnly = info.requires.length === 1 && SETTING_FIELDS.some((field) => field.key === info.requires[0]);
15459
- return {
15460
- id,
15461
- label: info.label,
15462
- ...keyOnly ? { keyVariable: info.requires[0] } : {},
15463
- ...KEY_URLS[id] ? { keyUrl: KEY_URLS[id] } : {},
15464
- configured: missingRequirements(id, environment).length === 0
15465
- };
15466
- });
15467
- return "error" in resolved ? { configured: false, reason: resolved.error, providers } : { configured: true, providers };
15468
- }
15469
- async function saveSetup(params, deps) {
15470
- const provider = typeof params.provider === "string" ? params.provider : "";
15471
- const raw = typeof params.key === "string" ? params.key.trim() : "";
15472
- if (!isProviderId(provider))
15473
- return { ok: false, error: `Unknown provider "${provider}".` };
15474
- const target = setupStatus(deps.environment).providers.find((candidate) => candidate.id === provider);
15475
- if (!target?.keyVariable)
15476
- return { ok: false, error: `${PROVIDER_INFO[provider].label} needs more than a key; run archymedes settings.` };
15477
- if (!raw)
15478
- return { ok: false, error: "The key is empty." };
15479
- let key;
15480
- try {
15481
- key = validateSetting(target.keyVariable, raw);
15482
- } catch (error) {
15483
- return { ok: false, error: error instanceof Error ? error.message : String(error) };
15484
- }
15485
- let note;
15486
- if (provider === "free" || provider === "openrouter") {
15487
- const result = await (deps.checkOpenRouter ?? ((value) => checkOpenRouterKey(value)))(key);
15488
- if (!result.ok && result.reason === "invalid")
15489
- return { ok: false, error: describeKeyCheckFailure(result) };
15490
- if (!result.ok)
15491
- note = `${describeKeyCheckFailure(result)} Saved anyway; the first request will confirm it.`;
15492
- }
15493
- const current = await (deps.load ?? (() => loadSettings(deps.processEnvironment)))();
15494
- const settings = { ...current, [target.keyVariable]: key, ARCHYMEDES_PROVIDER: provider };
15495
- const file = await (deps.save ?? ((value) => saveSettings(value, deps.processEnvironment)))(settings);
15496
- Object.assign(deps.environment, mergedEnvironment(settings, deps.processEnvironment));
15497
- return { ok: true, file, ...note ? { note } : {} };
15498
- }
15499
-
15500
15457
  // ../core/src/providers/model-fetch.ts
15501
15458
  import { promises as fs16 } from "node:fs";
15502
15459
  import path21 from "node:path";
@@ -15699,6 +15656,205 @@ function fetchableProviders(environment, providers) {
15699
15656
  return providers.filter((provider) => provider !== "archymedes-cloud" && missingRequirements(provider, environment).length === 0);
15700
15657
  }
15701
15658
 
15659
+ // src/app/acp-connect.ts
15660
+ var DEFAULT_BASE_URLS = {
15661
+ anthropic: "https://api.anthropic.com",
15662
+ openai: "https://api.openai.com/v1",
15663
+ openrouter: "https://openrouter.ai/api/v1",
15664
+ google: "https://generativelanguage.googleapis.com/v1beta/openai",
15665
+ xai: "https://api.x.ai/v1",
15666
+ deepseek: "https://api.deepseek.com/v1",
15667
+ mistral: "https://api.mistral.ai/v1",
15668
+ groq: "https://api.groq.com/openai/v1",
15669
+ ollama: "http://localhost:11434/v1"
15670
+ };
15671
+ var fieldSpec = (key) => SETTING_FIELDS.find((field) => field.key === key);
15672
+ function providerFields(provider, environment) {
15673
+ const required = PROVIDER_INFO[provider].requires;
15674
+ const baseUrl = provider === "free" ? undefined : `${providerEnvPrefix(provider)}_BASE_URL`;
15675
+ const keys = [...required, ...baseUrl && !required.includes(baseUrl) && fieldSpec(baseUrl) ? [baseUrl] : []];
15676
+ return keys.flatMap((key) => {
15677
+ const spec = fieldSpec(key);
15678
+ if (!spec)
15679
+ return [];
15680
+ const secret = "secret" in spec && spec.secret === true;
15681
+ const url = "url" in spec && spec.url === true;
15682
+ const value = environment[key]?.trim() ?? "";
15683
+ const placeholder = url ? DEFAULT_BASE_URLS[provider] ?? "https://…/v1" : undefined;
15684
+ return [{
15685
+ key: spec.key,
15686
+ label: spec.label,
15687
+ kind: secret ? "secret" : url ? "url" : "text",
15688
+ required: required.includes(key),
15689
+ isSet: value !== "",
15690
+ value: secret && value ? maskSetting(value) : value,
15691
+ ...placeholder ? { placeholder } : {}
15692
+ }];
15693
+ });
15694
+ }
15695
+ var refused = (error) => /\b40[13]\b/.test(error ?? "");
15696
+ async function connectProvider(params, deps) {
15697
+ const provider = typeof params.provider === "string" ? params.provider : "";
15698
+ if (!isProviderId(provider))
15699
+ return { ok: false, error: `Unknown provider "${provider}".` };
15700
+ const label3 = PROVIDER_INFO[provider].label;
15701
+ const values = params.values && typeof params.values === "object" ? params.values : {};
15702
+ const fields = providerFields(provider, deps.environment);
15703
+ const current = await (deps.load ?? (() => loadSettings(deps.processEnvironment)))();
15704
+ const next = { ...current };
15705
+ for (const field of fields) {
15706
+ const raw = typeof values[field.key] === "string" ? values[field.key].trim() : undefined;
15707
+ if (raw === undefined || raw === "") {
15708
+ if (field.kind === "url" && raw === "")
15709
+ delete next[field.key];
15710
+ continue;
15711
+ }
15712
+ try {
15713
+ next[field.key] = validateSetting(field.key, raw);
15714
+ } catch (error) {
15715
+ return { ok: false, field: field.key, error: error instanceof Error ? error.message : String(error) };
15716
+ }
15717
+ }
15718
+ const candidate = mergedEnvironment(next, deps.processEnvironment);
15719
+ const missing = missingRequirements(provider, candidate);
15720
+ if (missing.length > 0)
15721
+ return { ok: false, field: missing[0], error: `${label3} still needs ${missing.join(" and ")}.` };
15722
+ const keyField = fields.find((field) => field.kind === "secret")?.key;
15723
+ let note;
15724
+ if ((provider === "free" || provider === "openrouter") && candidate.OPENROUTER_API_KEY) {
15725
+ const check = await (deps.checkOpenRouter ?? ((value) => checkOpenRouterKey(value)))(candidate.OPENROUTER_API_KEY);
15726
+ if (!check.ok && check.reason === "invalid")
15727
+ return { ok: false, field: keyField, error: describeKeyCheckFailure(check) };
15728
+ if (!check.ok)
15729
+ note = `${describeKeyCheckFailure(check)} Saved anyway; the first request will confirm it.`;
15730
+ }
15731
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
15732
+ const listed = await fetchProviderModels(provider, candidate, fetchImpl, 8000);
15733
+ if (listed.models.length === 0 && refused(listed.error)) {
15734
+ return { ok: false, field: keyField, error: `${label3} refused the key (${listed.error}). Check it and paste it again.` };
15735
+ }
15736
+ if (listed.models.length === 0) {
15737
+ note = note ?? `Could not list ${label3}'s models (${listed.error ?? "none returned"}). Saved; check the base URL if this persists.`;
15738
+ }
15739
+ const file = await (deps.save ?? ((value) => saveSettings(value, deps.processEnvironment)))(next);
15740
+ Object.assign(deps.environment, mergedEnvironment(next, deps.processEnvironment));
15741
+ if (listed.models.length > 0)
15742
+ await rememberModels(provider, listed.models, deps.environment);
15743
+ return { ok: true, file, models: listed.models, ...note ? { note } : {} };
15744
+ }
15745
+ async function providerModels(params, deps) {
15746
+ const provider = typeof params.provider === "string" ? params.provider : "";
15747
+ if (!isProviderId(provider))
15748
+ return { models: [], error: `Unknown provider "${provider}".` };
15749
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
15750
+ const listed = await fetchProviderModels(provider, deps.environment, fetchImpl, 8000);
15751
+ if (listed.models.length > 0)
15752
+ await rememberModels(provider, listed.models, deps.environment);
15753
+ return { models: listed.models, ...listed.error ? { error: listed.error } : {} };
15754
+ }
15755
+ async function rememberModels(provider, models, environment) {
15756
+ try {
15757
+ const cache = await readModelCache(environment);
15758
+ await writeModelCache(environment, {
15759
+ fetchedAt: cache?.fetchedAt ?? Date.now(),
15760
+ ...cache?.freeCatalog ? { freeCatalog: cache.freeCatalog } : {},
15761
+ models: { ...cache?.models ?? {}, [provider]: models }
15762
+ });
15763
+ } catch {}
15764
+ }
15765
+ async function rememberModel(params, deps) {
15766
+ const provider = typeof params.provider === "string" ? params.provider : "";
15767
+ const model = typeof params.model === "string" ? params.model.trim() : "";
15768
+ if (!isProviderId(provider) || !model)
15769
+ return { ok: false, error: "A provider and a model are needed." };
15770
+ const key = `${providerEnvPrefix(provider)}_MODEL`;
15771
+ if (!fieldSpec(key))
15772
+ return { ok: true };
15773
+ let value;
15774
+ try {
15775
+ value = validateSetting(key, model);
15776
+ } catch (error) {
15777
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
15778
+ }
15779
+ const current = await (deps.load ?? (() => loadSettings(deps.processEnvironment)))();
15780
+ const next = { ...current, [key]: value };
15781
+ await (deps.save ?? ((settings) => saveSettings(settings, deps.processEnvironment)))(next);
15782
+ Object.assign(deps.environment, mergedEnvironment(next, deps.processEnvironment));
15783
+ return { ok: true };
15784
+ }
15785
+
15786
+ // src/app/acp-setup.ts
15787
+ var KEY_URLS = {
15788
+ free: OPENROUTER_KEYS_URL2,
15789
+ openrouter: OPENROUTER_KEYS_URL2,
15790
+ anthropic: "https://console.anthropic.com/settings/keys",
15791
+ openai: "https://platform.openai.com/api-keys",
15792
+ google: "https://aistudio.google.com/apikey",
15793
+ xai: "https://console.x.ai",
15794
+ deepseek: "https://platform.deepseek.com/api_keys",
15795
+ mistral: "https://console.mistral.ai/api-keys",
15796
+ groq: "https://console.groq.com/keys"
15797
+ };
15798
+ var ORDER = ["free", ...PROVIDER_IDS.filter((id) => id !== "free")];
15799
+ function setupStatus(environment, requested = {}) {
15800
+ const resolved = resolveProvider(environment, requested);
15801
+ const providers = ORDER.map((id) => {
15802
+ const info = PROVIDER_INFO[id];
15803
+ const keyOnly = info.requires.length === 1 && SETTING_FIELDS.some((field) => field.key === info.requires[0]);
15804
+ return {
15805
+ id,
15806
+ label: info.label,
15807
+ ...keyOnly ? { keyVariable: info.requires[0] } : {},
15808
+ ...KEY_URLS[id] ? { keyUrl: KEY_URLS[id] } : {},
15809
+ configured: missingRequirements(id, environment).length === 0
15810
+ };
15811
+ });
15812
+ return "error" in resolved ? { configured: false, reason: resolved.error, providers } : { configured: true, providers };
15813
+ }
15814
+ async function saveSetup(params, deps) {
15815
+ const provider = typeof params.provider === "string" ? params.provider : "";
15816
+ const raw = typeof params.key === "string" ? params.key.trim() : "";
15817
+ if (!isProviderId(provider))
15818
+ return { ok: false, error: `Unknown provider "${provider}".` };
15819
+ const target = setupStatus(deps.environment).providers.find((candidate) => candidate.id === provider);
15820
+ if (!target?.keyVariable)
15821
+ return { ok: false, error: `${PROVIDER_INFO[provider].label} needs more than a key; run archymedes settings.` };
15822
+ if (!raw)
15823
+ return { ok: false, error: "The key is empty." };
15824
+ let key;
15825
+ try {
15826
+ key = validateSetting(target.keyVariable, raw);
15827
+ } catch (error) {
15828
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
15829
+ }
15830
+ let note;
15831
+ if (provider === "free" || provider === "openrouter") {
15832
+ const result = await (deps.checkOpenRouter ?? ((value) => checkOpenRouterKey(value)))(key);
15833
+ if (!result.ok && result.reason === "invalid")
15834
+ return { ok: false, error: describeKeyCheckFailure(result) };
15835
+ if (!result.ok)
15836
+ note = `${describeKeyCheckFailure(result)} Saved anyway; the first request will confirm it.`;
15837
+ }
15838
+ const current = await (deps.load ?? (() => loadSettings(deps.processEnvironment)))();
15839
+ const settings = { ...current, [target.keyVariable]: key, ARCHYMEDES_PROVIDER: provider };
15840
+ const file = await (deps.save ?? ((value) => saveSettings(value, deps.processEnvironment)))(settings);
15841
+ Object.assign(deps.environment, mergedEnvironment(settings, deps.processEnvironment));
15842
+ return { ok: true, file, ...note ? { note } : {} };
15843
+ }
15844
+ function providerOverview(environment) {
15845
+ const preferred = environment.ARCHYMEDES_PROVIDER?.trim().toLowerCase();
15846
+ return setupStatus(environment).providers.map((provider) => {
15847
+ const info = PROVIDER_INFO[provider.id];
15848
+ return {
15849
+ ...provider,
15850
+ missing: missingRequirements(provider.id, environment),
15851
+ model: environment[`${providerEnvPrefix(provider.id)}_MODEL`]?.trim() || info.defaultModel,
15852
+ isDefault: preferred === provider.id,
15853
+ fields: providerFields(provider.id, environment)
15854
+ };
15855
+ });
15856
+ }
15857
+
15702
15858
  // src/session/models.ts
15703
15859
  function modelsForProvider(provider, asOf) {
15704
15860
  const seen = new Set;
@@ -16380,7 +16536,7 @@ function escapeCodeTimeoutMs(environment = process.env) {
16380
16536
  // package.json
16381
16537
  var package_default = {
16382
16538
  name: "archymedes-cli",
16383
- version: "3.3.0",
16539
+ version: "3.4.0",
16384
16540
  description: "Archymedes CLI — a coding agent that shows its work. Runs against your working tree or an isolated remote sandbox, with guarded approvals, git checkpoints, and exact, measured cost accounting.",
16385
16541
  keywords: [
16386
16542
  "cli",
@@ -16426,12 +16582,12 @@ var package_default = {
16426
16582
  prepublishOnly: "bun run ../../tooling/build/build-packages.ts"
16427
16583
  },
16428
16584
  optionalDependencies: {
16429
- "@archymedes/cli-darwin-arm64": "3.3.0",
16430
- "@archymedes/cli-darwin-x64": "3.3.0",
16431
- "@archymedes/cli-linux-arm64": "3.3.0",
16432
- "@archymedes/cli-linux-x64": "3.3.0",
16433
- "@archymedes/cli-win32-arm64": "3.3.0",
16434
- "@archymedes/cli-win32-x64": "3.3.0",
16585
+ "@archymedes/cli-darwin-arm64": "3.4.0",
16586
+ "@archymedes/cli-darwin-x64": "3.4.0",
16587
+ "@archymedes/cli-linux-arm64": "3.4.0",
16588
+ "@archymedes/cli-linux-x64": "3.4.0",
16589
+ "@archymedes/cli-win32-arm64": "3.4.0",
16590
+ "@archymedes/cli-win32-x64": "3.4.0",
16435
16591
  "@archymedes/state-darwin-arm64": "0.4.0",
16436
16592
  "@archymedes/state-darwin-x64": "0.4.0",
16437
16593
  "@archymedes/state-linux-arm64-gnu": "0.4.0",
@@ -19554,6 +19710,10 @@ async function runAcpServer(options) {
19554
19710
  const connection = new AcpConnection({
19555
19711
  send,
19556
19712
  extensions: {
19713
+ "_archymedes/providers": async () => ({ providers: providerOverview(options.environment) }),
19714
+ "_archymedes/providers/connect": async (params) => connectProvider(params, { environment: options.environment, processEnvironment: process.env }),
19715
+ "_archymedes/providers/models": async (params) => providerModels(params, { environment: options.environment }),
19716
+ "_archymedes/providers/remember-model": async (params) => rememberModel(params, { environment: options.environment, processEnvironment: process.env }),
19557
19717
  "_archymedes/setup/status": async () => setupStatus(options.environment, { provider: options.provider, model: options.model }),
19558
19718
  "_archymedes/setup/save": async (params) => saveSetup(params, { environment: options.environment, processEnvironment: process.env }),
19559
19719
  "_archymedes/settings/list": async () => listSettings(process.env),
@@ -25336,7 +25496,7 @@ function createWorkspaceFactory(args, environment) {
25336
25496
  const minutes = Math.max(1, Math.min(args.sandboxMinutes, 60));
25337
25497
  if (request.backend === "e2b") {
25338
25498
  const { findWorkspacePreset } = await import("./sandbox-templates-9jcjws23.mjs");
25339
- const { createE2BProvider } = await import("./factory-r6r0ypm6.mjs");
25499
+ const { createE2BProvider } = await import("./factory-xpqqcyqq.mjs");
25340
25500
  const preset = findWorkspacePreset(request.preset ?? args.preset);
25341
25501
  const sandbox = createE2BProvider(environment, preset.templateAlias);
25342
25502
  if (!sandbox)
@@ -25362,7 +25522,7 @@ function createWorkspaceFactory(args, environment) {
25362
25522
  return { workspace: created };
25363
25523
  }
25364
25524
  if (request.backend === "docker") {
25365
- const { createDockerProvider } = await import("./factory-r6r0ypm6.mjs");
25525
+ const { createDockerProvider } = await import("./factory-xpqqcyqq.mjs");
25366
25526
  const image = request.dockerImage || args.dockerImage || environment.DOCKER_CODING_IMAGE;
25367
25527
  const sandbox = createDockerProvider({ ...environment, DOCKER_CODING_IMAGE: image });
25368
25528
  if (!sandbox)
@@ -6,7 +6,7 @@ import {
6
6
  makeParseableTextFormat,
7
7
  modelPricesFromEnvironment,
8
8
  wanderPlannerInstructions
9
- } from "./archymedes-j0vrbr99.mjs";
9
+ } from "./archymedes-ds224xzf.mjs";
10
10
  import {
11
11
  ALLOWED_GIT_SUBCOMMANDS,
12
12
  ALLOWED_SANDBOX_PROGRAMS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archymedes-cli",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Archymedes CLI \u2014 a coding agent that shows its work. Runs against your working tree or an isolated remote sandbox, with guarded approvals, git checkpoints, and exact, measured cost accounting.",
5
5
  "keywords": [
6
6
  "cli",
@@ -46,12 +46,12 @@
46
46
  "prepublishOnly": "bun run ../../tooling/build/build-packages.ts"
47
47
  },
48
48
  "optionalDependencies": {
49
- "@archymedes/cli-darwin-arm64": "3.3.0",
50
- "@archymedes/cli-darwin-x64": "3.3.0",
51
- "@archymedes/cli-linux-arm64": "3.3.0",
52
- "@archymedes/cli-linux-x64": "3.3.0",
53
- "@archymedes/cli-win32-arm64": "3.3.0",
54
- "@archymedes/cli-win32-x64": "3.3.0",
49
+ "@archymedes/cli-darwin-arm64": "3.4.0",
50
+ "@archymedes/cli-darwin-x64": "3.4.0",
51
+ "@archymedes/cli-linux-arm64": "3.4.0",
52
+ "@archymedes/cli-linux-x64": "3.4.0",
53
+ "@archymedes/cli-win32-arm64": "3.4.0",
54
+ "@archymedes/cli-win32-x64": "3.4.0",
55
55
  "@archymedes/state-darwin-arm64": "0.4.0",
56
56
  "@archymedes/state-darwin-x64": "0.4.0",
57
57
  "@archymedes/state-linux-arm64-gnu": "0.4.0",