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 =
|
|
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 ?
|
|
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|
|
|
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,
|
package/dist/archymedes-main.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
wanderGradeCounts,
|
|
45
45
|
wanderPlannerInstructions,
|
|
46
46
|
withOpenRouterCacheControl
|
|
47
|
-
} from "./archymedes-
|
|
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|
|
|
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) =>
|
|
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 >=
|
|
12577
|
-
this.cap =
|
|
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.
|
|
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.
|
|
16430
|
-
"@archymedes/cli-darwin-x64": "3.
|
|
16431
|
-
"@archymedes/cli-linux-arm64": "3.
|
|
16432
|
-
"@archymedes/cli-linux-x64": "3.
|
|
16433
|
-
"@archymedes/cli-win32-arm64": "3.
|
|
16434
|
-
"@archymedes/cli-win32-x64": "3.
|
|
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-
|
|
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-
|
|
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)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archymedes-cli",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
50
|
-
"@archymedes/cli-darwin-x64": "3.
|
|
51
|
-
"@archymedes/cli-linux-arm64": "3.
|
|
52
|
-
"@archymedes/cli-linux-x64": "3.
|
|
53
|
-
"@archymedes/cli-win32-arm64": "3.
|
|
54
|
-
"@archymedes/cli-win32-x64": "3.
|
|
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",
|