eve-hub 0.6.39 → 0.6.42
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/CHANGELOG.md +18 -0
- package/model-timeout.mjs +79 -0
- package/package.json +1 -1
- package/roster-model.mjs +2 -0
- package/world.mjs +33 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# eve-hub
|
|
2
2
|
|
|
3
|
+
## 0.6.42
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The state frame carries each bot's latest finished turn per thread (`lastTurn`), so the app marks a thread failed from that turn.
|
|
8
|
+
|
|
9
|
+
## 0.6.41
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- A model call that sends no data for 30 s ends with a timeout instead of waiting 5 minutes per attempt; the clock restarts on every chunk, and the AI SDK's two retries stay, so a brief hang recovers without a hand retry.
|
|
14
|
+
|
|
15
|
+
## 0.6.40
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- The hub records the size of each input a bot receives and each compaction, from eve's own stream, so the team-model work has a measured before.
|
|
20
|
+
|
|
3
21
|
## 0.6.39
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// A model call that gets no response fails fast. Node's fetch waits 300 s for
|
|
2
|
+
// response headers and for each body chunk (undici's default headersTimeout /
|
|
3
|
+
// bodyTimeout), and eve hands the AI SDK no timeout of its own, so one hung
|
|
4
|
+
// gateway request stalled a turn for 5 minutes (15 with retries). Every
|
|
5
|
+
// streaming gateway request now gets an idle limit instead: no headers, or no
|
|
6
|
+
// chunk while a read is waiting, within MODEL_CALL_TIMEOUT_MS ends it with a
|
|
7
|
+
// TimeoutError; the clock restarts on every chunk, so an answer that is still
|
|
8
|
+
// streaming is never cut off. The AI SDK retries that twice (3 x 30 s plus its
|
|
9
|
+
// backoff of 2 s and 4 s, about 1 min 36 s), then eve parks the turn as recoverable, so a transient hang
|
|
10
|
+
// recovers without a hand retry.
|
|
11
|
+
//
|
|
12
|
+
// Streaming calls only: a non-streaming call (eve's compaction `generateText`)
|
|
13
|
+
// legitimately waits for the whole generation, so a hung compaction still waits
|
|
14
|
+
// the 300 s default. Remove this module when eve forwards `timeout`/`maxRetries`
|
|
15
|
+
// to the AI SDK (vercel/eve#3984).
|
|
16
|
+
export const MODEL_CALL_TIMEOUT_MS = 30_000;
|
|
17
|
+
|
|
18
|
+
const INSTALLED = Symbol.for("hub.modelCallTimeout");
|
|
19
|
+
const GATEWAY_HOST = "ai-gateway.vercel.sh";
|
|
20
|
+
|
|
21
|
+
function isStreamingGatewayCall(input, init) {
|
|
22
|
+
let url;
|
|
23
|
+
try {
|
|
24
|
+
url = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (url.hostname !== GATEWAY_HOST) return false;
|
|
29
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
|
|
30
|
+
return headers.get("ai-language-model-streaming") === "true";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function withModelCallTimeout(baseFetch, ms) {
|
|
34
|
+
return async function fetchWithModelCallTimeout(input, init) {
|
|
35
|
+
if (!isStreamingGatewayCall(input, init)) return baseFetch(input, init);
|
|
36
|
+
const ctrl = new AbortController();
|
|
37
|
+
const caller = init?.signal ?? (input instanceof Request ? input.signal : undefined);
|
|
38
|
+
const signal = caller ? AbortSignal.any([caller, ctrl.signal]) : ctrl.signal;
|
|
39
|
+
let timer;
|
|
40
|
+
const arm = () => {
|
|
41
|
+
timer = setTimeout(() => ctrl.abort(new DOMException(`model call got no response for ${ms} ms`, "TimeoutError")), ms);
|
|
42
|
+
};
|
|
43
|
+
arm();
|
|
44
|
+
let res;
|
|
45
|
+
try {
|
|
46
|
+
res = await baseFetch(input, { ...init, signal });
|
|
47
|
+
} finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
if (!res.body) return res;
|
|
51
|
+
const reader = res.body.getReader();
|
|
52
|
+
const body = new ReadableStream({
|
|
53
|
+
async pull(controller) {
|
|
54
|
+
arm();
|
|
55
|
+
try {
|
|
56
|
+
const { done, value } = await reader.read();
|
|
57
|
+
if (done) controller.close();
|
|
58
|
+
else controller.enqueue(value);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
controller.error(error);
|
|
61
|
+
} finally {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
cancel(reason) {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
return reader.cancel(reason);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function installModelCallTimeout() {
|
|
75
|
+
if (globalThis.fetch[INSTALLED]) return;
|
|
76
|
+
const wrapped = withModelCallTimeout(globalThis.fetch.bind(globalThis), MODEL_CALL_TIMEOUT_MS);
|
|
77
|
+
wrapped[INSTALLED] = true;
|
|
78
|
+
globalThis.fetch = wrapped;
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eve-hub",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.42",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Bot-side code shared by every hub agent: registry tools, the roster hook, the eve channel, the host sandbox and browser, and model helpers.",
|
|
6
6
|
"type": "module",
|
package/roster-model.mjs
CHANGED
|
@@ -16,8 +16,10 @@ import { defineDynamic } from "eve";
|
|
|
16
16
|
import { CONTEXT_WINDOW, selectModel } from "./bot-model.mjs";
|
|
17
17
|
import { DEFAULT_SUBAGENT_CAP } from "./subagent-cap.mjs";
|
|
18
18
|
import { sessionLimits } from "./session-limits.mjs";
|
|
19
|
+
import { installModelCallTimeout } from "./model-timeout.mjs";
|
|
19
20
|
import { findWorkspaceRoot, loadWorkspace, parseWorkspace } from "./workspace.mjs";
|
|
20
21
|
const real = process.env.ROSTER_MODELS !== "mock";
|
|
22
|
+
installModelCallTimeout();
|
|
21
23
|
|
|
22
24
|
export function createRosterModel({ agents, subagentCap }) {
|
|
23
25
|
return function rosterModel(name, { mock, copyMock } = {}) {
|
package/world.mjs
CHANGED
|
@@ -626,6 +626,9 @@ var names = (text, { person, names: names2 }) => {
|
|
|
626
626
|
var record = (id, person) => s("INSERT OR IGNORE INTO notifications (message_id, person) SELECT ?, ? WHERE EXISTS (SELECT 1 FROM messages WHERE id = ?)", id, person, id);
|
|
627
627
|
var notificationFor = (messageId2, kind, text, notify) => notify && kind === "agent" && names(text, notify) ? [record(messageId2, notify.person)] : [];
|
|
628
628
|
|
|
629
|
+
// ../../hosted-hub/src/dispatch/bot.ts
|
|
630
|
+
var MESSAGE_LINE = "\n[thread ";
|
|
631
|
+
|
|
629
632
|
// ../../hosted-hub/src/store/subagents.ts
|
|
630
633
|
var UP = `up(session_id, parent) AS (
|
|
631
634
|
SELECT session_id, parent FROM subagent_links WHERE session_id = ?
|
|
@@ -26782,6 +26785,8 @@ var HubThread = object2({
|
|
|
26782
26785
|
status: maybe(map2("ThreadStatus", string5())),
|
|
26783
26786
|
busySince: maybe(map2("ThreadBusySince", string5())),
|
|
26784
26787
|
turnCounts: maybe(map2("ThreadTurnCounts", int2())),
|
|
26788
|
+
/** Each bot's latest finished turn here: done | failed | cancelled. */
|
|
26789
|
+
lastTurn: maybe(map2("ThreadLastTurn", string5())),
|
|
26785
26790
|
archived: maybe(Archived),
|
|
26786
26791
|
source: maybe(ThreadSource),
|
|
26787
26792
|
tags: maybe(ThreadTags),
|
|
@@ -27323,7 +27328,35 @@ function fold(state, chunks, now = Date.now()) {
|
|
|
27323
27328
|
SELECT ?, ?, ?, ?, ?, 'streaming', ? WHERE EXISTS (SELECT 1 FROM sessions WHERE id = ? AND ended_at IS NULL)`, open3.id, session.thread, session.id, data.turnId, session.bot, at, session.id));
|
|
27324
27329
|
log("turn.status", { turnId: open3.id, status: "streaming" });
|
|
27325
27330
|
}
|
|
27331
|
+
if (event.type === "compaction.completed")
|
|
27332
|
+
stmts.push(s(
|
|
27333
|
+
"INSERT OR IGNORE INTO compaction_events (session_id, chunk, position, sequence, step_index, at, thread_id, bot) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
27334
|
+
session.id,
|
|
27335
|
+
index,
|
|
27336
|
+
eventIndex,
|
|
27337
|
+
data.sequence ?? null,
|
|
27338
|
+
data.stepIndex ?? null,
|
|
27339
|
+
at,
|
|
27340
|
+
session.thread,
|
|
27341
|
+
session.bot
|
|
27342
|
+
));
|
|
27326
27343
|
if (!open3) continue;
|
|
27344
|
+
if (event.type === "message.received" && typeof data.message === "string") {
|
|
27345
|
+
const cut = data.message.indexOf(MESSAGE_LINE);
|
|
27346
|
+
const state2 = cut < 0 ? 0 : cut;
|
|
27347
|
+
stmts.push(s(
|
|
27348
|
+
"INSERT OR IGNORE INTO inputs (session_id, chunk, position, turn_id, at, thread_id, bot, state_chars, message_chars) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
27349
|
+
session.id,
|
|
27350
|
+
index,
|
|
27351
|
+
eventIndex,
|
|
27352
|
+
open3.id,
|
|
27353
|
+
at,
|
|
27354
|
+
session.thread,
|
|
27355
|
+
session.bot,
|
|
27356
|
+
state2,
|
|
27357
|
+
data.message.length - state2
|
|
27358
|
+
));
|
|
27359
|
+
}
|
|
27327
27360
|
for (const deliveryId2 of event.meta?.deliveryIds ?? []) {
|
|
27328
27361
|
open3.trigger ??= messageOfDelivery(deliveryId2, session.bot);
|
|
27329
27362
|
stmts.push(s("INSERT OR IGNORE INTO turn_deliveries (delivery_id, turn_id) SELECT id, ? FROM deliveries WHERE id = ? AND session_id = ?", open3.id, deliveryId2, session.id));
|