open-agents-ai 0.15.6 → 0.15.7
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 +209 -9
- 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);
|
package/package.json
CHANGED