echopai 2.8.0 → 2.8.1
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/bin.js +67 -21
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -3430,6 +3430,22 @@ function buildHttpHeaders(ctx) {
|
|
|
3430
3430
|
function resolveRequestId(serverHeader, clientGenerated) {
|
|
3431
3431
|
return serverHeader && serverHeader.length > 0 ? serverHeader : clientGenerated;
|
|
3432
3432
|
}
|
|
3433
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
3434
|
+
var WHOAMI_TIMEOUT_MS = 8000;
|
|
3435
|
+
async function fetchWithTimeout(fn, url, init, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
3436
|
+
const ac = new AbortController;
|
|
3437
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
3438
|
+
try {
|
|
3439
|
+
return await fn(url, { ...init, signal: ac.signal });
|
|
3440
|
+
} catch (e) {
|
|
3441
|
+
if (ac.signal.aborted) {
|
|
3442
|
+
throw new Error(`request timed out after ${timeoutMs}ms: ${url}`);
|
|
3443
|
+
}
|
|
3444
|
+
throw e;
|
|
3445
|
+
} finally {
|
|
3446
|
+
clearTimeout(timer);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3433
3449
|
|
|
3434
3450
|
// src/runtime/trace.ts
|
|
3435
3451
|
import {
|
|
@@ -3560,7 +3576,10 @@ async function paginate(op, initialParams, ctx, write = writeStdout) {
|
|
|
3560
3576
|
process.stderr.write(`> [page ${pages + 1}] ${op.method} ${url}
|
|
3561
3577
|
`);
|
|
3562
3578
|
}
|
|
3563
|
-
const res = await fetchFn
|
|
3579
|
+
const res = await fetchWithTimeout(fetchFn, url, {
|
|
3580
|
+
method: op.method,
|
|
3581
|
+
headers
|
|
3582
|
+
});
|
|
3564
3583
|
const body = await res.text();
|
|
3565
3584
|
let json = null;
|
|
3566
3585
|
try {
|
|
@@ -3700,7 +3719,7 @@ import os2 from "node:os";
|
|
|
3700
3719
|
import path2 from "node:path";
|
|
3701
3720
|
|
|
3702
3721
|
// src/version.ts
|
|
3703
|
-
var CLI_VERSION = "2.8.
|
|
3722
|
+
var CLI_VERSION = "2.8.1";
|
|
3704
3723
|
|
|
3705
3724
|
// src/runtime/update_check.ts
|
|
3706
3725
|
var UPDATE_CACHE_PATH = path2.join(os2.homedir(), ".config", "echopai", "update_cache.json");
|
|
@@ -3943,7 +3962,7 @@ async function invoke(op, args, ctx) {
|
|
|
3943
3962
|
const startedAt = Date.now();
|
|
3944
3963
|
let res;
|
|
3945
3964
|
try {
|
|
3946
|
-
res = await fetch
|
|
3965
|
+
res = await fetchWithTimeout(fetch, url, init);
|
|
3947
3966
|
} catch (e) {
|
|
3948
3967
|
trace(op, { exit_code: 2, error_code: "network_error" });
|
|
3949
3968
|
writeError("network_error", e instanceof Error ? e.message : String(e), 2);
|
|
@@ -4172,7 +4191,7 @@ async function getWhoami(ctx, opts) {
|
|
|
4172
4191
|
if (inflight && inflight.key === key) {
|
|
4173
4192
|
return inflight.promise;
|
|
4174
4193
|
}
|
|
4175
|
-
const promise = doFetch(ctx, opts?.fetchImpl).then((resp) => {
|
|
4194
|
+
const promise = doFetch(ctx, opts?.fetchImpl, opts?.requestTimeoutMs).then((resp) => {
|
|
4176
4195
|
cache = { key, resp, expiresAt: Date.now() + ttlMs };
|
|
4177
4196
|
return resp;
|
|
4178
4197
|
}).finally(() => {
|
|
@@ -4182,7 +4201,7 @@ async function getWhoami(ctx, opts) {
|
|
|
4182
4201
|
inflight = { key, promise };
|
|
4183
4202
|
return promise;
|
|
4184
4203
|
}
|
|
4185
|
-
async function doFetch(ctx, fetchImpl) {
|
|
4204
|
+
async function doFetch(ctx, fetchImpl, requestTimeoutMs = WHOAMI_TIMEOUT_MS) {
|
|
4186
4205
|
const fn = fetchImpl ?? fetch;
|
|
4187
4206
|
const url = ctx.baseUrl.replace(/\/+$/, "") + "/v1/auth/whoami";
|
|
4188
4207
|
const { headers, requestId: clientRequestId } = buildHttpHeaders({
|
|
@@ -4192,7 +4211,7 @@ async function doFetch(ctx, fetchImpl) {
|
|
|
4192
4211
|
});
|
|
4193
4212
|
let res;
|
|
4194
4213
|
try {
|
|
4195
|
-
res = await fn
|
|
4214
|
+
res = await fetchWithTimeout(fn, url, { method: "GET", headers }, requestTimeoutMs);
|
|
4196
4215
|
} catch (e) {
|
|
4197
4216
|
throw new CallApiError({
|
|
4198
4217
|
code: "network_error",
|
|
@@ -4338,7 +4357,7 @@ async function callOp(op, args, ctx) {
|
|
|
4338
4357
|
const startedAt = Date.now();
|
|
4339
4358
|
let res;
|
|
4340
4359
|
try {
|
|
4341
|
-
res = await fetchFn
|
|
4360
|
+
res = await fetchWithTimeout(fetchFn, url, init);
|
|
4342
4361
|
} catch (e) {
|
|
4343
4362
|
throw new CallApiError({
|
|
4344
4363
|
code: "network_error",
|
|
@@ -6606,24 +6625,51 @@ function buildMcpCommand() {
|
|
|
6606
6625
|
}
|
|
6607
6626
|
throw e;
|
|
6608
6627
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6628
|
+
const WHOAMI_MAX_ATTEMPTS = 3;
|
|
6629
|
+
const WHOAMI_BACKOFF_MS = [500, 1500];
|
|
6630
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6631
|
+
let whoami = null;
|
|
6632
|
+
let lastErr = null;
|
|
6633
|
+
for (let attempt = 1;attempt <= WHOAMI_MAX_ATTEMPTS; attempt++) {
|
|
6634
|
+
try {
|
|
6635
|
+
whoami = await getWhoami({
|
|
6636
|
+
baseUrl: creds.baseUrl,
|
|
6637
|
+
bearer: creds.key,
|
|
6638
|
+
cliVersion: CLI_VERSION,
|
|
6639
|
+
channel: "mcp"
|
|
6640
|
+
});
|
|
6641
|
+
break;
|
|
6642
|
+
} catch (e) {
|
|
6643
|
+
lastErr = e;
|
|
6644
|
+
const status = e instanceof CallApiError ? e.httpStatus : undefined;
|
|
6645
|
+
const code = e instanceof CallApiError ? e.code : undefined;
|
|
6646
|
+
const isAuthError = status === 401 || status === 403 || code === "auth_missing" || code === "forbidden";
|
|
6647
|
+
if (isAuthError) {
|
|
6648
|
+
process.stderr.write(`[mcp] whoami auth failed${status ? ` (HTTP ${status})` : ""}: ` + `${e instanceof Error ? e.message : String(e)}
|
|
6649
|
+
` + "[mcp] hint: key 失效或无权限,请 `echopai login` 或检查 ECHOPAI_KEY。\n");
|
|
6650
|
+
process.exit(1);
|
|
6651
|
+
}
|
|
6652
|
+
if (attempt < WHOAMI_MAX_ATTEMPTS) {
|
|
6653
|
+
const backoffMs = WHOAMI_BACKOFF_MS[attempt - 1] ?? 1500;
|
|
6654
|
+
process.stderr.write(`[mcp] whoami attempt ${attempt}/${WHOAMI_MAX_ATTEMPTS} failed ` + `(${e instanceof Error ? e.message : String(e)}); ` + `retrying in ${backoffMs}ms
|
|
6619
6655
|
`);
|
|
6620
|
-
|
|
6656
|
+
await sleep(backoffMs);
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6621
6659
|
}
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6660
|
+
let availableVerbs;
|
|
6661
|
+
if (whoami) {
|
|
6662
|
+
const tokenScopes = new Set(whoami.scopes);
|
|
6663
|
+
availableVerbs = filterAvailableVerbs(ALL_VERB_SPECS, tokenScopes);
|
|
6664
|
+
process.stderr.write(`[mcp] kind=${whoami.kind} scopes=[${whoami.scopes.join(",")}]
|
|
6625
6665
|
` + `[mcp] exposing ${availableVerbs.length}/${ALL_VERB_SPECS.length} curated verbs as MCP tools
|
|
6626
6666
|
`);
|
|
6667
|
+
} else {
|
|
6668
|
+
availableVerbs = [...ALL_VERB_SPECS];
|
|
6669
|
+
process.stderr.write(`[mcp] whoami failed after ${WHOAMI_MAX_ATTEMPTS} attempts: ` + `${lastErr instanceof Error ? lastErr.message : String(lastErr)}
|
|
6670
|
+
` + `[mcp] degraded start: exposing all ${availableVerbs.length} curated verbs; ` + `scopes enforced per-call by the server
|
|
6671
|
+
`);
|
|
6672
|
+
}
|
|
6627
6673
|
const server = new McpServer({
|
|
6628
6674
|
name: "echopai",
|
|
6629
6675
|
version: CLI_VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echopai",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.1",
|
|
4
4
|
"description": "Command-line interface for the EchoPai Open Platform: stock-market data, news, analyst views, sentiment, signals, backtests.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://echopai.com",
|