open-agents-ai 0.15.6 → 0.15.8
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.js +226 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8188,7 +8188,18 @@ Integrate this guidance into your current approach. Continue working on the task
|
|
|
8188
8188
|
maxTokens: this.options.maxTokens,
|
|
8189
8189
|
timeoutMs: this.options.requestTimeoutMs
|
|
8190
8190
|
};
|
|
8191
|
-
|
|
8191
|
+
let response;
|
|
8192
|
+
try {
|
|
8193
|
+
response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
|
|
8194
|
+
} catch (reqErr) {
|
|
8195
|
+
const recovered = await this.retryOnTransient(reqErr, chatRequest, turn);
|
|
8196
|
+
if (!recovered) {
|
|
8197
|
+
this.emit({ type: "error", content: `Backend error: ${reqErr instanceof Error ? reqErr.message : String(reqErr)}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8198
|
+
messages.push({ role: "user", content: "[System: backend request failed, retrying on next turn. The previous request was lost.]" });
|
|
8199
|
+
continue;
|
|
8200
|
+
}
|
|
8201
|
+
response = recovered;
|
|
8202
|
+
}
|
|
8192
8203
|
totalTokens += response.usage?.totalTokens ?? 0;
|
|
8193
8204
|
promptTokens += response.usage?.promptTokens ?? 0;
|
|
8194
8205
|
completionTokens += response.usage?.completionTokens ?? 0;
|
|
@@ -8405,7 +8416,18 @@ Integrate this guidance into your current approach. Continue working on the task
|
|
|
8405
8416
|
}
|
|
8406
8417
|
const compactedMsgs = this.compactMessages(messages);
|
|
8407
8418
|
const chatRequest = { messages: compactedMsgs, tools: toolDefs, temperature: this.options.temperature, maxTokens: this.options.maxTokens, timeoutMs: this.options.requestTimeoutMs };
|
|
8408
|
-
|
|
8419
|
+
let response;
|
|
8420
|
+
try {
|
|
8421
|
+
response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
|
|
8422
|
+
} catch (reqErr) {
|
|
8423
|
+
const recovered = await this.retryOnTransient(reqErr, chatRequest, turn);
|
|
8424
|
+
if (!recovered) {
|
|
8425
|
+
this.emit({ type: "error", content: `Backend error: ${reqErr instanceof Error ? reqErr.message : String(reqErr)}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
8426
|
+
messages.push({ role: "user", content: "[System: backend request failed, retrying on next turn. The previous request was lost.]" });
|
|
8427
|
+
continue;
|
|
8428
|
+
}
|
|
8429
|
+
response = recovered;
|
|
8430
|
+
}
|
|
8409
8431
|
totalTokens += response.usage?.totalTokens ?? 0;
|
|
8410
8432
|
promptTokens += response.usage?.promptTokens ?? 0;
|
|
8411
8433
|
completionTokens += response.usage?.completionTokens ?? 0;
|
|
@@ -8809,6 +8831,58 @@ ${newerSummary}` : newerSummary;
|
|
|
8809
8831
|
}));
|
|
8810
8832
|
}
|
|
8811
8833
|
// -------------------------------------------------------------------------
|
|
8834
|
+
// Transient error recovery — retry on 502, fetch failed, timeouts
|
|
8835
|
+
// -------------------------------------------------------------------------
|
|
8836
|
+
/** Detect whether an error is transient (worth retrying) */
|
|
8837
|
+
isTransientError(err) {
|
|
8838
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8839
|
+
if (/Backend HTTP (502|503|504)/i.test(msg))
|
|
8840
|
+
return true;
|
|
8841
|
+
if (/fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|socket hang up/i.test(msg))
|
|
8842
|
+
return true;
|
|
8843
|
+
if (/received HTML error page/i.test(msg))
|
|
8844
|
+
return true;
|
|
8845
|
+
if (/model is loading|server busy|overloaded/i.test(msg))
|
|
8846
|
+
return true;
|
|
8847
|
+
return false;
|
|
8848
|
+
}
|
|
8849
|
+
/**
|
|
8850
|
+
* Retry a failed model request up to 3 times with exponential backoff.
|
|
8851
|
+
* Returns the response on success, or null if all retries failed.
|
|
8852
|
+
*/
|
|
8853
|
+
async retryOnTransient(initialErr, chatRequest, turn) {
|
|
8854
|
+
if (!this.isTransientError(initialErr))
|
|
8855
|
+
return null;
|
|
8856
|
+
const maxRetries = 3;
|
|
8857
|
+
const baseDelayMs = 3e3;
|
|
8858
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
8859
|
+
if (this.aborted)
|
|
8860
|
+
return null;
|
|
8861
|
+
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
8862
|
+
this.emit({
|
|
8863
|
+
type: "compaction",
|
|
8864
|
+
content: `Backend error \u2014 retrying in ${(delay / 1e3).toFixed(0)}s (attempt ${attempt}/${maxRetries})`,
|
|
8865
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8866
|
+
});
|
|
8867
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
8868
|
+
if (this.aborted)
|
|
8869
|
+
return null;
|
|
8870
|
+
try {
|
|
8871
|
+
const response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
|
|
8872
|
+
this.emit({
|
|
8873
|
+
type: "compaction",
|
|
8874
|
+
content: `Backend recovered on attempt ${attempt}`,
|
|
8875
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8876
|
+
});
|
|
8877
|
+
return response;
|
|
8878
|
+
} catch (retryErr) {
|
|
8879
|
+
if (!this.isTransientError(retryErr))
|
|
8880
|
+
return null;
|
|
8881
|
+
}
|
|
8882
|
+
}
|
|
8883
|
+
return null;
|
|
8884
|
+
}
|
|
8885
|
+
// -------------------------------------------------------------------------
|
|
8812
8886
|
// Streaming support — parallel path that emits token events
|
|
8813
8887
|
// -------------------------------------------------------------------------
|
|
8814
8888
|
/** Check whether the backend supports SSE streaming */
|
|
@@ -10809,6 +10883,106 @@ async function runSetupWizard(config) {
|
|
|
10809
10883
|
rl.close();
|
|
10810
10884
|
}
|
|
10811
10885
|
}
|
|
10886
|
+
async function promptForCustomEndpoint(config, rl) {
|
|
10887
|
+
process.stdout.write(`
|
|
10888
|
+
${c2.cyan("\u25CF")} Enter an OpenAI-compatible inference endpoint.
|
|
10889
|
+
`);
|
|
10890
|
+
process.stdout.write(` ${c2.dim("Examples:")}
|
|
10891
|
+
`);
|
|
10892
|
+
process.stdout.write(` ${c2.dim(" https://chutes.ai/v1")}
|
|
10893
|
+
`);
|
|
10894
|
+
process.stdout.write(` ${c2.dim(" http://10.0.0.5:11434")}
|
|
10895
|
+
`);
|
|
10896
|
+
process.stdout.write(` ${c2.dim(" https://api.together.xyz/v1")}
|
|
10897
|
+
|
|
10898
|
+
`);
|
|
10899
|
+
const endpoint = await ask(rl, ` ${c2.bold("Endpoint URL:")} `);
|
|
10900
|
+
if (!endpoint) {
|
|
10901
|
+
process.stdout.write(` ${c2.dim("No endpoint entered.")}
|
|
10902
|
+
`);
|
|
10903
|
+
const startAnyway = await ask(rl, `
|
|
10904
|
+
${c2.bold("Start anyway without a backend?")} (y/n) `);
|
|
10905
|
+
if (startAnyway.toLowerCase() === "y" || startAnyway.toLowerCase() === "yes") {
|
|
10906
|
+
return config.model;
|
|
10907
|
+
}
|
|
10908
|
+
return config.model;
|
|
10909
|
+
}
|
|
10910
|
+
const cleanUrl = endpoint.replace(/\/+$/, "");
|
|
10911
|
+
const needsKey = await ask(rl, `
|
|
10912
|
+
${c2.bold("Does this endpoint require an API key?")} (y/n) `);
|
|
10913
|
+
let apiKey = "";
|
|
10914
|
+
if (needsKey.toLowerCase() === "y" || needsKey.toLowerCase() === "yes") {
|
|
10915
|
+
apiKey = await ask(rl, ` ${c2.bold("API key:")} `);
|
|
10916
|
+
}
|
|
10917
|
+
process.stdout.write(`
|
|
10918
|
+
${c2.cyan("\u25CF")} Enter the model name for this endpoint.
|
|
10919
|
+
`);
|
|
10920
|
+
process.stdout.write(` ${c2.dim("Examples: qwen3.5:122b, meta-llama/Llama-3.3-70B, etc.")}
|
|
10921
|
+
|
|
10922
|
+
`);
|
|
10923
|
+
const modelName = await ask(rl, ` ${c2.bold("Model name")} (Enter for ${c2.dim(config.model)}): `);
|
|
10924
|
+
const chosenModel = modelName || config.model;
|
|
10925
|
+
process.stdout.write(`
|
|
10926
|
+
${c2.cyan("\u25CF")} Testing endpoint ${c2.bold(cleanUrl)}...
|
|
10927
|
+
`);
|
|
10928
|
+
let testOk = false;
|
|
10929
|
+
try {
|
|
10930
|
+
const testUrl = cleanUrl.endsWith("/v1") ? `${cleanUrl}/models` : cleanUrl.includes("/v1/") ? `${cleanUrl.replace(/\/v1\/.*/, "/v1/models")}` : `${cleanUrl}/v1/models`;
|
|
10931
|
+
const headers = { "Content-Type": "application/json" };
|
|
10932
|
+
if (apiKey)
|
|
10933
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
10934
|
+
const resp = await fetch(testUrl, { headers, signal: AbortSignal.timeout(1e4) });
|
|
10935
|
+
if (resp.ok) {
|
|
10936
|
+
process.stdout.write(` ${c2.green("\u2714")} Endpoint reachable.
|
|
10937
|
+
`);
|
|
10938
|
+
testOk = true;
|
|
10939
|
+
} else {
|
|
10940
|
+
try {
|
|
10941
|
+
const ollamaResp = await fetch(`${cleanUrl}/api/tags`, { signal: AbortSignal.timeout(1e4) });
|
|
10942
|
+
if (ollamaResp.ok) {
|
|
10943
|
+
process.stdout.write(` ${c2.green("\u2714")} Ollama endpoint detected.
|
|
10944
|
+
`);
|
|
10945
|
+
testOk = true;
|
|
10946
|
+
}
|
|
10947
|
+
} catch {
|
|
10948
|
+
}
|
|
10949
|
+
if (!testOk) {
|
|
10950
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Endpoint returned HTTP ${resp.status}
|
|
10951
|
+
`);
|
|
10952
|
+
}
|
|
10953
|
+
}
|
|
10954
|
+
} catch (err) {
|
|
10955
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Could not reach endpoint: ${err instanceof Error ? err.message : String(err)}
|
|
10956
|
+
`);
|
|
10957
|
+
}
|
|
10958
|
+
if (!testOk) {
|
|
10959
|
+
const startAnyway = await ask(rl, `
|
|
10960
|
+
${c2.bold("Endpoint unreachable. Start anyway?")} (y/n) `);
|
|
10961
|
+
if (startAnyway.toLowerCase() !== "y" && startAnyway.toLowerCase() !== "yes") {
|
|
10962
|
+
process.stdout.write(` ${c2.dim("You can configure the endpoint later with /endpoint")}
|
|
10963
|
+
|
|
10964
|
+
`);
|
|
10965
|
+
return config.model;
|
|
10966
|
+
}
|
|
10967
|
+
}
|
|
10968
|
+
setConfigValue("backendUrl", cleanUrl);
|
|
10969
|
+
setConfigValue("model", chosenModel);
|
|
10970
|
+
if (apiKey) {
|
|
10971
|
+
setConfigValue("apiKey", apiKey);
|
|
10972
|
+
}
|
|
10973
|
+
const backendType = cleanUrl.includes("/v1") ? "vllm" : "ollama";
|
|
10974
|
+
setConfigValue("backendType", backendType);
|
|
10975
|
+
process.stdout.write(`
|
|
10976
|
+
${c2.green("\u2714")} Configured: ${c2.bold(chosenModel)} at ${c2.bold(cleanUrl)}
|
|
10977
|
+
`);
|
|
10978
|
+
if (apiKey)
|
|
10979
|
+
process.stdout.write(` ${c2.green("\u2714")} API key saved.
|
|
10980
|
+
`);
|
|
10981
|
+
process.stdout.write(` ${c2.green("\u2714")} Backend type: ${c2.bold(backendType)}
|
|
10982
|
+
|
|
10983
|
+
`);
|
|
10984
|
+
return chosenModel;
|
|
10985
|
+
}
|
|
10812
10986
|
async function doSetup(config, rl) {
|
|
10813
10987
|
process.stdout.write(`
|
|
10814
10988
|
${c2.bold(c2.cyan("open-agents"))}
|
|
@@ -10832,16 +11006,42 @@ async function doSetup(config, rl) {
|
|
|
10832
11006
|
}
|
|
10833
11007
|
process.stdout.write("\n");
|
|
10834
11008
|
let models = [];
|
|
11009
|
+
let usingCustomEndpoint = false;
|
|
10835
11010
|
try {
|
|
10836
11011
|
models = await fetchOllamaModels(config.backendUrl);
|
|
10837
11012
|
} catch {
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
const
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
11013
|
+
process.stdout.write(` ${c2.yellow("\u26A0")} Cannot reach Ollama at ${c2.bold(config.backendUrl)}
|
|
11014
|
+
|
|
11015
|
+
`);
|
|
11016
|
+
const useWithout = await ask(rl, ` ${c2.bold("Use without Ollama?")} (y/n) `);
|
|
11017
|
+
if (useWithout.toLowerCase() === "y" || useWithout.toLowerCase() === "yes") {
|
|
11018
|
+
const endpointResult = await promptForCustomEndpoint(config, rl);
|
|
11019
|
+
if (endpointResult) {
|
|
11020
|
+
return endpointResult;
|
|
11021
|
+
}
|
|
11022
|
+
usingCustomEndpoint = true;
|
|
11023
|
+
} else {
|
|
11024
|
+
process.stdout.write(`
|
|
11025
|
+
${c2.cyan("\u25CF")} Install Ollama: ${c2.bold(c2.cyan("https://ollama.com"))}
|
|
11026
|
+
`);
|
|
11027
|
+
process.stdout.write(` ${c2.dim("Linux:")} curl -fsSL https://ollama.com/install.sh | sh
|
|
11028
|
+
`);
|
|
11029
|
+
process.stdout.write(` ${c2.dim("macOS:")} brew install ollama
|
|
11030
|
+
`);
|
|
11031
|
+
process.stdout.write(` ${c2.dim("Then:")} ollama serve
|
|
11032
|
+
|
|
11033
|
+
`);
|
|
11034
|
+
const startAnyway = await ask(rl, ` ${c2.bold("Start anyway?")} (y/n) `);
|
|
11035
|
+
if (startAnyway.toLowerCase() !== "y" && startAnyway.toLowerCase() !== "yes") {
|
|
11036
|
+
process.stdout.write(`
|
|
11037
|
+
${c2.dim("You can always configure an endpoint later with /endpoint")}
|
|
11038
|
+
|
|
11039
|
+
`);
|
|
11040
|
+
}
|
|
11041
|
+
return config.model;
|
|
11042
|
+
}
|
|
11043
|
+
}
|
|
11044
|
+
if (usingCustomEndpoint) {
|
|
10845
11045
|
return config.model;
|
|
10846
11046
|
}
|
|
10847
11047
|
const currentModel = findModel(models, config.model);
|
|
@@ -15222,10 +15422,8 @@ async function startInteractive(config, repoPath) {
|
|
|
15222
15422
|
const needsSetup = isFirstRun() || !await isModelAvailable(config);
|
|
15223
15423
|
if (needsSetup && config.backendType === "ollama") {
|
|
15224
15424
|
const setupModel = await runSetupWizard(config);
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
}
|
|
15228
|
-
config = { ...config, model: setupModel };
|
|
15425
|
+
const freshConfig = loadConfig();
|
|
15426
|
+
config = { ...config, ...freshConfig, model: setupModel ?? freshConfig.model };
|
|
15229
15427
|
}
|
|
15230
15428
|
}
|
|
15231
15429
|
if (config.backendType === "ollama" && !config.model.startsWith("open-agents-")) {
|
|
@@ -15244,16 +15442,18 @@ async function startInteractive(config, repoPath) {
|
|
|
15244
15442
|
if (!isResumed) {
|
|
15245
15443
|
try {
|
|
15246
15444
|
const healthUrl = config.backendType === "ollama" ? `${config.backendUrl}/api/tags` : `${config.backendUrl}/v1/models`;
|
|
15247
|
-
const
|
|
15445
|
+
const headers = {};
|
|
15446
|
+
if (config.apiKey)
|
|
15447
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
15448
|
+
const resp = await fetch(healthUrl, { headers, signal: AbortSignal.timeout(1e4) });
|
|
15248
15449
|
if (!resp.ok)
|
|
15249
15450
|
throw new Error(`HTTP ${resp.status}`);
|
|
15250
15451
|
} catch {
|
|
15251
|
-
|
|
15452
|
+
renderWarning(`Cannot reach ${config.backendType} at ${config.backendUrl}`);
|
|
15252
15453
|
if (config.backendType === "ollama") {
|
|
15253
15454
|
renderInfo("Start Ollama with: ollama serve");
|
|
15254
15455
|
}
|
|
15255
|
-
renderInfo("Use /endpoint to configure a different backend.");
|
|
15256
|
-
process.exit(1);
|
|
15456
|
+
renderInfo("Use /endpoint to configure a different backend. Starting anyway...");
|
|
15257
15457
|
}
|
|
15258
15458
|
}
|
|
15259
15459
|
const carousel = new Carousel();
|
|
@@ -15757,10 +15957,8 @@ async function runWithTUI(task, config, repoPath) {
|
|
|
15757
15957
|
const needsSetup = isFirstRun() || !await isModelAvailable(config);
|
|
15758
15958
|
if (needsSetup && config.backendType === "ollama") {
|
|
15759
15959
|
const setupModel = await runSetupWizard(config);
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
}
|
|
15763
|
-
config = { ...config, model: setupModel };
|
|
15960
|
+
const freshConfig = loadConfig();
|
|
15961
|
+
config = { ...config, ...freshConfig, model: setupModel ?? freshConfig.model };
|
|
15764
15962
|
}
|
|
15765
15963
|
if (config.backendType === "ollama" && !config.model.startsWith("open-agents-")) {
|
|
15766
15964
|
try {
|
|
@@ -15773,15 +15971,18 @@ async function runWithTUI(task, config, repoPath) {
|
|
|
15773
15971
|
}
|
|
15774
15972
|
try {
|
|
15775
15973
|
const healthUrl = config.backendType === "ollama" ? `${config.backendUrl}/api/tags` : `${config.backendUrl}/v1/models`;
|
|
15776
|
-
const
|
|
15974
|
+
const headers = {};
|
|
15975
|
+
if (config.apiKey)
|
|
15976
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
15977
|
+
const resp = await fetch(healthUrl, { headers, signal: AbortSignal.timeout(1e4) });
|
|
15777
15978
|
if (!resp.ok)
|
|
15778
15979
|
throw new Error(`HTTP ${resp.status}`);
|
|
15779
15980
|
} catch {
|
|
15780
|
-
|
|
15981
|
+
renderWarning(`Cannot reach ${config.backendType} at ${config.backendUrl}`);
|
|
15781
15982
|
if (config.backendType === "ollama") {
|
|
15782
15983
|
renderInfo("Start Ollama with: ollama serve");
|
|
15783
15984
|
}
|
|
15784
|
-
|
|
15985
|
+
renderInfo("The agent will retry when you submit a task. Use /endpoint to reconfigure.");
|
|
15785
15986
|
}
|
|
15786
15987
|
renderCompactHeader(config.model);
|
|
15787
15988
|
renderUserMessage(task);
|
|
@@ -15800,6 +16001,7 @@ var init_interactive = __esm({
|
|
|
15800
16001
|
init_dist5();
|
|
15801
16002
|
init_dist2();
|
|
15802
16003
|
init_listen();
|
|
16004
|
+
init_config();
|
|
15803
16005
|
init_updater();
|
|
15804
16006
|
init_commands();
|
|
15805
16007
|
init_setup();
|
package/package.json
CHANGED