agents 0.26.0 → 0.27.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.
- package/README.md +8 -9
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-Bhdbvf7s.d.ts} +13 -8
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-YrATZJu2.d.ts → agent-tools-bJZwBMfq.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-sdk-OoGkFy3s.js +2444 -0
- package/dist/ai-sdk-OoGkFy3s.js.map +1 -0
- package/dist/apply-chunks-1FheRb9W.js +99 -0
- package/dist/apply-chunks-1FheRb9W.js.map +1 -0
- package/dist/broadcast-state-CEyI4uAK.js +69 -0
- package/dist/broadcast-state-CEyI4uAK.js.map +1 -0
- package/dist/browser/ai-sdk.d.ts +1 -1
- package/dist/browser/ai-sdk.js +2 -2
- package/dist/browser/ai-sdk.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/tanstack-ai.d.ts +1 -1
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/{browser-DE3d-RLk.d.ts → browser-tmkcAAI3.d.ts} +2 -2
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-Bn-nzJQI.d.ts} +3 -3
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-CBqs6hqa.js} +58 -18
- package/dist/browser-tool-CBqs6hqa.js.map +1 -0
- package/dist/capability-B4WbF81e.js.map +1 -1
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-O5VbQzqg.d.ts} +9 -1
- package/dist/channel-Bak_6zDP.d.ts +333 -0
- package/dist/chat/index.d.ts +11 -9
- package/dist/chat/index.js +5 -194
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +2 -4
- package/dist/chat/react.js +3 -1
- package/dist/chat/react.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +1 -1
- package/dist/chat-sdk/index.js +1 -1
- package/dist/cli.js +144 -0
- package/dist/{client-fyQrLGkD.js → client-1tEQ1Oqz.js} +27 -2
- package/dist/{client-fyQrLGkD.js.map → client-1tEQ1Oqz.js.map} +1 -1
- package/dist/{client-je3koD9Z.d.ts → client-DS3Dz9l0.d.ts} +2 -2
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/conversations-DH0ut_8n.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-C07C5o5Y.d.ts} +3 -3
- package/dist/daemon-core-Buubj-y1.js +481 -0
- package/dist/daemon-core-Buubj-y1.js.map +1 -0
- package/dist/daemon-core-CbohVPAu.d.ts +132 -0
- package/dist/{do-oauth-client-provider-Tmf1vgKz.d.ts → do-oauth-client-provider-DfsUcpc3.d.ts} +7 -1
- package/dist/{durable-object-lifecycle-HhgOMWq9.d.ts → durable-object-lifecycle-DRqp475_.d.ts} +2 -2
- package/dist/{channels/email.d.ts → experimental/channels/email/index.d.ts} +25 -12
- package/dist/{channels/email.js → experimental/channels/email/index.js} +13 -35
- package/dist/experimental/channels/email/index.js.map +1 -0
- package/dist/experimental/channels/index.d.ts +270 -0
- package/dist/experimental/channels/index.js +1186 -0
- package/dist/experimental/channels/index.js.map +1 -0
- package/dist/{channels/slack.d.ts → experimental/channels/slack/index.d.ts} +19 -9
- package/dist/experimental/channels/slack/index.js +267 -0
- package/dist/experimental/channels/slack/index.js.map +1 -0
- package/dist/{channels/telegram.d.ts → experimental/channels/telegram/index.d.ts} +19 -9
- package/dist/experimental/channels/telegram/index.js +198 -0
- package/dist/experimental/channels/telegram/index.js.map +1 -0
- package/dist/experimental/channels/web/ai-sdk.d.ts +55 -0
- package/dist/experimental/channels/web/ai-sdk.js +266 -0
- package/dist/experimental/channels/web/ai-sdk.js.map +1 -0
- package/dist/experimental/channels/web/client.d.ts +97 -0
- package/dist/experimental/channels/web/client.js +349 -0
- package/dist/experimental/channels/web/client.js.map +1 -0
- package/dist/experimental/channels/web/index.d.ts +189 -0
- package/dist/experimental/channels/web/index.js +332 -0
- package/dist/experimental/channels/web/index.js.map +1 -0
- package/dist/gateway-BDC07gYO.d.ts +100 -0
- package/dist/handle-zAzh0UW2.d.ts +460 -0
- package/dist/harness/ai-sdk/index.d.ts +175 -0
- package/dist/harness/ai-sdk/index.js +605 -0
- package/dist/harness/ai-sdk/index.js.map +1 -0
- package/dist/harness/container/daemon/index.d.ts +76 -0
- package/dist/harness/container/daemon/index.js +3 -0
- package/dist/harness/container/index.d.ts +740 -0
- package/dist/harness/container/index.js +2152 -0
- package/dist/harness/container/index.js.map +1 -0
- package/dist/harness/container/runtime/index.d.ts +144 -0
- package/dist/harness/container/runtime/index.js +1082 -0
- package/dist/harness/container/runtime/index.js.map +1 -0
- package/dist/harness/opencode/index.d.ts +369 -0
- package/dist/harness/opencode/index.js +1072 -0
- package/dist/harness/opencode/index.js.map +1 -0
- package/dist/harness/pi/index.d.ts +2 -1
- package/dist/harness/store/index.d.ts +24 -0
- package/dist/harness/store/index.js +2 -0
- package/dist/harness/think/index.d.ts +821 -0
- package/dist/harness/think/index.js +2529 -0
- package/dist/harness/think/index.js.map +1 -0
- package/dist/harness-B6B9S3rb.d.ts +203 -0
- package/dist/{images-iiTDSyRu.js → images-Ce9SCu3X.js} +2 -248
- package/dist/images-Ce9SCu3X.js.map +1 -0
- package/dist/{index-BreL0YrQ.d.ts → index-D0A5tbWu.d.ts} +2 -2
- package/dist/index-Ib_4dwOo.d.ts +200 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/ingress-EFbrS547.js +9 -0
- package/dist/ingress-EFbrS547.js.map +1 -0
- package/dist/input-schema-DNrpTu49.js +23 -0
- package/dist/input-schema-DNrpTu49.js.map +1 -0
- package/dist/internal-kmw70xwR.js +46 -0
- package/dist/internal-kmw70xwR.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +3 -3
- package/dist/mcp/client/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/client/do-oauth-client-provider.js +9 -0
- package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/client/index.d.ts +1 -1
- package/dist/mcp/client/index.js +1 -1
- package/dist/mcp/client/x402.d.ts +35 -4
- package/dist/mcp/client/x402.js +24 -3
- package/dist/mcp/client/x402.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/models/ai-sdk/index.d.ts +16 -14
- package/dist/models/ai-sdk/index.js +1 -2442
- package/dist/models/opencode/index.d.ts +65 -0
- package/dist/models/opencode/index.js +285 -0
- package/dist/models/opencode/index.js.map +1 -0
- package/dist/models/pi-ai/index.d.ts +9 -7
- package/dist/models/pi-ai/index.js +2 -1
- package/dist/models/pi-ai/index.js.map +1 -1
- package/dist/options-pwTNq9z6.d.ts +134 -0
- package/dist/protocol-BEgrJYC5.d.ts +331 -0
- package/dist/protocol-CukuI73M.d.ts +386 -0
- package/dist/protocol-cOklVBBD.js +227 -0
- package/dist/protocol-cOklVBBD.js.map +1 -0
- package/dist/{protocol-kHsr-f0X.js → protocol-eXeJxhgu.js} +2 -67
- package/dist/protocol-eXeJxhgu.js.map +1 -0
- package/dist/protocol-y2sSY584.js +148 -0
- package/dist/protocol-y2sSY584.js.map +1 -0
- package/dist/queue/index.d.ts +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-CrFCnv_J.d.ts} +2 -2
- package/dist/schedules/index.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-B9pNuxdT.js +2272 -0
- package/dist/sessions-B9pNuxdT.js.map +1 -0
- package/dist/settings-C45hDliX.js +253 -0
- package/dist/settings-C45hDliX.js.map +1 -0
- package/dist/{transport-PfFnt-Ld.d.ts → settings-NgGwF7Qt.d.ts} +14 -208
- package/dist/{src-BZeu-SCD.js → src-DgCbZtIR.js} +4 -4
- package/dist/{src-BZeu-SCD.js.map → src-DgCbZtIR.js.map} +1 -1
- package/dist/stall-watchdog-BpRF2uwG.js +195 -0
- package/dist/stall-watchdog-BpRF2uwG.js.map +1 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/store-DLeqKuj3.d.ts +310 -0
- package/dist/store-vgcPYo8K.js +432 -0
- package/dist/store-vgcPYo8K.js.map +1 -0
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/{streams-DW-UCOUc.js → streams-CITClVer.js} +16 -3
- package/dist/streams-CITClVer.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-fbG61qM8.d.ts} +2 -2
- package/dist/sub-routing.d.ts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{tasks-68DESxf0.js → tasks-CkTZNaV1.js} +2 -1
- package/dist/tasks-CkTZNaV1.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-PtlB4Uyn.d.ts} +2 -2
- package/dist/tool-3PA8xjB_.js +421 -0
- package/dist/tool-3PA8xjB_.js.map +1 -0
- package/dist/tool-BPJEbwaX.d.ts +278 -0
- package/dist/transport-k2mFrtL8.d.ts +200 -0
- package/dist/turns-DCV3ycDl.js +317 -0
- package/dist/turns-DCV3ycDl.js.map +1 -0
- package/dist/voice/client.js +1 -1
- package/dist/voice/index.d.ts +2 -2
- package/dist/voice/index.js +1 -1
- package/dist/websearch/index.d.ts +58 -0
- package/dist/websearch/index.js +2 -0
- package/dist/websearch/tools/ai-sdk.d.ts +77 -0
- package/dist/websearch/tools/ai-sdk.js +46 -0
- package/dist/websearch/tools/ai-sdk.js.map +1 -0
- package/dist/websearch/tools/pi.d.ts +82 -0
- package/dist/websearch/tools/pi.js +89 -0
- package/dist/websearch/tools/pi.js.map +1 -0
- package/dist/websearch/tools/tanstack-ai.d.ts +71 -0
- package/dist/websearch/tools/tanstack-ai.js +46 -0
- package/dist/websearch/tools/tanstack-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +12 -296
- package/dist/websockets/index.js +1 -1
- package/dist/{websockets-Ci-LZFHH.js → websockets-CmENGb-Z.js} +18 -4
- package/dist/websockets-CmENGb-Z.js.map +1 -0
- package/dist/{wire-types-CrjKvDyi.d.ts → wire-types-Cv0iwjk5.d.ts} +3 -51
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/dist/workflows.js +1 -1
- package/docs/browse-the-web.md +3 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +2 -1
- package/docs/lifecycle.md +40 -0
- package/docs/search-the-web.md +133 -0
- package/package.json +117 -38
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/channel-Bnm4S7T2.d.ts +0 -491
- package/dist/channels/ai-sdk.d.ts +0 -46
- package/dist/channels/ai-sdk.js +0 -120
- package/dist/channels/ai-sdk.js.map +0 -1
- package/dist/channels/email.js.map +0 -1
- package/dist/channels/index.d.ts +0 -233
- package/dist/channels/index.js +0 -608
- package/dist/channels/index.js.map +0 -1
- package/dist/channels/slack.js +0 -614
- package/dist/channels/slack.js.map +0 -1
- package/dist/channels/tanstack-ai.d.ts +0 -39
- package/dist/channels/tanstack-ai.js +0 -17
- package/dist/channels/tanstack-ai.js.map +0 -1
- package/dist/channels/telegram.js +0 -427
- package/dist/channels/telegram.js.map +0 -1
- package/dist/channels/voice.d.ts +0 -45
- package/dist/channels/voice.js +0 -122
- package/dist/channels/voice.js.map +0 -1
- package/dist/images-iiTDSyRu.js.map +0 -1
- package/dist/index-BB0kqhIz.d.ts +0 -101
- package/dist/ingress-BfetZbMO.js +0 -83
- package/dist/ingress-BfetZbMO.js.map +0 -1
- package/dist/internal-CYlgHl1l.js +0 -59
- package/dist/internal-CYlgHl1l.js.map +0 -1
- package/dist/models/ai-sdk/index.js.map +0 -1
- package/dist/protocol-kHsr-f0X.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/streams-DW-UCOUc.js.map +0 -1
- package/dist/surface-bZZJqBka.js +0 -17
- package/dist/surface-bZZJqBka.js.map +0 -1
- package/dist/tasks-68DESxf0.js.map +0 -1
- package/dist/tool-schema-CBjGPrsQ.js +0 -31
- package/dist/tool-schema-CBjGPrsQ.js.map +0 -1
- package/dist/websockets-Ci-LZFHH.js.map +0 -1
- package/docs/channels.md +0 -323
|
@@ -0,0 +1,2529 @@
|
|
|
1
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../../classPrivateFieldGet2-DZBYAB34.js";
|
|
2
|
+
import { t as _classPrivateMethodInitSpec } from "../../classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
3
|
+
import { n as bindLifecycleCapability, t as LifecycleCapability } from "../../capability-B4WbF81e.js";
|
|
4
|
+
import { n as STREAM_RESUME_NONE_REASONS, r as StreamAccumulator, t as CHAT_MESSAGE_TYPES } from "../../protocol-eXeJxhgu.js";
|
|
5
|
+
import { n as Streams } from "../../streams-CITClVer.js";
|
|
6
|
+
import { i as createToolsFromClientSchemas, n as iterateWithStallWatchdog, r as parseProtocolMessage, t as ChatStreamStalledError } from "../../stall-watchdog-BpRF2uwG.js";
|
|
7
|
+
import { t as Sessions } from "../../sessions-B9pNuxdT.js";
|
|
8
|
+
import { a as toTranscriptMessage, r as toResponseChunk, t as answerToolCall } from "../../turns-DCV3ycDl.js";
|
|
9
|
+
import { convertToModelMessages, stepCountIs, streamText, toUIMessageStream } from "ai";
|
|
10
|
+
//#region src/harness/think/events.ts
|
|
11
|
+
var _listeners = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
var _observers = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
/** Fan-out of session events to listeners, per session. */
|
|
14
|
+
var SessionEvents = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
_classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Map());
|
|
17
|
+
_classPrivateFieldInitSpec(this, _observers, /* @__PURE__ */ new Set());
|
|
18
|
+
}
|
|
19
|
+
/** Listen to every session. Returns the unsubscribe. */
|
|
20
|
+
observe(listener) {
|
|
21
|
+
_classPrivateFieldGet2(_observers, this).add(listener);
|
|
22
|
+
return () => {
|
|
23
|
+
_classPrivateFieldGet2(_observers, this).delete(listener);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Listen to one session. Returns the unsubscribe. */
|
|
27
|
+
subscribe(session, listener) {
|
|
28
|
+
let listeners = _classPrivateFieldGet2(_listeners, this).get(session);
|
|
29
|
+
if (!listeners) {
|
|
30
|
+
listeners = /* @__PURE__ */ new Set();
|
|
31
|
+
_classPrivateFieldGet2(_listeners, this).set(session, listeners);
|
|
32
|
+
}
|
|
33
|
+
listeners.add(listener);
|
|
34
|
+
return () => {
|
|
35
|
+
listeners.delete(listener);
|
|
36
|
+
if (listeners.size === 0) _classPrivateFieldGet2(_listeners, this).delete(session);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Deliver an event to a session's listeners. A throwing listener is logged. */
|
|
40
|
+
emit(session, event) {
|
|
41
|
+
for (const observer of _classPrivateFieldGet2(_observers, this)) try {
|
|
42
|
+
observer(session, event);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("A ThinkHarness observer failed", error);
|
|
45
|
+
}
|
|
46
|
+
for (const listener of _classPrivateFieldGet2(_listeners, this).get(session) ?? []) try {
|
|
47
|
+
listener(event);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("A ThinkHarness session listener failed", error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var _close = /* @__PURE__ */ new WeakMap();
|
|
54
|
+
var _listener = /* @__PURE__ */ new WeakMap();
|
|
55
|
+
var _queue = /* @__PURE__ */ new WeakMap();
|
|
56
|
+
var _onStop = /* @__PURE__ */ new WeakMap();
|
|
57
|
+
var _draining = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
var _stopped = /* @__PURE__ */ new WeakMap();
|
|
59
|
+
var _Watcher_brand = /* @__PURE__ */ new WeakSet();
|
|
60
|
+
/** One watch: events queue until `start`, then reach the listener in order. */
|
|
61
|
+
var Watcher = class {
|
|
62
|
+
constructor(state, onStop) {
|
|
63
|
+
_classPrivateMethodInitSpec(this, _Watcher_brand);
|
|
64
|
+
_classPrivateFieldInitSpec(this, _close, () => {});
|
|
65
|
+
_classPrivateFieldInitSpec(this, _listener, void 0);
|
|
66
|
+
_classPrivateFieldInitSpec(this, _queue, []);
|
|
67
|
+
_classPrivateFieldInitSpec(this, _onStop, void 0);
|
|
68
|
+
_classPrivateFieldInitSpec(this, _draining, false);
|
|
69
|
+
_classPrivateFieldInitSpec(this, _stopped, false);
|
|
70
|
+
this.state = state;
|
|
71
|
+
_classPrivateFieldSet2(_onStop, this, onStop);
|
|
72
|
+
this.closed = new Promise((resolve) => {
|
|
73
|
+
_classPrivateFieldSet2(_close, this, resolve);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Start delivering queued and later events. */
|
|
77
|
+
start(listener) {
|
|
78
|
+
_classPrivateFieldSet2(_listener, this, listener);
|
|
79
|
+
_assertClassBrand(_Watcher_brand, this, _drain).call(this);
|
|
80
|
+
}
|
|
81
|
+
/** Stop the watch. */
|
|
82
|
+
async stop() {
|
|
83
|
+
if (_classPrivateFieldGet2(_stopped, this)) return;
|
|
84
|
+
_classPrivateFieldSet2(_stopped, this, true);
|
|
85
|
+
_classPrivateFieldGet2(_onStop, this).call(this);
|
|
86
|
+
_classPrivateFieldGet2(_close, this).call(this);
|
|
87
|
+
}
|
|
88
|
+
/** Queue events for the listener. */
|
|
89
|
+
push(events) {
|
|
90
|
+
if (_classPrivateFieldGet2(_stopped, this) || events.length === 0) return;
|
|
91
|
+
_classPrivateFieldGet2(_queue, this).push(events);
|
|
92
|
+
_assertClassBrand(_Watcher_brand, this, _drain).call(this);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
async function _drain() {
|
|
96
|
+
if (!_classPrivateFieldGet2(_listener, this) || _classPrivateFieldGet2(_draining, this)) return;
|
|
97
|
+
_classPrivateFieldSet2(_draining, this, true);
|
|
98
|
+
try {
|
|
99
|
+
for (;;) {
|
|
100
|
+
const events = _classPrivateFieldGet2(_queue, this).shift();
|
|
101
|
+
if (!events || _classPrivateFieldGet2(_stopped, this)) return;
|
|
102
|
+
try {
|
|
103
|
+
await _classPrivateFieldGet2(_listener, this).call(this, events);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("A ThinkHarness session watcher failed", error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
_classPrivateFieldSet2(_draining, this, false);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/harness/think/records.ts
|
|
114
|
+
const STATUSES = /* @__PURE__ */ new Set([
|
|
115
|
+
"queued",
|
|
116
|
+
"running",
|
|
117
|
+
"done",
|
|
118
|
+
"unanswered"
|
|
119
|
+
]);
|
|
120
|
+
function parseStatus(value) {
|
|
121
|
+
if (!STATUSES.has(value)) throw new Error(`Unknown ThinkHarness operation status ${value}`);
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
function parseOperation(row) {
|
|
125
|
+
const input = JSON.parse(row.input);
|
|
126
|
+
return {
|
|
127
|
+
session: row.session_id,
|
|
128
|
+
operationId: row.operation_id,
|
|
129
|
+
seq: row.seq,
|
|
130
|
+
input,
|
|
131
|
+
status: parseStatus(row.status),
|
|
132
|
+
source: row.source === "client" ? "client" : "server",
|
|
133
|
+
parentId: row.parent_id ?? void 0,
|
|
134
|
+
messageId: row.message_id ?? void 0,
|
|
135
|
+
streamId: row.stream_id ?? void 0,
|
|
136
|
+
pendingModel: row.pending_model === 1,
|
|
137
|
+
steps: row.steps,
|
|
138
|
+
interruptions: row.interruptions,
|
|
139
|
+
overflowRetries: row.overflow_retries,
|
|
140
|
+
reason: row.reason ?? void 0,
|
|
141
|
+
text: row.text ?? void 0,
|
|
142
|
+
abandonReason: row.abandon_reason ?? void 0,
|
|
143
|
+
createdAt: row.created_at
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
var _sql = /* @__PURE__ */ new WeakMap();
|
|
147
|
+
var _ready = /* @__PURE__ */ new WeakMap();
|
|
148
|
+
/**
|
|
149
|
+
* The harness's own tables, over the object's SQLite: sessions, the
|
|
150
|
+
* operation queue, and started tool calls. Named apart from the shared
|
|
151
|
+
* `agents/harness/store`, which is a different, harness-agnostic store.
|
|
152
|
+
*/
|
|
153
|
+
var OperationRecords = class {
|
|
154
|
+
constructor(sql) {
|
|
155
|
+
_classPrivateFieldInitSpec(this, _sql, void 0);
|
|
156
|
+
_classPrivateFieldInitSpec(this, _ready, false);
|
|
157
|
+
_classPrivateFieldSet2(_sql, this, sql);
|
|
158
|
+
}
|
|
159
|
+
/** Create the tables. Idempotent. */
|
|
160
|
+
ensureTables() {
|
|
161
|
+
if (_classPrivateFieldGet2(_ready, this)) return;
|
|
162
|
+
_classPrivateFieldGet2(_sql, this).exec(`CREATE TABLE IF NOT EXISTS cf_think_harness_sessions (
|
|
163
|
+
id TEXT PRIMARY KEY,
|
|
164
|
+
parent TEXT,
|
|
165
|
+
client_tools TEXT,
|
|
166
|
+
created_at INTEGER NOT NULL
|
|
167
|
+
)`);
|
|
168
|
+
_classPrivateFieldGet2(_sql, this).exec(`CREATE TABLE IF NOT EXISTS cf_think_harness_operations (
|
|
169
|
+
session_id TEXT NOT NULL,
|
|
170
|
+
operation_id TEXT NOT NULL,
|
|
171
|
+
seq INTEGER NOT NULL,
|
|
172
|
+
input TEXT NOT NULL,
|
|
173
|
+
status TEXT NOT NULL,
|
|
174
|
+
source TEXT NOT NULL,
|
|
175
|
+
parent_id TEXT,
|
|
176
|
+
message_id TEXT,
|
|
177
|
+
stream_id TEXT,
|
|
178
|
+
pending_model INTEGER NOT NULL DEFAULT 0,
|
|
179
|
+
steps INTEGER NOT NULL DEFAULT 0,
|
|
180
|
+
interruptions INTEGER NOT NULL DEFAULT 0,
|
|
181
|
+
overflow_retries INTEGER NOT NULL DEFAULT 0,
|
|
182
|
+
reason TEXT,
|
|
183
|
+
text TEXT,
|
|
184
|
+
abandon_reason TEXT,
|
|
185
|
+
created_at INTEGER NOT NULL,
|
|
186
|
+
updated_at INTEGER NOT NULL,
|
|
187
|
+
PRIMARY KEY (session_id, operation_id)
|
|
188
|
+
)`);
|
|
189
|
+
if (!new Set(_classPrivateFieldGet2(_sql, this).exec(`PRAGMA table_info(cf_think_harness_operations)`).toArray().map((column) => column.name)).has("abandon_reason")) _classPrivateFieldGet2(_sql, this).exec(`ALTER TABLE cf_think_harness_operations ADD COLUMN abandon_reason TEXT`);
|
|
190
|
+
_classPrivateFieldGet2(_sql, this).exec(`CREATE INDEX IF NOT EXISTS cf_think_harness_operations_open
|
|
191
|
+
ON cf_think_harness_operations (session_id, status, seq)`);
|
|
192
|
+
_classPrivateFieldGet2(_sql, this).exec(`CREATE TABLE IF NOT EXISTS cf_think_harness_tool_calls (
|
|
193
|
+
session_id TEXT NOT NULL,
|
|
194
|
+
tool_call_id TEXT NOT NULL,
|
|
195
|
+
operation_id TEXT NOT NULL,
|
|
196
|
+
attempts INTEGER NOT NULL,
|
|
197
|
+
PRIMARY KEY (session_id, tool_call_id)
|
|
198
|
+
)`);
|
|
199
|
+
_classPrivateFieldSet2(_ready, this, true);
|
|
200
|
+
}
|
|
201
|
+
/** Record a session, unless it exists. Returns whether it was new. */
|
|
202
|
+
ensureSession(id, parent) {
|
|
203
|
+
return _classPrivateFieldGet2(_sql, this).exec(`INSERT OR IGNORE INTO cf_think_harness_sessions (id, parent, created_at)
|
|
204
|
+
VALUES (?, ?, ?)`, id, parent ?? null, Date.now()).rowsWritten > 0;
|
|
205
|
+
}
|
|
206
|
+
/** Every session, oldest first. */
|
|
207
|
+
sessions() {
|
|
208
|
+
return _classPrivateFieldGet2(_sql, this).exec(`SELECT id, parent, client_tools, created_at
|
|
209
|
+
FROM cf_think_harness_sessions ORDER BY created_at, id`).toArray().map((row) => ({
|
|
210
|
+
id: row.id,
|
|
211
|
+
parent: row.parent ?? void 0,
|
|
212
|
+
createdAt: row.created_at
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
/** The client tools a session's client last declared. */
|
|
216
|
+
clientTools(session) {
|
|
217
|
+
const row = _classPrivateFieldGet2(_sql, this).exec(`SELECT client_tools FROM cf_think_harness_sessions WHERE id = ?`, session).toArray()[0];
|
|
218
|
+
if (!row?.client_tools) return [];
|
|
219
|
+
return JSON.parse(row.client_tools);
|
|
220
|
+
}
|
|
221
|
+
/** Replace the client tools a session's client declared. */
|
|
222
|
+
setClientTools(session, tools) {
|
|
223
|
+
_classPrivateFieldGet2(_sql, this).exec(`UPDATE cf_think_harness_sessions SET client_tools = ? WHERE id = ?`, JSON.stringify(tools), session);
|
|
224
|
+
}
|
|
225
|
+
/** Insert a queued operation. Returns false when the id already exists. */
|
|
226
|
+
insert(session, operationId, input, source) {
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
return _classPrivateFieldGet2(_sql, this).exec(`INSERT OR IGNORE INTO cf_think_harness_operations
|
|
229
|
+
(session_id, operation_id, seq, input, status, source, created_at, updated_at)
|
|
230
|
+
VALUES (?, ?,
|
|
231
|
+
(SELECT COALESCE(MAX(seq), 0) + 1 FROM cf_think_harness_operations),
|
|
232
|
+
?, 'queued', ?, ?, ?)`, session, operationId, JSON.stringify(input), source, now, now).rowsWritten > 0;
|
|
233
|
+
}
|
|
234
|
+
/** One operation. */
|
|
235
|
+
get(session, operationId) {
|
|
236
|
+
const row = _classPrivateFieldGet2(_sql, this).exec(`SELECT * FROM cf_think_harness_operations
|
|
237
|
+
WHERE session_id = ? AND operation_id = ?`, session, operationId).toArray()[0];
|
|
238
|
+
return row ? parseOperation(row) : void 0;
|
|
239
|
+
}
|
|
240
|
+
/** Unsettled operations, oldest first; every session when none is given. */
|
|
241
|
+
open(session) {
|
|
242
|
+
return (session === void 0 ? _classPrivateFieldGet2(_sql, this).exec(`SELECT * FROM cf_think_harness_operations
|
|
243
|
+
WHERE status IN ('queued', 'running') ORDER BY seq`) : _classPrivateFieldGet2(_sql, this).exec(`SELECT * FROM cf_think_harness_operations
|
|
244
|
+
WHERE session_id = ? AND status IN ('queued', 'running')
|
|
245
|
+
ORDER BY seq`, session)).toArray().map(parseOperation);
|
|
246
|
+
}
|
|
247
|
+
/** Apply a change to an operation's fields. */
|
|
248
|
+
update(session, operationId, change) {
|
|
249
|
+
const sets = [];
|
|
250
|
+
const values = [];
|
|
251
|
+
const set = (column, value) => {
|
|
252
|
+
sets.push(`${column} = ?`);
|
|
253
|
+
values.push(value);
|
|
254
|
+
};
|
|
255
|
+
if (change.status !== void 0) set("status", change.status);
|
|
256
|
+
if (change.parentId !== void 0) set("parent_id", change.parentId);
|
|
257
|
+
if (change.messageId !== void 0) set("message_id", change.messageId);
|
|
258
|
+
if (change.streamId !== void 0) set("stream_id", change.streamId);
|
|
259
|
+
if (change.pendingModel !== void 0) set("pending_model", change.pendingModel ? 1 : 0);
|
|
260
|
+
if (change.steps !== void 0) set("steps", change.steps);
|
|
261
|
+
if (change.interruptions !== void 0) set("interruptions", change.interruptions);
|
|
262
|
+
if (change.overflowRetries !== void 0) set("overflow_retries", change.overflowRetries);
|
|
263
|
+
if (change.reason !== void 0) set("reason", change.reason);
|
|
264
|
+
if (change.text !== void 0) set("text", change.text);
|
|
265
|
+
if (change.abandonReason !== void 0) set("abandon_reason", change.abandonReason);
|
|
266
|
+
set("updated_at", Date.now());
|
|
267
|
+
_classPrivateFieldGet2(_sql, this).exec(`UPDATE cf_think_harness_operations SET ${sets.join(", ")}
|
|
268
|
+
WHERE session_id = ? AND operation_id = ?`, ...values, session, operationId);
|
|
269
|
+
}
|
|
270
|
+
/** Forget every settled operation in a session, for a reset. */
|
|
271
|
+
deleteSettled(session) {
|
|
272
|
+
_classPrivateFieldGet2(_sql, this).exec(`DELETE FROM cf_think_harness_operations
|
|
273
|
+
WHERE session_id = ? AND status IN ('done', 'unanswered')`, session);
|
|
274
|
+
_classPrivateFieldGet2(_sql, this).exec(`DELETE FROM cf_think_harness_tool_calls WHERE session_id = ?`, session);
|
|
275
|
+
}
|
|
276
|
+
/** The record of a started tool call, if it was started. */
|
|
277
|
+
toolCall(session, toolCallId) {
|
|
278
|
+
const row = _classPrivateFieldGet2(_sql, this).exec(`SELECT * FROM cf_think_harness_tool_calls
|
|
279
|
+
WHERE session_id = ? AND tool_call_id = ?`, session, toolCallId).toArray()[0];
|
|
280
|
+
return row ? {
|
|
281
|
+
session: row.session_id,
|
|
282
|
+
toolCallId: row.tool_call_id,
|
|
283
|
+
operationId: row.operation_id,
|
|
284
|
+
attempts: row.attempts
|
|
285
|
+
} : void 0;
|
|
286
|
+
}
|
|
287
|
+
/** Record that a tool call is starting. Returns the attempt number. */
|
|
288
|
+
startToolCall(session, toolCallId, operationId) {
|
|
289
|
+
const attempts = (this.toolCall(session, toolCallId)?.attempts ?? 0) + 1;
|
|
290
|
+
_classPrivateFieldGet2(_sql, this).exec(`INSERT OR REPLACE INTO cf_think_harness_tool_calls
|
|
291
|
+
(session_id, tool_call_id, operation_id, attempts)
|
|
292
|
+
VALUES (?, ?, ?, ?)`, session, toolCallId, operationId, attempts);
|
|
293
|
+
return attempts;
|
|
294
|
+
}
|
|
295
|
+
/** Forget a tool call once its result is in the transcript. */
|
|
296
|
+
finishToolCall(session, toolCallId) {
|
|
297
|
+
_classPrivateFieldGet2(_sql, this).exec(`DELETE FROM cf_think_harness_tool_calls
|
|
298
|
+
WHERE session_id = ? AND tool_call_id = ?`, session, toolCallId);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/harness/think/tools.ts
|
|
303
|
+
/** The model-facing copy of a tool set, restricted to `activeTools` if given. */
|
|
304
|
+
function modelTools(tools, activeTools) {
|
|
305
|
+
const active = activeTools ? new Set(activeTools) : void 0;
|
|
306
|
+
const offered = {};
|
|
307
|
+
for (const [name, tool] of Object.entries(tools)) {
|
|
308
|
+
if (active && !active.has(name)) continue;
|
|
309
|
+
if (tool.type === "provider") {
|
|
310
|
+
offered[name] = tool;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const { execute: _execute, needsApproval: _needsApproval, ...definition } = tool;
|
|
314
|
+
if ("recovery" in definition) delete definition.recovery;
|
|
315
|
+
offered[name] = definition;
|
|
316
|
+
}
|
|
317
|
+
return offered;
|
|
318
|
+
}
|
|
319
|
+
/** Whether the harness runs this tool, as opposed to a client or provider. */
|
|
320
|
+
function isServerTool(tools, name) {
|
|
321
|
+
const tool = Object.hasOwn(tools, name) ? tools[name] : void 0;
|
|
322
|
+
return tool !== void 0 && tool.type !== "provider" && typeof tool.execute === "function";
|
|
323
|
+
}
|
|
324
|
+
/** Whether a tool's own `needsApproval` asks for approval of this call. */
|
|
325
|
+
async function toolNeedsApproval(tool, input, options) {
|
|
326
|
+
const needsApproval = tool.needsApproval;
|
|
327
|
+
if (typeof needsApproval === "function") return Boolean(await needsApproval(input, {
|
|
328
|
+
...options,
|
|
329
|
+
context: void 0
|
|
330
|
+
}));
|
|
331
|
+
return needsApproval === true;
|
|
332
|
+
}
|
|
333
|
+
function isAsyncIterable(value) {
|
|
334
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Run one tool's `execute`. A streaming tool's outputs before its last are
|
|
338
|
+
* reported to `onPreliminary`; its last output is the result.
|
|
339
|
+
*/
|
|
340
|
+
async function executeTool(tool, input, options) {
|
|
341
|
+
const execute = tool.execute;
|
|
342
|
+
if (typeof execute !== "function") return {
|
|
343
|
+
ok: false,
|
|
344
|
+
errorText: "The tool has no execute function"
|
|
345
|
+
};
|
|
346
|
+
try {
|
|
347
|
+
const result = await execute(input, {
|
|
348
|
+
toolCallId: options.toolCallId,
|
|
349
|
+
messages: options.messages,
|
|
350
|
+
abortSignal: options.abortSignal,
|
|
351
|
+
context: void 0
|
|
352
|
+
});
|
|
353
|
+
if (!isAsyncIterable(result)) return {
|
|
354
|
+
ok: true,
|
|
355
|
+
output: result
|
|
356
|
+
};
|
|
357
|
+
let last;
|
|
358
|
+
let seen = false;
|
|
359
|
+
for await (const output of result) {
|
|
360
|
+
if (seen) options.onPreliminary(last);
|
|
361
|
+
last = output;
|
|
362
|
+
seen = true;
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
ok: true,
|
|
366
|
+
output: last
|
|
367
|
+
};
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return {
|
|
370
|
+
ok: false,
|
|
371
|
+
errorText: error instanceof Error ? error.message : String(error)
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/harness/think/turn.ts
|
|
377
|
+
/** Whether a part is a tool call. */
|
|
378
|
+
function isToolCallPart(part) {
|
|
379
|
+
return (part.type.startsWith("tool-") || part.type === "dynamic-tool") && "toolCallId" in part;
|
|
380
|
+
}
|
|
381
|
+
/** The tool name a tool call part carries. */
|
|
382
|
+
function toolNameOf(part) {
|
|
383
|
+
return part.type === "dynamic-tool" ? part.toolName : part.type.slice(5);
|
|
384
|
+
}
|
|
385
|
+
/** The parts of a message's last model step. */
|
|
386
|
+
function lastStep(message) {
|
|
387
|
+
let start = 0;
|
|
388
|
+
message.parts.forEach((part, index) => {
|
|
389
|
+
if (part.type === "step-start") start = index + 1;
|
|
390
|
+
});
|
|
391
|
+
return message.parts.slice(start);
|
|
392
|
+
}
|
|
393
|
+
/** Whether a tool call has its result, error or denial. */
|
|
394
|
+
function isSettled(part) {
|
|
395
|
+
return part.state === "output-available" || part.state === "output-error" || part.state === "output-denied";
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Decide the turn's next action.
|
|
399
|
+
*
|
|
400
|
+
* @param progress - The operation's durable progress.
|
|
401
|
+
* @param message - The turn's assistant message, once it has one.
|
|
402
|
+
* @param isServerTool - Whether the harness runs a tool of this name.
|
|
403
|
+
* @param maxSteps - Most model calls one operation makes.
|
|
404
|
+
* @returns What to do next.
|
|
405
|
+
*/
|
|
406
|
+
function nextAction(progress, message, isServerTool, maxSteps) {
|
|
407
|
+
if (progress.streamId !== void 0) return { _tag: "recover-model" };
|
|
408
|
+
if (message === void 0) return progress.pendingModel ? { _tag: "call-model" } : { _tag: "end" };
|
|
409
|
+
const runnable = [];
|
|
410
|
+
let awaiting = false;
|
|
411
|
+
for (const part of lastStep(message)) {
|
|
412
|
+
if (!isToolCallPart(part) || isSettled(part) || part.providerExecuted) continue;
|
|
413
|
+
const server = isServerTool(toolNameOf(part));
|
|
414
|
+
switch (part.state) {
|
|
415
|
+
case "input-available":
|
|
416
|
+
if (server) runnable.push(part);
|
|
417
|
+
else awaiting = true;
|
|
418
|
+
break;
|
|
419
|
+
case "approval-responded":
|
|
420
|
+
if (server && part.approval.approved) runnable.push(part);
|
|
421
|
+
else awaiting = true;
|
|
422
|
+
break;
|
|
423
|
+
case "approval-requested":
|
|
424
|
+
awaiting = true;
|
|
425
|
+
break;
|
|
426
|
+
case "input-streaming": break;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (runnable.length > 0) return {
|
|
430
|
+
_tag: "run-tools",
|
|
431
|
+
calls: runnable
|
|
432
|
+
};
|
|
433
|
+
if (awaiting) return { _tag: "await-input" };
|
|
434
|
+
if (progress.pendingModel && progress.steps < maxSteps) return { _tag: "call-model" };
|
|
435
|
+
return { _tag: "end" };
|
|
436
|
+
}
|
|
437
|
+
/** The text of a message's text parts. */
|
|
438
|
+
function textOf(message) {
|
|
439
|
+
if (!message) return "";
|
|
440
|
+
return message.parts.map((part) => part.type === "text" ? part.text : "").join("");
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The partial message an interrupted model call leaves, made safe to keep:
|
|
444
|
+
* tool calls whose input never finished streaming are dropped, since the
|
|
445
|
+
* model never committed to them.
|
|
446
|
+
*/
|
|
447
|
+
function settledPartial(message) {
|
|
448
|
+
return {
|
|
449
|
+
...message,
|
|
450
|
+
parts: message.parts.filter((part) => !(isToolCallPart(part) && part.state === "input-streaming"))
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/** Whether a message has anything besides step markers. */
|
|
454
|
+
function hasContent(message) {
|
|
455
|
+
return message.parts.some((part) => part.type !== "step-start");
|
|
456
|
+
}
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/harness/think/harness.ts
|
|
459
|
+
/**
|
|
460
|
+
* Thrown by `submit()` when asked to steer a running turn, which
|
|
461
|
+
* ThinkHarness does not support. Submit without `whenBusy` to queue the
|
|
462
|
+
* input behind the running turn instead.
|
|
463
|
+
*
|
|
464
|
+
* @experimental
|
|
465
|
+
*/
|
|
466
|
+
var SteerNotSupportedError = class extends Error {
|
|
467
|
+
/** @param session - The session the submission was for. */
|
|
468
|
+
constructor(session) {
|
|
469
|
+
super("ThinkHarness does not support whenBusy: \"steer\". Submit without it to queue the input behind the running turn.");
|
|
470
|
+
this.session = session;
|
|
471
|
+
this._tag = "SteerNotSupportedError";
|
|
472
|
+
this.name = "SteerNotSupportedError";
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
/** The root session's id: the Sessions capability's default session. */
|
|
476
|
+
const ROOT_SESSION = "";
|
|
477
|
+
const WAKE_FN = "wake";
|
|
478
|
+
const HEARTBEAT_MS = 3e4;
|
|
479
|
+
const DEFAULT_MAX_STEPS = 10;
|
|
480
|
+
const DEFAULT_MAX_ATTEMPTS = 10;
|
|
481
|
+
const DEFAULT_BACKOFF_MS = 1e3;
|
|
482
|
+
const MAX_BACKOFF_MS = 6e4;
|
|
483
|
+
const DEFAULT_STALL_TIMEOUT_MS = 12e4;
|
|
484
|
+
const MAX_OVERFLOW_RETRIES = 1;
|
|
485
|
+
/** Ceiling for one stored segment, under the 2 MB SQLite row limit. */
|
|
486
|
+
const STREAM_MAX_CHUNK_BYTES = 19e5;
|
|
487
|
+
/** Chunks packed into one stored stream segment. */
|
|
488
|
+
const SEGMENT_CHUNKS = 10;
|
|
489
|
+
/** Raw bytes packed into one stored segment before it is written. */
|
|
490
|
+
const SEGMENT_MAX_BYTES = 256e3;
|
|
491
|
+
/** Longest a streamed chunk waits in memory before it is written. */
|
|
492
|
+
const FLUSH_AFTER_MS = 100;
|
|
493
|
+
/** Segments read at a time when rebuilding an interrupted model call. */
|
|
494
|
+
const READ_PAGE = 50;
|
|
495
|
+
const INTERRUPTED_TOOL_ERROR = "The tool call was interrupted before it finished, because the agent restarted. It may or may not have taken effect.";
|
|
496
|
+
function wakeJobId(session) {
|
|
497
|
+
return `think-wake:${session}`;
|
|
498
|
+
}
|
|
499
|
+
function sessionOfJob(payload) {
|
|
500
|
+
return typeof payload === "object" && payload !== null && "session" in payload && typeof payload.session === "string" ? payload.session : void 0;
|
|
501
|
+
}
|
|
502
|
+
async function resolve(value, session) {
|
|
503
|
+
if (typeof value === "function") return value({ session });
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
function errorText(error) {
|
|
507
|
+
return error instanceof Error ? error.message : String(error);
|
|
508
|
+
}
|
|
509
|
+
function isToolAnswer(input) {
|
|
510
|
+
return typeof input === "object" && !Array.isArray(input) && "type" in input && (input.type === "approval" || input.type === "tool-result");
|
|
511
|
+
}
|
|
512
|
+
function isUIMessage(input) {
|
|
513
|
+
return typeof input === "object" && !Array.isArray(input) && "role" in input && "parts" in input;
|
|
514
|
+
}
|
|
515
|
+
function harnessInputMessage(input, id) {
|
|
516
|
+
return {
|
|
517
|
+
id: input.messageId ?? id,
|
|
518
|
+
role: "user",
|
|
519
|
+
parts: input.parts.map((part) => part.type === "text" ? {
|
|
520
|
+
type: "text",
|
|
521
|
+
text: part.text
|
|
522
|
+
} : {
|
|
523
|
+
type: "file",
|
|
524
|
+
mediaType: part.mediaType,
|
|
525
|
+
url: part.url,
|
|
526
|
+
...part.filename !== void 0 && { filename: part.filename }
|
|
527
|
+
})
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
/** The stored form of an input. */
|
|
531
|
+
function operationInput(input, operationId, autoContinue) {
|
|
532
|
+
if (typeof input === "string") return {
|
|
533
|
+
kind: "messages",
|
|
534
|
+
messages: [{
|
|
535
|
+
id: operationId,
|
|
536
|
+
role: "user",
|
|
537
|
+
parts: [{
|
|
538
|
+
type: "text",
|
|
539
|
+
text: input
|
|
540
|
+
}]
|
|
541
|
+
}]
|
|
542
|
+
};
|
|
543
|
+
if (Array.isArray(input)) return {
|
|
544
|
+
kind: "messages",
|
|
545
|
+
messages: input
|
|
546
|
+
};
|
|
547
|
+
if (isToolAnswer(input)) return {
|
|
548
|
+
kind: "answer",
|
|
549
|
+
answer: input,
|
|
550
|
+
autoContinue
|
|
551
|
+
};
|
|
552
|
+
if (isUIMessage(input)) return {
|
|
553
|
+
kind: "messages",
|
|
554
|
+
messages: [input]
|
|
555
|
+
};
|
|
556
|
+
return {
|
|
557
|
+
kind: "messages",
|
|
558
|
+
messages: [harnessInputMessage(input, operationId)]
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function toolCallAnswer(answer) {
|
|
562
|
+
return answer.type === "approval" ? {
|
|
563
|
+
type: "approval-response",
|
|
564
|
+
approvalId: answer.approvalId,
|
|
565
|
+
approved: answer.approved,
|
|
566
|
+
...answer.reason !== void 0 && { reason: answer.reason }
|
|
567
|
+
} : {
|
|
568
|
+
type: "tool-result",
|
|
569
|
+
toolCallId: answer.toolCallId,
|
|
570
|
+
result: answer.result
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
/** Replace one tool call part of a message. */
|
|
574
|
+
function withToolPart(message, toolCallId, update) {
|
|
575
|
+
return {
|
|
576
|
+
...message,
|
|
577
|
+
parts: message.parts.map((part) => isToolCallPart(part) && part.toolCallId === toolCallId ? update(part) : part)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function withOutcome(part, outcome) {
|
|
581
|
+
return outcome.ok ? {
|
|
582
|
+
...part,
|
|
583
|
+
state: "output-available",
|
|
584
|
+
output: outcome.output,
|
|
585
|
+
preliminary: void 0
|
|
586
|
+
} : {
|
|
587
|
+
...part,
|
|
588
|
+
state: "output-error",
|
|
589
|
+
errorText: outcome.errorText
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function asUIMessages(messages) {
|
|
593
|
+
return messages;
|
|
594
|
+
}
|
|
595
|
+
function asUIMessage(message) {
|
|
596
|
+
return message;
|
|
597
|
+
}
|
|
598
|
+
function asSessionMessage(message) {
|
|
599
|
+
return message;
|
|
600
|
+
}
|
|
601
|
+
var _options = /* @__PURE__ */ new WeakMap();
|
|
602
|
+
var _events = /* @__PURE__ */ new WeakMap();
|
|
603
|
+
var _drives = /* @__PURE__ */ new WeakMap();
|
|
604
|
+
var _live = /* @__PURE__ */ new WeakMap();
|
|
605
|
+
var _waiters = /* @__PURE__ */ new WeakMap();
|
|
606
|
+
var _configured = /* @__PURE__ */ new WeakMap();
|
|
607
|
+
var _streams = /* @__PURE__ */ new WeakMap();
|
|
608
|
+
var _sessions = /* @__PURE__ */ new WeakMap();
|
|
609
|
+
var _bound = /* @__PURE__ */ new WeakMap();
|
|
610
|
+
var _ownMessages = /* @__PURE__ */ new WeakMap();
|
|
611
|
+
var _ownClears = /* @__PURE__ */ new WeakMap();
|
|
612
|
+
var _heartbeatMs = /* @__PURE__ */ new WeakMap();
|
|
613
|
+
var _records = /* @__PURE__ */ new WeakMap();
|
|
614
|
+
var _ThinkHarness_brand = /* @__PURE__ */ new WeakSet();
|
|
615
|
+
/**
|
|
616
|
+
* Think's agent loop hosted in a Durable Object, behind the same small
|
|
617
|
+
* harness interface as `PiHarness`: `harness.prompt()`, `harness.sessions`,
|
|
618
|
+
* `harness.session(id)`. It implements the shared `AgentHarness`
|
|
619
|
+
* interface, so a Channels host can serve its sessions.
|
|
620
|
+
*
|
|
621
|
+
* Transcripts live in a Sessions capability and in-flight model output in
|
|
622
|
+
* a Streams capability, both owned by the harness, and the harness's queue
|
|
623
|
+
* of operations in its own table. The harness runs every server tool call itself, so after an
|
|
624
|
+
* eviction it knows exactly which call was cut short and applies that
|
|
625
|
+
* tool's recovery policy.
|
|
626
|
+
*
|
|
627
|
+
* Durability is the Lifecycle's job queue, used directly: one wake job per
|
|
628
|
+
* session. A submission is written, then the session's wake is scheduled.
|
|
629
|
+
* While a session has work, the wake job reschedules itself as a heartbeat,
|
|
630
|
+
* so an eviction leaves it due and its alarm restarts the object. Each step
|
|
631
|
+
* of a turn reads only durable state, so the restarted object continues
|
|
632
|
+
* where the last write left off.
|
|
633
|
+
*
|
|
634
|
+
* @experimental The API may change between releases.
|
|
635
|
+
*/
|
|
636
|
+
var ThinkHarness = class extends LifecycleCapability {
|
|
637
|
+
/**
|
|
638
|
+
* @param options - The model, tools and hooks.
|
|
639
|
+
*/
|
|
640
|
+
constructor(options) {
|
|
641
|
+
super("think-harness");
|
|
642
|
+
_classPrivateMethodInitSpec(this, _ThinkHarness_brand);
|
|
643
|
+
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
644
|
+
_classPrivateFieldInitSpec(this, _events, new SessionEvents());
|
|
645
|
+
_classPrivateFieldInitSpec(this, _drives, /* @__PURE__ */ new Map());
|
|
646
|
+
_classPrivateFieldInitSpec(this, _live, /* @__PURE__ */ new Map());
|
|
647
|
+
_classPrivateFieldInitSpec(this, _waiters, /* @__PURE__ */ new Map());
|
|
648
|
+
_classPrivateFieldInitSpec(this, _configured, /* @__PURE__ */ new Set());
|
|
649
|
+
_classPrivateFieldInitSpec(this, _streams, new Streams({ maxChunkBytes: STREAM_MAX_CHUNK_BYTES }));
|
|
650
|
+
_classPrivateFieldInitSpec(this, _sessions, void 0);
|
|
651
|
+
_classPrivateFieldInitSpec(this, _bound, false);
|
|
652
|
+
_classPrivateFieldInitSpec(this, _ownMessages, /* @__PURE__ */ new Map());
|
|
653
|
+
_classPrivateFieldInitSpec(this, _ownClears, /* @__PURE__ */ new Map());
|
|
654
|
+
_classPrivateFieldInitSpec(this, _heartbeatMs, HEARTBEAT_MS);
|
|
655
|
+
_classPrivateFieldInitSpec(this, _records, void 0);
|
|
656
|
+
_classPrivateFieldSet2(_options, this, options);
|
|
657
|
+
_classPrivateFieldSet2(_sessions, this, new Sessions({ ...options.reservedMetadataKeys && { reservedMetadataKeys: options.reservedMetadataKeys } }));
|
|
658
|
+
this.sessions = new ThinkSessions(this);
|
|
659
|
+
}
|
|
660
|
+
/** Create the harness's tables and wake every session with open work. */
|
|
661
|
+
async onStart() {
|
|
662
|
+
_assertClassBrand(_ThinkHarness_brand, this, _bindOwned).call(this);
|
|
663
|
+
await _classPrivateFieldGet2(_sessions, this).onStart();
|
|
664
|
+
await _classPrivateFieldGet2(_streams, this).onStart();
|
|
665
|
+
const store = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this);
|
|
666
|
+
store.ensureSession("");
|
|
667
|
+
const sessions = new Set(store.open().map((op) => op.session));
|
|
668
|
+
for (const session of sessions) {
|
|
669
|
+
const wake = this.lifecycle.jobs.get(wakeJobId(session));
|
|
670
|
+
if (wake && wake.time > Date.now()) continue;
|
|
671
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _wake).call(this, session);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* The alarm memory-limit breaker sealed: the work it stopped would only
|
|
676
|
+
* run out of memory again. Mark the running operations to be settled
|
|
677
|
+
* `unanswered` with reason `"out_of_memory"` rather than run again.
|
|
678
|
+
* Queued operations behind them still run. Runs after the isolate's
|
|
679
|
+
* work unwound, so it keeps to small synchronous writes.
|
|
680
|
+
*/
|
|
681
|
+
onMemoryLimit(context) {
|
|
682
|
+
if (!context.sealed) return;
|
|
683
|
+
const store = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this);
|
|
684
|
+
for (const op of store.open()) {
|
|
685
|
+
if (op.status !== "running") continue;
|
|
686
|
+
store.update(op.session, op.operationId, { abandonReason: "out_of_memory" });
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/** Run a session's wake job. */
|
|
690
|
+
async onJob(context) {
|
|
691
|
+
if (context.job.fn !== WAKE_FN) return;
|
|
692
|
+
const session = sessionOfJob(context.job.payload);
|
|
693
|
+
if (session !== void 0) return _assertClassBrand(_ThinkHarness_brand, this, _wakeStep).call(this, session);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* A handle on one session. No I/O until you call it.
|
|
697
|
+
*
|
|
698
|
+
* @param id - The session. Default: the root session.
|
|
699
|
+
*/
|
|
700
|
+
session(id = "") {
|
|
701
|
+
return new ThinkSession(this, id);
|
|
702
|
+
}
|
|
703
|
+
/** Submit to the root session and wait for the answer. */
|
|
704
|
+
prompt(input, options = {}) {
|
|
705
|
+
return this.session().prompt(input, options);
|
|
706
|
+
}
|
|
707
|
+
/** Durably submit to the root session. Resolves before the model runs. */
|
|
708
|
+
submit(input, options = {}) {
|
|
709
|
+
return this.session().submit(input, options);
|
|
710
|
+
}
|
|
711
|
+
/** Operations not settled yet, oldest first, in one session or all. */
|
|
712
|
+
async pending(session) {
|
|
713
|
+
await this.lifecycle.ready();
|
|
714
|
+
return _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).open(session).map((op) => ({
|
|
715
|
+
operationId: op.operationId,
|
|
716
|
+
session: op.session,
|
|
717
|
+
status: op.status === "queued" ? "queued" : "running"
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
/** @internal */
|
|
721
|
+
async enqueue(session, input, options) {
|
|
722
|
+
if (options.whenBusy === "steer") throw new SteerNotSupportedError(session);
|
|
723
|
+
await this.lifecycle.ready();
|
|
724
|
+
const operationId = options.operationId ?? crypto.randomUUID();
|
|
725
|
+
const store = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this);
|
|
726
|
+
let accepted = false;
|
|
727
|
+
this.lifecycle.storage.transactionSync(() => {
|
|
728
|
+
store.ensureSession(session);
|
|
729
|
+
if (options.clientTools !== void 0) store.setClientTools(session, options.clientTools);
|
|
730
|
+
accepted = store.insert(session, operationId, input, options.source ?? "server");
|
|
731
|
+
});
|
|
732
|
+
if (accepted) _classPrivateFieldGet2(_events, this).emit(session, {
|
|
733
|
+
type: "operation",
|
|
734
|
+
status: {
|
|
735
|
+
operationId,
|
|
736
|
+
status: "queued"
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _wake).call(this, session);
|
|
740
|
+
_assertClassBrand(_ThinkHarness_brand, this, _ensureDrive).call(this, session);
|
|
741
|
+
return {
|
|
742
|
+
operationId,
|
|
743
|
+
session,
|
|
744
|
+
accepted
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
/** @internal Stop one operation, or everything open in a session. */
|
|
748
|
+
async abort(session, operationId) {
|
|
749
|
+
await this.lifecycle.ready();
|
|
750
|
+
const store = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this);
|
|
751
|
+
const targets = operationId === void 0 ? store.open(session) : [store.get(session, operationId)].filter((op) => op !== void 0 && (op.status === "queued" || op.status === "running"));
|
|
752
|
+
for (const op of targets) {
|
|
753
|
+
const live = _classPrivateFieldGet2(_live, this).get(session);
|
|
754
|
+
if (live?.operationId === op.operationId) live.abort.abort();
|
|
755
|
+
else if (op.status === "queued") {
|
|
756
|
+
store.update(session, op.operationId, {
|
|
757
|
+
status: "unanswered",
|
|
758
|
+
reason: "withdrawn"
|
|
759
|
+
});
|
|
760
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, op.operationId);
|
|
761
|
+
} else await _assertClassBrand(_ThinkHarness_brand, this, _abandon).call(this, op, "aborted");
|
|
762
|
+
}
|
|
763
|
+
return targets.length > 0;
|
|
764
|
+
}
|
|
765
|
+
/** @internal Wait for an operation to settle. */
|
|
766
|
+
async wait(session, operationId, signal) {
|
|
767
|
+
await this.lifecycle.ready();
|
|
768
|
+
for (;;) {
|
|
769
|
+
const op = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).get(session, operationId);
|
|
770
|
+
if (!op) return {
|
|
771
|
+
operationId,
|
|
772
|
+
session,
|
|
773
|
+
status: "unanswered",
|
|
774
|
+
reason: "not_found"
|
|
775
|
+
};
|
|
776
|
+
if (op.status === "done" || op.status === "unanswered") return _assertClassBrand(_ThinkHarness_brand, this, _result).call(this, op);
|
|
777
|
+
await new Promise((resolveWait, reject) => {
|
|
778
|
+
if (signal?.aborted) {
|
|
779
|
+
reject(signal.reason);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const key = waiterKey(session, operationId);
|
|
783
|
+
const waiters = _classPrivateFieldGet2(_waiters, this).get(key) ?? /* @__PURE__ */ new Set();
|
|
784
|
+
_classPrivateFieldGet2(_waiters, this).set(key, waiters);
|
|
785
|
+
const done = () => {
|
|
786
|
+
signal?.removeEventListener("abort", aborted);
|
|
787
|
+
resolveWait();
|
|
788
|
+
};
|
|
789
|
+
const aborted = () => {
|
|
790
|
+
waiters.delete(done);
|
|
791
|
+
reject(signal?.reason);
|
|
792
|
+
};
|
|
793
|
+
waiters.add(done);
|
|
794
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/** @internal */
|
|
799
|
+
async inspect(session, operationId) {
|
|
800
|
+
await this.lifecycle.ready();
|
|
801
|
+
const op = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).get(session, operationId);
|
|
802
|
+
if (!op) return void 0;
|
|
803
|
+
switch (op.status) {
|
|
804
|
+
case "queued":
|
|
805
|
+
case "running": return {
|
|
806
|
+
operationId,
|
|
807
|
+
session,
|
|
808
|
+
status: op.status
|
|
809
|
+
};
|
|
810
|
+
case "done":
|
|
811
|
+
case "unanswered": return _assertClassBrand(_ThinkHarness_brand, this, _result).call(this, op);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/** @internal The active transcript of a session. */
|
|
815
|
+
async messages(session) {
|
|
816
|
+
return asUIMessages(await _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session).getHistory());
|
|
817
|
+
}
|
|
818
|
+
/** @internal Abort everything in a session and clear its transcript. */
|
|
819
|
+
async reset(session, handoff) {
|
|
820
|
+
await this.abort(session);
|
|
821
|
+
await _classPrivateFieldGet2(_drives, this).get(session);
|
|
822
|
+
const handle = _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session);
|
|
823
|
+
_classPrivateFieldGet2(_ownClears, this).set(session, (_classPrivateFieldGet2(_ownClears, this).get(session) ?? 0) + 1);
|
|
824
|
+
try {
|
|
825
|
+
await handle.clearMessages();
|
|
826
|
+
} finally {
|
|
827
|
+
const left = (_classPrivateFieldGet2(_ownClears, this).get(session) ?? 1) - 1;
|
|
828
|
+
if (left === 0) _classPrivateFieldGet2(_ownClears, this).delete(session);
|
|
829
|
+
else _classPrivateFieldGet2(_ownClears, this).set(session, left);
|
|
830
|
+
}
|
|
831
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).deleteSettled(session);
|
|
832
|
+
_classPrivateFieldGet2(_events, this).emit(session, { type: "reset" });
|
|
833
|
+
if (handoff) {
|
|
834
|
+
const note = {
|
|
835
|
+
id: crypto.randomUUID(),
|
|
836
|
+
role: "system",
|
|
837
|
+
parts: [{
|
|
838
|
+
type: "text",
|
|
839
|
+
text: handoff
|
|
840
|
+
}]
|
|
841
|
+
};
|
|
842
|
+
const release = _assertClassBrand(_ThinkHarness_brand, this, _markOwn).call(this, session, [note.id]);
|
|
843
|
+
try {
|
|
844
|
+
await handle.appendMessage(asSessionMessage(note));
|
|
845
|
+
} finally {
|
|
846
|
+
release();
|
|
847
|
+
}
|
|
848
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
849
|
+
type: "message",
|
|
850
|
+
message: note
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
/** @internal */
|
|
855
|
+
subscribe(session, listener) {
|
|
856
|
+
return _classPrivateFieldGet2(_events, this).subscribe(session, listener);
|
|
857
|
+
}
|
|
858
|
+
/** @internal The shared harness interface's watch. */
|
|
859
|
+
async watch(session) {
|
|
860
|
+
await this.lifecycle.ready();
|
|
861
|
+
let watcher;
|
|
862
|
+
const early = [];
|
|
863
|
+
const unsubscribe = _classPrivateFieldGet2(_events, this).subscribe(session, (event) => {
|
|
864
|
+
const events = toSessionEvents(event);
|
|
865
|
+
if (watcher) watcher.push(events);
|
|
866
|
+
else early.push(events);
|
|
867
|
+
});
|
|
868
|
+
const live = _classPrivateFieldGet2(_live, this).get(session);
|
|
869
|
+
const partial = live ? [...live.partial] : _assertClassBrand(_ThinkHarness_brand, this, _recoverableChunks).call(this, session);
|
|
870
|
+
const messages = await this.messages(session);
|
|
871
|
+
const open = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).open(session);
|
|
872
|
+
const running = open.find((op) => op.status === "running");
|
|
873
|
+
watcher = new Watcher({
|
|
874
|
+
messages: messages.map((message) => toTranscriptMessage(message)),
|
|
875
|
+
pending: open.map((op) => ({
|
|
876
|
+
operationId: op.operationId,
|
|
877
|
+
status: op.status === "queued" ? "queued" : "placed"
|
|
878
|
+
})),
|
|
879
|
+
...running && { run: {
|
|
880
|
+
operations: [running.operationId],
|
|
881
|
+
...partial.length > 0 && { partial: partial.flatMap((chunk) => {
|
|
882
|
+
const converted = toResponseChunk(chunk, {});
|
|
883
|
+
return converted ? [converted] : [];
|
|
884
|
+
}) }
|
|
885
|
+
} }
|
|
886
|
+
}, unsubscribe);
|
|
887
|
+
for (const events of early) watcher.push(events);
|
|
888
|
+
return watcher;
|
|
889
|
+
}
|
|
890
|
+
/** @internal The running operation and its model call's chunks so far. */
|
|
891
|
+
async inFlight(session) {
|
|
892
|
+
await this.lifecycle.ready();
|
|
893
|
+
const running = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).open(session).find((op) => op.status === "running");
|
|
894
|
+
if (!running) return void 0;
|
|
895
|
+
const live = _classPrivateFieldGet2(_live, this).get(session);
|
|
896
|
+
if (live?.operationId === running.operationId) return {
|
|
897
|
+
operationId: running.operationId,
|
|
898
|
+
continuation: live.continues,
|
|
899
|
+
chunks: [...live.partial]
|
|
900
|
+
};
|
|
901
|
+
return {
|
|
902
|
+
operationId: running.operationId,
|
|
903
|
+
continuation: await _assertClassBrand(_ThinkHarness_brand, this, _message).call(this, running) !== void 0,
|
|
904
|
+
chunks: running.streamId ? _assertClassBrand(_ThinkHarness_brand, this, _streamChunks).call(this, running.streamId) : []
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Listen to every session's events, for a host that serves several
|
|
909
|
+
* sessions over one set of connections. Returns the unsubscribe.
|
|
910
|
+
*/
|
|
911
|
+
observe(listener) {
|
|
912
|
+
return _classPrivateFieldGet2(_events, this).observe(listener);
|
|
913
|
+
}
|
|
914
|
+
/** @internal */
|
|
915
|
+
async createSession(parent) {
|
|
916
|
+
await this.lifecycle.ready();
|
|
917
|
+
const id = crypto.randomUUID();
|
|
918
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).ensureSession(id, parent);
|
|
919
|
+
return this.session(id);
|
|
920
|
+
}
|
|
921
|
+
/** @internal Copy a session's active path into a new session. */
|
|
922
|
+
async fork(from) {
|
|
923
|
+
const source = _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, from);
|
|
924
|
+
const rows = await source.getHistoryRowStats();
|
|
925
|
+
const fork = await this.createSession(from);
|
|
926
|
+
const target = _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, fork.id);
|
|
927
|
+
let parentId = null;
|
|
928
|
+
for (const row of rows) {
|
|
929
|
+
const message = await source.getMessage(row.id);
|
|
930
|
+
if (!message) continue;
|
|
931
|
+
await target.appendMessage(message, { parentId });
|
|
932
|
+
parentId = message.id;
|
|
933
|
+
}
|
|
934
|
+
return fork;
|
|
935
|
+
}
|
|
936
|
+
/** @internal */
|
|
937
|
+
async listSessions() {
|
|
938
|
+
await this.lifecycle.ready();
|
|
939
|
+
const store = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this);
|
|
940
|
+
const busy = new Set(store.open().map((op) => op.session));
|
|
941
|
+
return store.sessions().map((record) => ({
|
|
942
|
+
id: record.id,
|
|
943
|
+
...record.parent !== void 0 && { parent: record.parent },
|
|
944
|
+
busy: busy.has(record.id)
|
|
945
|
+
}));
|
|
946
|
+
}
|
|
947
|
+
/** @internal The answers to a message: its children in the tree. */
|
|
948
|
+
async branches(session, messageId) {
|
|
949
|
+
return asUIMessages(await _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session).getBranches(messageId));
|
|
950
|
+
}
|
|
951
|
+
/** @internal Full-text search over a session's messages. */
|
|
952
|
+
search(session, query, options) {
|
|
953
|
+
return _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session).search(query, options?.limit === void 0 ? void 0 : { limit: options.limit });
|
|
954
|
+
}
|
|
955
|
+
/** @internal Compact a session with its compaction function. */
|
|
956
|
+
async compact(session) {
|
|
957
|
+
return _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session).compact();
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
/** Schedule the session's wake job, now or at `time`. */
|
|
961
|
+
function _wake(session, time = Date.now()) {
|
|
962
|
+
return this.lifecycle.jobs.push({
|
|
963
|
+
id: wakeJobId(session),
|
|
964
|
+
fn: WAKE_FN,
|
|
965
|
+
time,
|
|
966
|
+
payload: { session },
|
|
967
|
+
singleflight: true,
|
|
968
|
+
recoveryLoop: true
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* One run of a session's wake job. With work open, it makes sure a drive
|
|
973
|
+
* is running and keeps a heartbeat; with none, it completes.
|
|
974
|
+
*/
|
|
975
|
+
function _wakeStep(session) {
|
|
976
|
+
const heartbeat = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_heartbeatMs, this) };
|
|
977
|
+
if (_classPrivateFieldGet2(_drives, this).has(session)) return heartbeat;
|
|
978
|
+
if (_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).open(session).length === 0) return void 0;
|
|
979
|
+
const drive = _assertClassBrand(_ThinkHarness_brand, this, _ensureDrive).call(this, session);
|
|
980
|
+
this.lifecycle.trackAlarmWork(drive);
|
|
981
|
+
return heartbeat;
|
|
982
|
+
}
|
|
983
|
+
/** Run the session's open operations in order, unless already running. */
|
|
984
|
+
function _ensureDrive(session) {
|
|
985
|
+
const running = _classPrivateFieldGet2(_drives, this).get(session);
|
|
986
|
+
if (running) return running;
|
|
987
|
+
let wakeAt;
|
|
988
|
+
const drive = (async () => {
|
|
989
|
+
try {
|
|
990
|
+
await this.lifecycle.runInHostContext(async () => {
|
|
991
|
+
for (;;) {
|
|
992
|
+
const op = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).open(session)[0];
|
|
993
|
+
if (!op) return;
|
|
994
|
+
const outcome = await _assertClassBrand(_ThinkHarness_brand, this, _run).call(this, op);
|
|
995
|
+
if (outcome.retryAt !== void 0) {
|
|
996
|
+
wakeAt = outcome.retryAt;
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
console.error("ThinkHarness drive failed", error);
|
|
1003
|
+
wakeAt = Date.now() + _classPrivateFieldGet2(_heartbeatMs, this);
|
|
1004
|
+
} finally {
|
|
1005
|
+
_classPrivateFieldGet2(_drives, this).delete(session);
|
|
1006
|
+
}
|
|
1007
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _wake).call(this, session, wakeAt);
|
|
1008
|
+
})();
|
|
1009
|
+
_classPrivateFieldGet2(_drives, this).set(session, drive);
|
|
1010
|
+
return drive;
|
|
1011
|
+
}
|
|
1012
|
+
async function _run(first) {
|
|
1013
|
+
const { session, operationId } = first;
|
|
1014
|
+
const live = {
|
|
1015
|
+
operationId,
|
|
1016
|
+
abort: new AbortController(),
|
|
1017
|
+
partial: [],
|
|
1018
|
+
continues: false
|
|
1019
|
+
};
|
|
1020
|
+
_classPrivateFieldGet2(_live, this).set(session, live);
|
|
1021
|
+
try {
|
|
1022
|
+
let op = first;
|
|
1023
|
+
if (op.status === "queued") {
|
|
1024
|
+
op = await _assertClassBrand(_ThinkHarness_brand, this, _place).call(this, op);
|
|
1025
|
+
if (!op) return {};
|
|
1026
|
+
}
|
|
1027
|
+
const continuation = await _assertClassBrand(_ThinkHarness_brand, this, _message).call(this, op) !== void 0;
|
|
1028
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1029
|
+
type: "run-start",
|
|
1030
|
+
operationId,
|
|
1031
|
+
continuation
|
|
1032
|
+
});
|
|
1033
|
+
try {
|
|
1034
|
+
return await _assertClassBrand(_ThinkHarness_brand, this, _steps).call(this, session, operationId, live);
|
|
1035
|
+
} finally {
|
|
1036
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1037
|
+
type: "run-end",
|
|
1038
|
+
operationId
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
console.error(`ThinkHarness operation ${operationId} failed`, error);
|
|
1043
|
+
const op = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).get(session, operationId);
|
|
1044
|
+
if (op && (op.status === "running" || op.status === "queued")) await _assertClassBrand(_ThinkHarness_brand, this, _abandon).call(this, op, errorText(error));
|
|
1045
|
+
return {};
|
|
1046
|
+
} finally {
|
|
1047
|
+
_classPrivateFieldGet2(_live, this).delete(session);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
async function _steps(session, operationId, live) {
|
|
1051
|
+
const maxSteps = _classPrivateFieldGet2(_options, this).maxSteps ?? DEFAULT_MAX_STEPS;
|
|
1052
|
+
for (;;) {
|
|
1053
|
+
const op = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).get(session, operationId);
|
|
1054
|
+
if (!op || op.status !== "running") return {};
|
|
1055
|
+
if (live.abort.signal.aborted) {
|
|
1056
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _abandon).call(this, op, "aborted");
|
|
1057
|
+
return {};
|
|
1058
|
+
}
|
|
1059
|
+
if (op.abandonReason !== void 0) {
|
|
1060
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _abandon).call(this, op, op.abandonReason);
|
|
1061
|
+
return {};
|
|
1062
|
+
}
|
|
1063
|
+
const message = await _assertClassBrand(_ThinkHarness_brand, this, _message).call(this, op);
|
|
1064
|
+
const tools = await _assertClassBrand(_ThinkHarness_brand, this, _tools).call(this, session);
|
|
1065
|
+
const action = nextAction(op, message, (name) => isServerTool(tools, name), maxSteps);
|
|
1066
|
+
switch (action._tag) {
|
|
1067
|
+
case "recover-model": {
|
|
1068
|
+
const outcome = await _assertClassBrand(_ThinkHarness_brand, this, _recoverModel).call(this, op, message);
|
|
1069
|
+
if (outcome) return outcome;
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
case "call-model": {
|
|
1073
|
+
const outcome = await _assertClassBrand(_ThinkHarness_brand, this, _callModel).call(this, op, message, tools, live);
|
|
1074
|
+
if (outcome) return outcome;
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
case "run-tools":
|
|
1078
|
+
if (!message) return {};
|
|
1079
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _runTools).call(this, op, message, action.calls, tools, live);
|
|
1080
|
+
break;
|
|
1081
|
+
case "await-input":
|
|
1082
|
+
case "end":
|
|
1083
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, void 0, () => {
|
|
1084
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1085
|
+
status: "done",
|
|
1086
|
+
text: textOf(message)
|
|
1087
|
+
});
|
|
1088
|
+
});
|
|
1089
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, operationId, message);
|
|
1090
|
+
return {};
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Place a queued operation: write what it brings to the transcript and
|
|
1096
|
+
* mark it running, in one transaction. Returns the running operation, or
|
|
1097
|
+
* undefined when placing settled it.
|
|
1098
|
+
*/
|
|
1099
|
+
async function _place(op) {
|
|
1100
|
+
const { session, operationId } = op;
|
|
1101
|
+
const store = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this);
|
|
1102
|
+
const handle = _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session);
|
|
1103
|
+
const input = op.input;
|
|
1104
|
+
const placed = [];
|
|
1105
|
+
let settledText;
|
|
1106
|
+
let unanswered;
|
|
1107
|
+
const running = (change) => store.update(session, operationId, {
|
|
1108
|
+
status: "running",
|
|
1109
|
+
parentId: change.parentId ?? null,
|
|
1110
|
+
messageId: change.messageId,
|
|
1111
|
+
pendingModel: true
|
|
1112
|
+
});
|
|
1113
|
+
switch (input.kind) {
|
|
1114
|
+
case "messages": {
|
|
1115
|
+
let messages = input.messages;
|
|
1116
|
+
if (op.source === "client") {
|
|
1117
|
+
if (messages.some((message) => message.role !== "user")) {
|
|
1118
|
+
unanswered = "client_role";
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1121
|
+
const fresh = [];
|
|
1122
|
+
for (const message of messages) if (!await handle.getMessage(message.id)) fresh.push(message);
|
|
1123
|
+
messages = fresh;
|
|
1124
|
+
}
|
|
1125
|
+
const last = messages.at(-1);
|
|
1126
|
+
if (!last) {
|
|
1127
|
+
unanswered = "empty";
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, void 0, (put) => {
|
|
1131
|
+
let parentId;
|
|
1132
|
+
for (const message of messages) {
|
|
1133
|
+
put(message, {
|
|
1134
|
+
parentId,
|
|
1135
|
+
source: op.source
|
|
1136
|
+
});
|
|
1137
|
+
parentId = message.id;
|
|
1138
|
+
placed.push(message);
|
|
1139
|
+
}
|
|
1140
|
+
running({
|
|
1141
|
+
parentId: last.id,
|
|
1142
|
+
messageId: crypto.randomUUID()
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
case "answer": {
|
|
1148
|
+
const history = asUIMessages(await handle.getHistory());
|
|
1149
|
+
const tools = await _assertClassBrand(_ThinkHarness_brand, this, _tools).call(this, session);
|
|
1150
|
+
const answer = input.answer;
|
|
1151
|
+
if (answer.type === "tool-result" && op.source === "client") {
|
|
1152
|
+
const call = history.flatMap((message) => message.parts).find((part) => isToolCallPart(part) && part.toolCallId === answer.toolCallId);
|
|
1153
|
+
if (call && isServerTool(tools, toolNameOf(call))) {
|
|
1154
|
+
unanswered = "not_client_tool";
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
const answered = answerToolCall(history, toolCallAnswer(answer));
|
|
1159
|
+
if (!answered) {
|
|
1160
|
+
unanswered = "not_waiting";
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
const latest = history.at(-1);
|
|
1164
|
+
const continues = input.autoContinue && latest?.id === answered.id && nextAction({
|
|
1165
|
+
streamId: void 0,
|
|
1166
|
+
pendingModel: true,
|
|
1167
|
+
steps: 0
|
|
1168
|
+
}, answered, (name) => isServerTool(tools, name), Number.POSITIVE_INFINITY)._tag !== "await-input";
|
|
1169
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, void 0, (put) => {
|
|
1170
|
+
put(answered);
|
|
1171
|
+
placed.push(answered);
|
|
1172
|
+
if (continues) running({ messageId: answered.id });
|
|
1173
|
+
else store.update(session, operationId, {
|
|
1174
|
+
status: "done",
|
|
1175
|
+
text: textOf(answered)
|
|
1176
|
+
});
|
|
1177
|
+
});
|
|
1178
|
+
if (!continues) settledText = textOf(answered);
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
case "regenerate": {
|
|
1182
|
+
const history = asUIMessages(await handle.getHistory());
|
|
1183
|
+
const target = input.messageId ?? [...history].reverse().find((message) => message.role === "user")?.id;
|
|
1184
|
+
if (!target || !await handle.getMessage(target)) {
|
|
1185
|
+
unanswered = "not_found";
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, void 0, () => running({
|
|
1189
|
+
parentId: target,
|
|
1190
|
+
messageId: crypto.randomUUID()
|
|
1191
|
+
}));
|
|
1192
|
+
break;
|
|
1193
|
+
}
|
|
1194
|
+
case "continue": {
|
|
1195
|
+
const latest = await handle.getLatestLeaf();
|
|
1196
|
+
if (!latest) {
|
|
1197
|
+
unanswered = "not_found";
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, void 0, () => latest.role === "assistant" ? running({ messageId: latest.id }) : running({
|
|
1201
|
+
parentId: latest.id,
|
|
1202
|
+
messageId: crypto.randomUUID()
|
|
1203
|
+
}));
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
for (const message of placed) _classPrivateFieldGet2(_events, this).emit(session, {
|
|
1208
|
+
type: "message",
|
|
1209
|
+
message,
|
|
1210
|
+
operationId
|
|
1211
|
+
});
|
|
1212
|
+
if (unanswered !== void 0) {
|
|
1213
|
+
store.update(session, operationId, {
|
|
1214
|
+
status: "unanswered",
|
|
1215
|
+
reason: unanswered
|
|
1216
|
+
});
|
|
1217
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, operationId);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
if (settledText !== void 0) {
|
|
1221
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, operationId);
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1225
|
+
type: "operation",
|
|
1226
|
+
status: {
|
|
1227
|
+
operationId,
|
|
1228
|
+
status: "placed"
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
return store.get(session, operationId);
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* One model call. Its chunks go to a stream as they arrive; when it ends,
|
|
1235
|
+
* the message is persisted and the stream discarded in one transaction.
|
|
1236
|
+
* Returns an outcome only when the operation must wait.
|
|
1237
|
+
*/
|
|
1238
|
+
async function _callModel(op, existing, tools, live) {
|
|
1239
|
+
const { session, operationId } = op;
|
|
1240
|
+
const messageId = op.messageId ?? crypto.randomUUID();
|
|
1241
|
+
const hooks = _classPrivateFieldGet2(_options, this).hooks;
|
|
1242
|
+
const handle = _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session);
|
|
1243
|
+
const history = asUIMessages(await handle.getHistory({ leafId: existing ? existing.id : op.parentId }));
|
|
1244
|
+
const system = await resolve(_classPrivateFieldGet2(_options, this).system, session);
|
|
1245
|
+
const config = await hooks?.beforeTurn?.({
|
|
1246
|
+
session,
|
|
1247
|
+
operationId,
|
|
1248
|
+
step: op.steps,
|
|
1249
|
+
continuation: existing !== void 0,
|
|
1250
|
+
messages: history,
|
|
1251
|
+
system,
|
|
1252
|
+
tools: Object.keys(tools)
|
|
1253
|
+
}) ?? {};
|
|
1254
|
+
const model = config.model ?? await resolve(_classPrivateFieldGet2(_options, this).model, session);
|
|
1255
|
+
if (model === void 0) throw new Error("ThinkHarness has no model");
|
|
1256
|
+
const offered = modelTools(tools, config.activeTools);
|
|
1257
|
+
const effectiveSystem = config.system ?? system;
|
|
1258
|
+
const messages = config.messages ?? await convertToModelMessages(history, {
|
|
1259
|
+
tools,
|
|
1260
|
+
ignoreIncompleteToolCalls: true
|
|
1261
|
+
});
|
|
1262
|
+
const streamId = `think:${crypto.randomUUID()}`;
|
|
1263
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1264
|
+
streamId,
|
|
1265
|
+
messageId
|
|
1266
|
+
});
|
|
1267
|
+
const writer = await _classPrivateFieldGet2(_streams, this).open(streamId, {
|
|
1268
|
+
tag: streamTag(session, operationId),
|
|
1269
|
+
metadata: {
|
|
1270
|
+
session,
|
|
1271
|
+
operationId,
|
|
1272
|
+
messageId
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
const stall = new AbortController();
|
|
1276
|
+
const signal = AbortSignal.any([live.abort.signal, stall.signal]);
|
|
1277
|
+
let modelError;
|
|
1278
|
+
let final;
|
|
1279
|
+
let finishReason;
|
|
1280
|
+
let caught;
|
|
1281
|
+
live.partial = [];
|
|
1282
|
+
live.continues = existing !== void 0;
|
|
1283
|
+
let segment = [];
|
|
1284
|
+
let segmentBytes = 0;
|
|
1285
|
+
let flushTimer;
|
|
1286
|
+
const flush = () => {
|
|
1287
|
+
if (flushTimer !== void 0) clearTimeout(flushTimer);
|
|
1288
|
+
flushTimer = void 0;
|
|
1289
|
+
if (segment.length === 0) return;
|
|
1290
|
+
writer.append(segment);
|
|
1291
|
+
segment = [];
|
|
1292
|
+
segmentBytes = 0;
|
|
1293
|
+
};
|
|
1294
|
+
try {
|
|
1295
|
+
const stream = toUIMessageStream({
|
|
1296
|
+
stream: streamText({
|
|
1297
|
+
model,
|
|
1298
|
+
...effectiveSystem !== void 0 && { system: effectiveSystem },
|
|
1299
|
+
messages,
|
|
1300
|
+
tools: offered,
|
|
1301
|
+
...config.toolChoice !== void 0 && { toolChoice: config.toolChoice },
|
|
1302
|
+
...config.providerOptions !== void 0 && { providerOptions: config.providerOptions },
|
|
1303
|
+
...config.maxOutputTokens !== void 0 && { maxOutputTokens: config.maxOutputTokens },
|
|
1304
|
+
...config.temperature !== void 0 && { temperature: config.temperature },
|
|
1305
|
+
stopWhen: stepCountIs(1),
|
|
1306
|
+
abortSignal: signal,
|
|
1307
|
+
onError: ({ error }) => {
|
|
1308
|
+
modelError ??= error;
|
|
1309
|
+
}
|
|
1310
|
+
}).stream,
|
|
1311
|
+
tools: offered,
|
|
1312
|
+
originalMessages: existing ? history : [],
|
|
1313
|
+
generateMessageId: () => messageId,
|
|
1314
|
+
onEnd: ({ responseMessage }) => {
|
|
1315
|
+
final = responseMessage;
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
const stallTimeoutMs = _classPrivateFieldGet2(_options, this).recovery?.stallTimeoutMs ?? DEFAULT_STALL_TIMEOUT_MS;
|
|
1319
|
+
for await (const chunk of untilAborted(iterateWithStallWatchdog(stream, stallTimeoutMs, () => stall.abort(new ChatStreamStalledError("Model stream stalled"))), live.abort.signal)) {
|
|
1320
|
+
if (chunk.type === "finish") finishReason = chunk.finishReason;
|
|
1321
|
+
live.partial.push(chunk);
|
|
1322
|
+
segment.push(chunk);
|
|
1323
|
+
segmentBytes += JSON.stringify(chunk).length;
|
|
1324
|
+
if (segment.length >= SEGMENT_CHUNKS || segmentBytes >= SEGMENT_MAX_BYTES) flush();
|
|
1325
|
+
else flushTimer ??= setTimeout(flush, FLUSH_AFTER_MS);
|
|
1326
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1327
|
+
type: "chunk",
|
|
1328
|
+
operationId,
|
|
1329
|
+
chunk
|
|
1330
|
+
});
|
|
1331
|
+
if (hooks?.onChunk) try {
|
|
1332
|
+
hooks.onChunk({
|
|
1333
|
+
session,
|
|
1334
|
+
operationId,
|
|
1335
|
+
chunk
|
|
1336
|
+
});
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
console.error("ThinkHarness onChunk hook failed", error);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
} catch (error) {
|
|
1342
|
+
caught = error;
|
|
1343
|
+
}
|
|
1344
|
+
flush();
|
|
1345
|
+
const message = final ?? accumulate(messageId, existing, live.partial);
|
|
1346
|
+
const partial = message ? settledPartial(message) : void 0;
|
|
1347
|
+
const keep = partial && hasContent(partial) ? partial : void 0;
|
|
1348
|
+
const stalled = caught instanceof ChatStreamStalledError || stall.signal.aborted;
|
|
1349
|
+
const failure = stalled ? void 0 : modelError ?? caught;
|
|
1350
|
+
if (live.abort.signal.aborted) {
|
|
1351
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, streamId, (put) => {
|
|
1352
|
+
if (keep) put(keep, { parentId: op.parentId });
|
|
1353
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1354
|
+
status: "unanswered",
|
|
1355
|
+
reason: "aborted",
|
|
1356
|
+
streamId: null
|
|
1357
|
+
});
|
|
1358
|
+
});
|
|
1359
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, operationId, keep);
|
|
1360
|
+
return {};
|
|
1361
|
+
}
|
|
1362
|
+
if (stalled || failure !== void 0) {
|
|
1363
|
+
if (failure !== void 0) try {
|
|
1364
|
+
await hooks?.onError?.(failure, {
|
|
1365
|
+
session,
|
|
1366
|
+
operationId
|
|
1367
|
+
});
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
console.error("ThinkHarness onError hook failed", error);
|
|
1370
|
+
}
|
|
1371
|
+
const kind = stalled ? "retry" : hooks?.classifyError?.(failure) ?? "fail";
|
|
1372
|
+
if (kind === "context-overflow" && op.overflowRetries < MAX_OVERFLOW_RETRIES) {
|
|
1373
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, streamId, (put) => {
|
|
1374
|
+
if (keep) put(keep, { parentId: op.parentId });
|
|
1375
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1376
|
+
streamId: null,
|
|
1377
|
+
overflowRetries: op.overflowRetries + 1
|
|
1378
|
+
});
|
|
1379
|
+
});
|
|
1380
|
+
try {
|
|
1381
|
+
await handle.compact();
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
console.warn("ThinkHarness could not compact the session", error);
|
|
1384
|
+
}
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (kind === "retry") return _assertClassBrand(_ThinkHarness_brand, this, _interrupted).call(this, op, streamId, keep);
|
|
1388
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, streamId, (put) => {
|
|
1389
|
+
if (keep) put(keep, { parentId: op.parentId });
|
|
1390
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1391
|
+
status: "unanswered",
|
|
1392
|
+
reason: errorText(failure),
|
|
1393
|
+
streamId: null
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, operationId, keep);
|
|
1397
|
+
return {};
|
|
1398
|
+
}
|
|
1399
|
+
if (!message) throw new Error("The model call produced no message");
|
|
1400
|
+
const toolCalls = lastStep(message).some((part) => isToolCallPart(part) && !part.providerExecuted);
|
|
1401
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, streamId, (put) => {
|
|
1402
|
+
put(message, { parentId: op.parentId });
|
|
1403
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1404
|
+
streamId: null,
|
|
1405
|
+
steps: op.steps + 1,
|
|
1406
|
+
interruptions: 0,
|
|
1407
|
+
pendingModel: toolCalls
|
|
1408
|
+
});
|
|
1409
|
+
});
|
|
1410
|
+
live.partial = [];
|
|
1411
|
+
live.continues = true;
|
|
1412
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1413
|
+
type: "message",
|
|
1414
|
+
message,
|
|
1415
|
+
operationId
|
|
1416
|
+
});
|
|
1417
|
+
await hooks?.onStepFinish?.({
|
|
1418
|
+
session,
|
|
1419
|
+
operationId,
|
|
1420
|
+
step: op.steps,
|
|
1421
|
+
message,
|
|
1422
|
+
finishReason
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* A model call an eviction cut short: keep what it produced, then call
|
|
1427
|
+
* the model again to continue the same message, within the budget.
|
|
1428
|
+
*/
|
|
1429
|
+
async function _recoverModel(op, existing) {
|
|
1430
|
+
const streamId = op.streamId;
|
|
1431
|
+
if (streamId === void 0) return void 0;
|
|
1432
|
+
const chunks = _assertClassBrand(_ThinkHarness_brand, this, _streamChunks).call(this, streamId);
|
|
1433
|
+
const rebuilt = chunks.length > 0 ? accumulate(op.messageId ?? crypto.randomUUID(), existing, chunks) : void 0;
|
|
1434
|
+
const partial = rebuilt ? settledPartial(rebuilt) : void 0;
|
|
1435
|
+
const keep = partial && hasContent(partial) ? partial : void 0;
|
|
1436
|
+
return _assertClassBrand(_ThinkHarness_brand, this, _interrupted).call(this, op, streamId, keep);
|
|
1437
|
+
}
|
|
1438
|
+
/** Count an interruption, keep the partial, and decide whether to go on. */
|
|
1439
|
+
async function _interrupted(op, streamId, partial) {
|
|
1440
|
+
const { session, operationId } = op;
|
|
1441
|
+
const recovery = _classPrivateFieldGet2(_options, this).recovery;
|
|
1442
|
+
const attempt = op.interruptions + 1;
|
|
1443
|
+
let decision = attempt > (recovery?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS) ? "abandon" : "continue";
|
|
1444
|
+
if (decision === "continue" && _classPrivateFieldGet2(_options, this).hooks?.onRecovery) decision = await _classPrivateFieldGet2(_options, this).hooks.onRecovery({
|
|
1445
|
+
session,
|
|
1446
|
+
operationId,
|
|
1447
|
+
attempt,
|
|
1448
|
+
partial
|
|
1449
|
+
});
|
|
1450
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, streamId, (put) => {
|
|
1451
|
+
if (partial) put(partial, { parentId: op.parentId });
|
|
1452
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1453
|
+
streamId: null,
|
|
1454
|
+
interruptions: attempt,
|
|
1455
|
+
...decision === "abandon" && {
|
|
1456
|
+
status: "unanswered",
|
|
1457
|
+
reason: "interrupted"
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
});
|
|
1461
|
+
if (partial) _classPrivateFieldGet2(_events, this).emit(session, {
|
|
1462
|
+
type: "message",
|
|
1463
|
+
message: partial,
|
|
1464
|
+
operationId
|
|
1465
|
+
});
|
|
1466
|
+
if (decision === "abandon") {
|
|
1467
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, operationId, partial);
|
|
1468
|
+
return {};
|
|
1469
|
+
}
|
|
1470
|
+
const delay = backoff(attempt, recovery?.backoffMs ?? DEFAULT_BACKOFF_MS);
|
|
1471
|
+
return delay > 0 ? { retryAt: Date.now() + delay } : void 0;
|
|
1472
|
+
}
|
|
1473
|
+
async function _runTools(op, message, calls, tools, live) {
|
|
1474
|
+
const { session, operationId } = op;
|
|
1475
|
+
const store = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this);
|
|
1476
|
+
const hooks = _classPrivateFieldGet2(_options, this).hooks;
|
|
1477
|
+
const modelMessages = await convertToModelMessages(asUIMessages(await _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session).getHistory({ leafId: message.id })).slice(0, -1), {
|
|
1478
|
+
tools,
|
|
1479
|
+
ignoreIncompleteToolCalls: true
|
|
1480
|
+
});
|
|
1481
|
+
let current = message;
|
|
1482
|
+
const toRun = [];
|
|
1483
|
+
const approvals = [];
|
|
1484
|
+
for (const call of calls) {
|
|
1485
|
+
const toolName = toolNameOf(call);
|
|
1486
|
+
const tool = tools[toolName];
|
|
1487
|
+
if (!tool) continue;
|
|
1488
|
+
if (call.state === "input-available") {
|
|
1489
|
+
if (await _classPrivateFieldGet2(_options, this).toolApproval?.({
|
|
1490
|
+
session,
|
|
1491
|
+
toolName,
|
|
1492
|
+
toolCallId: call.toolCallId,
|
|
1493
|
+
input: call.input
|
|
1494
|
+
}) || await toolNeedsApproval(tool, call.input, {
|
|
1495
|
+
toolCallId: call.toolCallId,
|
|
1496
|
+
messages: modelMessages
|
|
1497
|
+
})) {
|
|
1498
|
+
approvals.push(call.toolCallId);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
toRun.push(call);
|
|
1503
|
+
}
|
|
1504
|
+
if (approvals.length > 0) {
|
|
1505
|
+
for (const toolCallId of approvals) current = withToolPart(current, toolCallId, (part) => ({
|
|
1506
|
+
...part,
|
|
1507
|
+
state: "approval-requested",
|
|
1508
|
+
approval: { id: `approval:${toolCallId}` }
|
|
1509
|
+
}));
|
|
1510
|
+
const snapshot = current;
|
|
1511
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, void 0, (put) => put(snapshot));
|
|
1512
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1513
|
+
type: "message",
|
|
1514
|
+
message: snapshot,
|
|
1515
|
+
operationId
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
await Promise.all(toRun.map(async (call) => {
|
|
1519
|
+
const toolName = toolNameOf(call);
|
|
1520
|
+
const tool = tools[toolName];
|
|
1521
|
+
if (!tool) return;
|
|
1522
|
+
const { toolCallId } = call;
|
|
1523
|
+
const started = store.toolCall(session, toolCallId);
|
|
1524
|
+
let outcome;
|
|
1525
|
+
if (started) {
|
|
1526
|
+
const policy = toolRecovery(tool);
|
|
1527
|
+
const maxAttempts = _classPrivateFieldGet2(_options, this).recovery?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
1528
|
+
if (policy === "report" || started.attempts >= maxAttempts) outcome = {
|
|
1529
|
+
ok: false,
|
|
1530
|
+
errorText: INTERRUPTED_TOOL_ERROR
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
let attempt = started?.attempts ?? 0;
|
|
1534
|
+
let input = call.input;
|
|
1535
|
+
if (!outcome) {
|
|
1536
|
+
attempt = store.startToolCall(session, toolCallId, operationId);
|
|
1537
|
+
const decision = await hooks?.beforeToolCall?.({
|
|
1538
|
+
session,
|
|
1539
|
+
operationId,
|
|
1540
|
+
toolCallId,
|
|
1541
|
+
toolName,
|
|
1542
|
+
input,
|
|
1543
|
+
attempt
|
|
1544
|
+
});
|
|
1545
|
+
if (decision?.action === "block") outcome = {
|
|
1546
|
+
ok: false,
|
|
1547
|
+
errorText: decision.reason
|
|
1548
|
+
};
|
|
1549
|
+
else if (decision?.action === "substitute") outcome = {
|
|
1550
|
+
ok: true,
|
|
1551
|
+
output: decision.output
|
|
1552
|
+
};
|
|
1553
|
+
else {
|
|
1554
|
+
if (decision?.action === "run" && decision.input !== void 0) input = decision.input;
|
|
1555
|
+
outcome = await executeTool(tool, input, {
|
|
1556
|
+
toolCallId,
|
|
1557
|
+
messages: modelMessages,
|
|
1558
|
+
abortSignal: live.abort.signal,
|
|
1559
|
+
onPreliminary: (output) => _classPrivateFieldGet2(_events, this).emit(session, {
|
|
1560
|
+
type: "chunk",
|
|
1561
|
+
operationId,
|
|
1562
|
+
chunk: {
|
|
1563
|
+
type: "tool-output-available",
|
|
1564
|
+
toolCallId,
|
|
1565
|
+
output,
|
|
1566
|
+
preliminary: true
|
|
1567
|
+
}
|
|
1568
|
+
})
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
const settled = outcome;
|
|
1573
|
+
await hooks?.afterToolCall?.({
|
|
1574
|
+
session,
|
|
1575
|
+
operationId,
|
|
1576
|
+
toolCallId,
|
|
1577
|
+
toolName,
|
|
1578
|
+
input,
|
|
1579
|
+
attempt,
|
|
1580
|
+
...settled
|
|
1581
|
+
});
|
|
1582
|
+
current = withToolPart(current, toolCallId, (part) => withOutcome(part, settled));
|
|
1583
|
+
const snapshot = current;
|
|
1584
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, void 0, (put) => {
|
|
1585
|
+
put(snapshot);
|
|
1586
|
+
store.finishToolCall(session, toolCallId);
|
|
1587
|
+
store.update(session, operationId, { interruptions: 0 });
|
|
1588
|
+
});
|
|
1589
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1590
|
+
type: "chunk",
|
|
1591
|
+
operationId,
|
|
1592
|
+
chunk: settled.ok ? {
|
|
1593
|
+
type: "tool-output-available",
|
|
1594
|
+
toolCallId,
|
|
1595
|
+
output: settled.output
|
|
1596
|
+
} : {
|
|
1597
|
+
type: "tool-output-error",
|
|
1598
|
+
toolCallId,
|
|
1599
|
+
errorText: settled.errorText
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1603
|
+
type: "message",
|
|
1604
|
+
message: snapshot,
|
|
1605
|
+
operationId
|
|
1606
|
+
});
|
|
1607
|
+
}));
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* End an operation that cannot run on: keep what its interrupted model
|
|
1611
|
+
* call produced, mark any tool call it left open as failed, and settle
|
|
1612
|
+
* it `unanswered`.
|
|
1613
|
+
*/
|
|
1614
|
+
async function _abandon(op, reason) {
|
|
1615
|
+
const { session, operationId } = op;
|
|
1616
|
+
const existing = await _assertClassBrand(_ThinkHarness_brand, this, _message).call(this, op);
|
|
1617
|
+
let message = existing;
|
|
1618
|
+
const chunks = op.streamId === void 0 ? [] : _assertClassBrand(_ThinkHarness_brand, this, _streamChunks).call(this, op.streamId);
|
|
1619
|
+
if (chunks.length > 0) {
|
|
1620
|
+
const rebuilt = accumulate(op.messageId ?? crypto.randomUUID(), existing, chunks);
|
|
1621
|
+
if (rebuilt) message = settledPartial(rebuilt);
|
|
1622
|
+
}
|
|
1623
|
+
if (message) {
|
|
1624
|
+
for (const part of lastStep(message)) if (isToolCallPart(part) && !part.providerExecuted && (part.state === "input-available" || part.state === "approval-responded")) message = withToolPart(message, part.toolCallId, (call) => withOutcome(call, {
|
|
1625
|
+
ok: false,
|
|
1626
|
+
errorText: reason
|
|
1627
|
+
}));
|
|
1628
|
+
}
|
|
1629
|
+
const keep = message && hasContent(message) && message !== existing ? message : void 0;
|
|
1630
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _commit).call(this, session, op.streamId, (put) => {
|
|
1631
|
+
if (keep) put(keep, { parentId: op.parentId });
|
|
1632
|
+
_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).update(session, operationId, {
|
|
1633
|
+
status: "unanswered",
|
|
1634
|
+
reason,
|
|
1635
|
+
streamId: null
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
if (keep) _classPrivateFieldGet2(_events, this).emit(session, {
|
|
1639
|
+
type: "message",
|
|
1640
|
+
message: keep,
|
|
1641
|
+
operationId
|
|
1642
|
+
});
|
|
1643
|
+
await _assertClassBrand(_ThinkHarness_brand, this, _settled).call(this, session, operationId, keep ?? existing);
|
|
1644
|
+
}
|
|
1645
|
+
/** Tell everyone an operation settled. Its row is already written. */
|
|
1646
|
+
async function _settled(session, operationId, message) {
|
|
1647
|
+
const op = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).get(session, operationId);
|
|
1648
|
+
if (!op) return;
|
|
1649
|
+
const result = _assertClassBrand(_ThinkHarness_brand, this, _result).call(this, op);
|
|
1650
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1651
|
+
type: "operation",
|
|
1652
|
+
status: result.status === "done" ? {
|
|
1653
|
+
operationId,
|
|
1654
|
+
status: "done",
|
|
1655
|
+
...result.text !== void 0 && { text: result.text }
|
|
1656
|
+
} : {
|
|
1657
|
+
operationId,
|
|
1658
|
+
status: "unanswered",
|
|
1659
|
+
...result.reason !== void 0 && { reason: result.reason }
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
const key = waiterKey(session, operationId);
|
|
1663
|
+
for (const waiter of _classPrivateFieldGet2(_waiters, this).get(key) ?? []) waiter();
|
|
1664
|
+
_classPrivateFieldGet2(_waiters, this).delete(key);
|
|
1665
|
+
try {
|
|
1666
|
+
await _classPrivateFieldGet2(_options, this).hooks?.onTurnEnd?.({
|
|
1667
|
+
...result,
|
|
1668
|
+
...message && { message }
|
|
1669
|
+
});
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
console.error("ThinkHarness onTurnEnd hook failed", error);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function _result(op) {
|
|
1675
|
+
const { operationId, session } = op;
|
|
1676
|
+
return op.status === "done" ? {
|
|
1677
|
+
operationId,
|
|
1678
|
+
session,
|
|
1679
|
+
status: "done",
|
|
1680
|
+
text: op.text ?? "",
|
|
1681
|
+
...op.messageId !== void 0 && { messageId: op.messageId }
|
|
1682
|
+
} : {
|
|
1683
|
+
operationId,
|
|
1684
|
+
session,
|
|
1685
|
+
status: "unanswered",
|
|
1686
|
+
...op.reason !== void 0 && { reason: op.reason }
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
function _tables() {
|
|
1690
|
+
_classPrivateFieldGet2(_records, this) ?? _classPrivateFieldSet2(_records, this, new OperationRecords(this.lifecycle.storage.sql));
|
|
1691
|
+
_classPrivateFieldGet2(_records, this).ensureTables();
|
|
1692
|
+
return _classPrivateFieldGet2(_records, this);
|
|
1693
|
+
}
|
|
1694
|
+
/** The Sessions handle for a session, configured on first use. */
|
|
1695
|
+
/**
|
|
1696
|
+
* Give the Sessions and Streams the harness owns this capability's
|
|
1697
|
+
* Lifecycle services. Done on first use, not only in `onStart`, because an
|
|
1698
|
+
* RPC can read a transcript before startup (the read itself then waits
|
|
1699
|
+
* for startup).
|
|
1700
|
+
*/
|
|
1701
|
+
function _bindOwned() {
|
|
1702
|
+
if (_classPrivateFieldGet2(_bound, this)) return;
|
|
1703
|
+
bindLifecycleCapability(_classPrivateFieldGet2(_sessions, this), this.lifecycle);
|
|
1704
|
+
bindLifecycleCapability(_classPrivateFieldGet2(_streams, this), this.lifecycle);
|
|
1705
|
+
_classPrivateFieldSet2(_bound, this, true);
|
|
1706
|
+
_classPrivateFieldGet2(_sessions, this).subscribe((change) => {
|
|
1707
|
+
const session = change.sessionId;
|
|
1708
|
+
switch (change.type) {
|
|
1709
|
+
case "append":
|
|
1710
|
+
case "update":
|
|
1711
|
+
case "import":
|
|
1712
|
+
if (_assertClassBrand(_ThinkHarness_brand, this, _takeOwn).call(this, session, change.message.id)) return;
|
|
1713
|
+
_classPrivateFieldGet2(_events, this).emit(session, {
|
|
1714
|
+
type: "message",
|
|
1715
|
+
message: asUIMessage(change.message)
|
|
1716
|
+
});
|
|
1717
|
+
return;
|
|
1718
|
+
case "clear":
|
|
1719
|
+
if ((_classPrivateFieldGet2(_ownClears, this).get(session) ?? 0) > 0) return;
|
|
1720
|
+
_classPrivateFieldGet2(_events, this).emit(session, { type: "reset" });
|
|
1721
|
+
return;
|
|
1722
|
+
case "delete":
|
|
1723
|
+
case "compact":
|
|
1724
|
+
case "compaction":
|
|
1725
|
+
_classPrivateFieldGet2(_events, this).emit(session, { type: "transcript" });
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
/** Note messages the harness is about to write; returns the release. */
|
|
1731
|
+
function _markOwn(session, ids) {
|
|
1732
|
+
let own = _classPrivateFieldGet2(_ownMessages, this).get(session);
|
|
1733
|
+
if (!own) {
|
|
1734
|
+
own = /* @__PURE__ */ new Map();
|
|
1735
|
+
_classPrivateFieldGet2(_ownMessages, this).set(session, own);
|
|
1736
|
+
}
|
|
1737
|
+
for (const id of ids) own.set(id, (own.get(id) ?? 0) + 1);
|
|
1738
|
+
return () => {
|
|
1739
|
+
const current = _classPrivateFieldGet2(_ownMessages, this).get(session);
|
|
1740
|
+
if (!current) return;
|
|
1741
|
+
for (const id of ids) {
|
|
1742
|
+
const left = (current.get(id) ?? 1) - 1;
|
|
1743
|
+
if (left <= 0) current.delete(id);
|
|
1744
|
+
else current.set(id, left);
|
|
1745
|
+
}
|
|
1746
|
+
if (current.size === 0) _classPrivateFieldGet2(_ownMessages, this).delete(session);
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
/** Whether a change is one of the harness's own writes; consumes the mark. */
|
|
1750
|
+
function _takeOwn(session, id) {
|
|
1751
|
+
const own = _classPrivateFieldGet2(_ownMessages, this).get(session);
|
|
1752
|
+
const count = own?.get(id);
|
|
1753
|
+
if (!own || count === void 0) return false;
|
|
1754
|
+
if (count <= 1) own.delete(id);
|
|
1755
|
+
else own.set(id, count - 1);
|
|
1756
|
+
if (own.size === 0) _classPrivateFieldGet2(_ownMessages, this).delete(session);
|
|
1757
|
+
return true;
|
|
1758
|
+
}
|
|
1759
|
+
function _handle$1(session) {
|
|
1760
|
+
_assertClassBrand(_ThinkHarness_brand, this, _bindOwned).call(this);
|
|
1761
|
+
const handle = _classPrivateFieldGet2(_sessions, this).session(session);
|
|
1762
|
+
if (!_classPrivateFieldGet2(_configured, this).has(session)) {
|
|
1763
|
+
_classPrivateFieldGet2(_configured, this).add(session);
|
|
1764
|
+
_classPrivateFieldGet2(_options, this).configureSession?.(handle, session);
|
|
1765
|
+
}
|
|
1766
|
+
return handle;
|
|
1767
|
+
}
|
|
1768
|
+
async function _message(op) {
|
|
1769
|
+
if (op.messageId === void 0) return void 0;
|
|
1770
|
+
const message = await _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, op.session).getMessage(op.messageId);
|
|
1771
|
+
return message ? asUIMessages([message])[0] : void 0;
|
|
1772
|
+
}
|
|
1773
|
+
async function _tools(session) {
|
|
1774
|
+
const tools = await resolve(_classPrivateFieldGet2(_options, this).tools, session) ?? {};
|
|
1775
|
+
const client = createToolsFromClientSchemas(_assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).clientTools(session));
|
|
1776
|
+
for (const name of Object.keys(client)) if (Object.hasOwn(tools, name)) console.warn(`ThinkHarness ignored a client tool named "${name}": a server tool has that name`);
|
|
1777
|
+
return {
|
|
1778
|
+
...client,
|
|
1779
|
+
...tools
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Persist messages and operation changes in one transaction; with a
|
|
1784
|
+
* stream id, the transaction also settles and discards that stream (the
|
|
1785
|
+
* cutover), so an eviction leaves either the stream or the message.
|
|
1786
|
+
*/
|
|
1787
|
+
async function _commit(session, streamId, writes) {
|
|
1788
|
+
const sync = _assertClassBrand(_ThinkHarness_brand, this, _handle$1).call(this, session).__DO_NOT_USE_WILL_BREAK__sync();
|
|
1789
|
+
const afters = [];
|
|
1790
|
+
const ids = [];
|
|
1791
|
+
const commit = () => {
|
|
1792
|
+
afters.length = 0;
|
|
1793
|
+
ids.length = 0;
|
|
1794
|
+
writes((message, options = {}) => {
|
|
1795
|
+
ids.push(message.id);
|
|
1796
|
+
afters.push(sync.upsert(asSessionMessage(message), {
|
|
1797
|
+
...options.parentId !== void 0 && { parentId: options.parentId },
|
|
1798
|
+
source: options.source ?? "server"
|
|
1799
|
+
}).after);
|
|
1800
|
+
});
|
|
1801
|
+
};
|
|
1802
|
+
try {
|
|
1803
|
+
if (!(streamId !== void 0 && _classPrivateFieldGet2(_streams, this).__DO_NOT_USE_WILL_BREAK__sync().settle(streamId, "completed", null, {
|
|
1804
|
+
commit,
|
|
1805
|
+
discard: true
|
|
1806
|
+
}))) this.lifecycle.storage.transactionSync(commit);
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
sync.abandon();
|
|
1809
|
+
throw error;
|
|
1810
|
+
}
|
|
1811
|
+
const release = _assertClassBrand(_ThinkHarness_brand, this, _markOwn).call(this, session, ids);
|
|
1812
|
+
try {
|
|
1813
|
+
for (const after of afters) await after();
|
|
1814
|
+
} finally {
|
|
1815
|
+
release();
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
/** Every chunk a stream holds, without waiting for more. */
|
|
1819
|
+
function _streamChunks(streamId) {
|
|
1820
|
+
const streams = _classPrivateFieldGet2(_streams, this).__DO_NOT_USE_WILL_BREAK__sync();
|
|
1821
|
+
if (!streams.getStream(streamId)) return [];
|
|
1822
|
+
const chunks = [];
|
|
1823
|
+
let from = 0;
|
|
1824
|
+
for (;;) {
|
|
1825
|
+
const rows = streams.readChunks(streamId, from, READ_PAGE);
|
|
1826
|
+
for (const row of rows) {
|
|
1827
|
+
const segment = JSON.parse(row.chunk);
|
|
1828
|
+
chunks.push(...segment);
|
|
1829
|
+
}
|
|
1830
|
+
if (rows.length < READ_PAGE) return chunks;
|
|
1831
|
+
from = (rows.at(-1)?.seq ?? from) + 1;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
/** Chunks an evicted model call left in its stream, for a late watcher. */
|
|
1835
|
+
function _recoverableChunks(session) {
|
|
1836
|
+
const running = _assertClassBrand(_ThinkHarness_brand, this, _tables).call(this).open(session).find((op) => op.status === "running");
|
|
1837
|
+
return running?.streamId ? _assertClassBrand(_ThinkHarness_brand, this, _streamChunks).call(this, running.streamId) : [];
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Rebuild a message from the chunks of a model call, applied on top of the
|
|
1841
|
+
* message as it was before the call.
|
|
1842
|
+
*/
|
|
1843
|
+
function accumulate(messageId, existing, chunks) {
|
|
1844
|
+
if (chunks.length === 0) return existing;
|
|
1845
|
+
const accumulator = new StreamAccumulator({
|
|
1846
|
+
messageId: existing?.id ?? messageId,
|
|
1847
|
+
continuation: existing !== void 0,
|
|
1848
|
+
...existing && {
|
|
1849
|
+
existingParts: existing.parts,
|
|
1850
|
+
...isRecord(existing.metadata) && { existingMetadata: existing.metadata }
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
for (const chunk of chunks) accumulator.applyChunk(chunk);
|
|
1854
|
+
return {
|
|
1855
|
+
...accumulator.toMessage(),
|
|
1856
|
+
role: "assistant"
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Stop iterating as soon as `signal` aborts, even if the source never
|
|
1861
|
+
* notices: a model stream that ignores its abort signal must not hold an
|
|
1862
|
+
* aborted turn open.
|
|
1863
|
+
*/
|
|
1864
|
+
async function* untilAborted(source, signal) {
|
|
1865
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
1866
|
+
let onAbort = () => {};
|
|
1867
|
+
const aborted = new Promise((resolveAbort) => {
|
|
1868
|
+
onAbort = () => resolveAbort("aborted");
|
|
1869
|
+
if (signal.aborted) onAbort();
|
|
1870
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
1871
|
+
});
|
|
1872
|
+
try {
|
|
1873
|
+
for (;;) {
|
|
1874
|
+
const next = await Promise.race([iterator.next(), aborted]);
|
|
1875
|
+
if (next === "aborted") {
|
|
1876
|
+
iterator.return?.().catch(() => {});
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (next.done) return;
|
|
1880
|
+
yield next.value;
|
|
1881
|
+
}
|
|
1882
|
+
} finally {
|
|
1883
|
+
signal.removeEventListener("abort", onAbort);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Classify the context-window overflow errors of common providers
|
|
1888
|
+
* (Anthropic, OpenAI, Google, Bedrock, Mistral and others) as
|
|
1889
|
+
* `"context-overflow"`, and everything else as `"fail"`. Pass it as
|
|
1890
|
+
* `hooks.classifyError`, or call it from your own classifier.
|
|
1891
|
+
*
|
|
1892
|
+
* @param error - The model call's error.
|
|
1893
|
+
* @returns `"context-overflow"` or `"fail"`.
|
|
1894
|
+
* @experimental
|
|
1895
|
+
*/
|
|
1896
|
+
function classifyContextOverflow(error) {
|
|
1897
|
+
let text;
|
|
1898
|
+
if (error instanceof Error) text = error.message;
|
|
1899
|
+
else if (typeof error === "string") text = error;
|
|
1900
|
+
else try {
|
|
1901
|
+
text = JSON.stringify(error);
|
|
1902
|
+
} catch {
|
|
1903
|
+
text = String(error);
|
|
1904
|
+
}
|
|
1905
|
+
return CONTEXT_OVERFLOW_PATTERN.test(text) ? "context-overflow" : "fail";
|
|
1906
|
+
}
|
|
1907
|
+
const CONTEXT_OVERFLOW_PATTERN = /prompt is too long|context[_ ]length[_ ]exceeded|maximum context length|exceeds the maximum number of tokens|input token count|reduce the length of|input is too long|too many (?:input )?tokens|context window/i;
|
|
1908
|
+
/** A tool's own recovery field, or the default. */
|
|
1909
|
+
function toolRecovery(tool) {
|
|
1910
|
+
return "recovery" in tool && tool.recovery === "rerun" ? "rerun" : "report";
|
|
1911
|
+
}
|
|
1912
|
+
function isRecord(value) {
|
|
1913
|
+
return typeof value === "object" && value !== null;
|
|
1914
|
+
}
|
|
1915
|
+
function backoff(attempt, baseMs) {
|
|
1916
|
+
if (attempt <= 1) return 0;
|
|
1917
|
+
return Math.min(baseMs * 2 ** (attempt - 2), MAX_BACKOFF_MS);
|
|
1918
|
+
}
|
|
1919
|
+
function streamTag(session, operationId) {
|
|
1920
|
+
return `think:${JSON.stringify([session, operationId])}`;
|
|
1921
|
+
}
|
|
1922
|
+
function waiterKey(session, operationId) {
|
|
1923
|
+
return JSON.stringify([session, operationId]);
|
|
1924
|
+
}
|
|
1925
|
+
/** Project a session event onto the shared harness interface's events. */
|
|
1926
|
+
function toSessionEvents(event) {
|
|
1927
|
+
switch (event.type) {
|
|
1928
|
+
case "operation": return [{
|
|
1929
|
+
type: "operation",
|
|
1930
|
+
status: event.status
|
|
1931
|
+
}];
|
|
1932
|
+
case "run-start": return [{
|
|
1933
|
+
type: "run-start",
|
|
1934
|
+
operations: [event.operationId]
|
|
1935
|
+
}];
|
|
1936
|
+
case "run-end": return [{
|
|
1937
|
+
type: "run-end",
|
|
1938
|
+
operations: [event.operationId]
|
|
1939
|
+
}];
|
|
1940
|
+
case "chunk": {
|
|
1941
|
+
const chunk = toResponseChunk(event.chunk, {});
|
|
1942
|
+
return chunk ? [{
|
|
1943
|
+
type: "chunk",
|
|
1944
|
+
chunk
|
|
1945
|
+
}] : [];
|
|
1946
|
+
}
|
|
1947
|
+
case "message": return [{
|
|
1948
|
+
type: "message",
|
|
1949
|
+
message: toTranscriptMessage(event.message)
|
|
1950
|
+
}];
|
|
1951
|
+
case "reset": return [{ type: "reset" }];
|
|
1952
|
+
case "transcript": return [];
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
var _harness$1 = /* @__PURE__ */ new WeakMap();
|
|
1956
|
+
/**
|
|
1957
|
+
* One session, addressed through the harness.
|
|
1958
|
+
*
|
|
1959
|
+
* @experimental
|
|
1960
|
+
*/
|
|
1961
|
+
var ThinkSession = class {
|
|
1962
|
+
/**
|
|
1963
|
+
* @param harness - The harness that owns the session.
|
|
1964
|
+
* @param id - The session's id.
|
|
1965
|
+
*/
|
|
1966
|
+
constructor(harness, id) {
|
|
1967
|
+
_classPrivateFieldInitSpec(this, _harness$1, void 0);
|
|
1968
|
+
_classPrivateFieldSet2(_harness$1, this, harness);
|
|
1969
|
+
this.id = id;
|
|
1970
|
+
}
|
|
1971
|
+
/** Durably submit input. Resolves before the model runs. */
|
|
1972
|
+
submit(input, options = {}) {
|
|
1973
|
+
const operationId = options.operationId ?? crypto.randomUUID();
|
|
1974
|
+
return _classPrivateFieldGet2(_harness$1, this).enqueue(this.id, operationInput(input, operationId, options.autoContinue ?? true), {
|
|
1975
|
+
...options,
|
|
1976
|
+
operationId
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
/** Submit and wait for the answer and the updated transcript. */
|
|
1980
|
+
async prompt(input, options = {}) {
|
|
1981
|
+
const receipt = await this.submit(input, options);
|
|
1982
|
+
return {
|
|
1983
|
+
...await this.wait(receipt.operationId),
|
|
1984
|
+
messages: await this.messages()
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Answer a user message again. The new answer is a branch beside the
|
|
1989
|
+
* earlier ones, which `Session.getBranches()` still reads.
|
|
1990
|
+
*
|
|
1991
|
+
* @param messageId - The user message. Default: the latest one.
|
|
1992
|
+
*/
|
|
1993
|
+
regenerate(messageId, options = {}) {
|
|
1994
|
+
return _classPrivateFieldGet2(_harness$1, this).enqueue(this.id, {
|
|
1995
|
+
kind: "regenerate",
|
|
1996
|
+
...messageId !== void 0 && { messageId }
|
|
1997
|
+
}, options);
|
|
1998
|
+
}
|
|
1999
|
+
/** Call the model again to continue the latest message. */
|
|
2000
|
+
continue(options = {}) {
|
|
2001
|
+
return _classPrivateFieldGet2(_harness$1, this).enqueue(this.id, { kind: "continue" }, options);
|
|
2002
|
+
}
|
|
2003
|
+
/** Wait for an operation to settle. Aborting `signal` stops only the wait. */
|
|
2004
|
+
wait(operationId, signal) {
|
|
2005
|
+
return _classPrivateFieldGet2(_harness$1, this).wait(this.id, operationId, signal);
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Withdraw one queued operation or abort it while it runs; with no id,
|
|
2009
|
+
* everything open in the session.
|
|
2010
|
+
*/
|
|
2011
|
+
abort(operationId) {
|
|
2012
|
+
return _classPrivateFieldGet2(_harness$1, this).abort(this.id, operationId);
|
|
2013
|
+
}
|
|
2014
|
+
/** Abort everything and clear the transcript, optionally leaving a note. */
|
|
2015
|
+
reset(handoff) {
|
|
2016
|
+
return _classPrivateFieldGet2(_harness$1, this).reset(this.id, handoff);
|
|
2017
|
+
}
|
|
2018
|
+
/** The active transcript. */
|
|
2019
|
+
messages() {
|
|
2020
|
+
return _classPrivateFieldGet2(_harness$1, this).messages(this.id);
|
|
2021
|
+
}
|
|
2022
|
+
/** Operations not settled yet, oldest first. */
|
|
2023
|
+
pending() {
|
|
2024
|
+
return _classPrivateFieldGet2(_harness$1, this).pending(this.id);
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* The running operation, with the chunks its model call has streamed so
|
|
2028
|
+
* far, including those an eviction left behind. Send them, then
|
|
2029
|
+
* `subscribe()`'s chunk events, to a client that joins mid-turn.
|
|
2030
|
+
*/
|
|
2031
|
+
inFlight() {
|
|
2032
|
+
return _classPrivateFieldGet2(_harness$1, this).inFlight(this.id);
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* The answers to a message: its children in the transcript tree. A
|
|
2036
|
+
* regenerated answer is a branch beside the earlier ones.
|
|
2037
|
+
*
|
|
2038
|
+
* @param messageId - The message, usually a user message.
|
|
2039
|
+
*/
|
|
2040
|
+
branches(messageId) {
|
|
2041
|
+
return _classPrivateFieldGet2(_harness$1, this).branches(this.id, messageId);
|
|
2042
|
+
}
|
|
2043
|
+
/** Full-text search over this session's messages. */
|
|
2044
|
+
search(query, options) {
|
|
2045
|
+
return _classPrivateFieldGet2(_harness$1, this).search(this.id, query, options);
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Summarize older messages with the compaction function set in
|
|
2049
|
+
* `configureSession`. Resolves to null when there was nothing to compact.
|
|
2050
|
+
*/
|
|
2051
|
+
compact() {
|
|
2052
|
+
return _classPrivateFieldGet2(_harness$1, this).compact(this.id);
|
|
2053
|
+
}
|
|
2054
|
+
/** Listen to the session's events in the AI SDK's vocabulary. */
|
|
2055
|
+
subscribe(listener) {
|
|
2056
|
+
return _classPrivateFieldGet2(_harness$1, this).subscribe(this.id, listener);
|
|
2057
|
+
}
|
|
2058
|
+
/** The shared harness interface's watch: the state, then every change. */
|
|
2059
|
+
watch() {
|
|
2060
|
+
return _classPrivateFieldGet2(_harness$1, this).watch(this.id);
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Submit input and stream its answer to `callback`, as Think's `chat()`
|
|
2064
|
+
* does: `onStart`, then `onEvent` with each UI chunk as JSON, then
|
|
2065
|
+
* `onDone` or `onError`. Works over RPC with an `RpcTarget` callback.
|
|
2066
|
+
*
|
|
2067
|
+
* The callback lives in memory. If the object is evicted mid-turn the
|
|
2068
|
+
* turn still finishes, but the callback is gone; a caller that must not
|
|
2069
|
+
* lose the answer should `wait()` on the receipt as well.
|
|
2070
|
+
*/
|
|
2071
|
+
async chat(input, callback, options = {}) {
|
|
2072
|
+
const operationId = options.operationId ?? crypto.randomUUID();
|
|
2073
|
+
let delivered = Promise.resolve();
|
|
2074
|
+
const deliver = (fn) => {
|
|
2075
|
+
delivered = delivered.then(fn).catch((error) => {
|
|
2076
|
+
console.error("ThinkHarness chat callback failed", error);
|
|
2077
|
+
});
|
|
2078
|
+
};
|
|
2079
|
+
const unsubscribe = this.subscribe((event) => {
|
|
2080
|
+
if (event.type === "chunk" && event.operationId === operationId) deliver(() => callback.onEvent(JSON.stringify(event.chunk)));
|
|
2081
|
+
});
|
|
2082
|
+
try {
|
|
2083
|
+
const receipt = await this.submit(input, {
|
|
2084
|
+
...options,
|
|
2085
|
+
operationId
|
|
2086
|
+
});
|
|
2087
|
+
deliver(() => callback.onStart?.({ operationId: receipt.operationId }));
|
|
2088
|
+
const result = await this.wait(receipt.operationId);
|
|
2089
|
+
unsubscribe();
|
|
2090
|
+
await delivered;
|
|
2091
|
+
if (result.status === "done") await callback.onDone();
|
|
2092
|
+
else await callback.onError(result.reason ?? "unanswered");
|
|
2093
|
+
return result;
|
|
2094
|
+
} finally {
|
|
2095
|
+
unsubscribe();
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
/** One operation's status, or undefined when the id is unknown. */
|
|
2099
|
+
inspect(operationId) {
|
|
2100
|
+
return _classPrivateFieldGet2(_harness$1, this).inspect(this.id, operationId);
|
|
2101
|
+
}
|
|
2102
|
+
/** Whether the session has open work. */
|
|
2103
|
+
async busy() {
|
|
2104
|
+
return (await this.pending()).length > 0;
|
|
2105
|
+
}
|
|
2106
|
+
};
|
|
2107
|
+
var _harness2 = /* @__PURE__ */ new WeakMap();
|
|
2108
|
+
/**
|
|
2109
|
+
* Every session in this object.
|
|
2110
|
+
*
|
|
2111
|
+
* @experimental
|
|
2112
|
+
*/
|
|
2113
|
+
var ThinkSessions = class {
|
|
2114
|
+
/** @param harness - The harness that owns the sessions. */
|
|
2115
|
+
constructor(harness) {
|
|
2116
|
+
_classPrivateFieldInitSpec(this, _harness2, void 0);
|
|
2117
|
+
_classPrivateFieldSet2(_harness2, this, harness);
|
|
2118
|
+
}
|
|
2119
|
+
/** A handle on one session. */
|
|
2120
|
+
get(id) {
|
|
2121
|
+
return _classPrivateFieldGet2(_harness2, this).session(id);
|
|
2122
|
+
}
|
|
2123
|
+
/** A new, empty session. */
|
|
2124
|
+
create() {
|
|
2125
|
+
return _classPrivateFieldGet2(_harness2, this).createSession();
|
|
2126
|
+
}
|
|
2127
|
+
/** A new session that starts with `from`'s active transcript. */
|
|
2128
|
+
fork(from) {
|
|
2129
|
+
return _classPrivateFieldGet2(_harness2, this).fork(from);
|
|
2130
|
+
}
|
|
2131
|
+
/** Every session, with whether it has open work. */
|
|
2132
|
+
list() {
|
|
2133
|
+
return _classPrivateFieldGet2(_harness2, this).listSessions();
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
//#endregion
|
|
2137
|
+
//#region src/harness/think/chat.ts
|
|
2138
|
+
/** `WebSocket.OPEN`; the constant is not defined on every runtime's global. */
|
|
2139
|
+
const OPEN = 1;
|
|
2140
|
+
function send(connection, frame) {
|
|
2141
|
+
if (connection.readyState !== OPEN) return;
|
|
2142
|
+
try {
|
|
2143
|
+
connection.send(JSON.stringify(frame));
|
|
2144
|
+
} catch {}
|
|
2145
|
+
}
|
|
2146
|
+
function parseBody(body) {
|
|
2147
|
+
if (typeof body !== "string" || body === "") return {};
|
|
2148
|
+
try {
|
|
2149
|
+
const parsed = JSON.parse(body);
|
|
2150
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
2151
|
+
} catch {
|
|
2152
|
+
return {};
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
function clientToolsOf(value) {
|
|
2156
|
+
return value.map((tool) => ({
|
|
2157
|
+
name: tool.name,
|
|
2158
|
+
...tool.description !== void 0 && { description: tool.description },
|
|
2159
|
+
...tool.parameters !== void 0 && { parameters: tool.parameters }
|
|
2160
|
+
}));
|
|
2161
|
+
}
|
|
2162
|
+
var _harness = /* @__PURE__ */ new WeakMap();
|
|
2163
|
+
var _webSockets = /* @__PURE__ */ new WeakMap();
|
|
2164
|
+
var _session = /* @__PURE__ */ new WeakMap();
|
|
2165
|
+
var _framing = /* @__PURE__ */ new WeakMap();
|
|
2166
|
+
var _awaitingAck = /* @__PURE__ */ new WeakMap();
|
|
2167
|
+
var _replayed = /* @__PURE__ */ new WeakMap();
|
|
2168
|
+
var _requesters = /* @__PURE__ */ new WeakMap();
|
|
2169
|
+
var _pendingProbes = /* @__PURE__ */ new WeakMap();
|
|
2170
|
+
var _unobserve = /* @__PURE__ */ new WeakMap();
|
|
2171
|
+
var _ThinkChat_brand = /* @__PURE__ */ new WeakSet();
|
|
2172
|
+
/**
|
|
2173
|
+
* Serves one ThinkHarness session over Think's chat WebSocket protocol and
|
|
2174
|
+
* its `GET …/get-messages` route. Install it on the Lifecycle after the
|
|
2175
|
+
* harness and the WebSockets capability:
|
|
2176
|
+
*
|
|
2177
|
+
* ```ts
|
|
2178
|
+
* readonly webSockets = new WebSockets();
|
|
2179
|
+
* readonly chat = new ThinkChat({ harness: this.harness, webSockets: this.webSockets });
|
|
2180
|
+
* readonly lifecycle = Lifecycle.install(this)
|
|
2181
|
+
* .use(this.harness)
|
|
2182
|
+
* .use(this.webSockets).use(this.chat);
|
|
2183
|
+
* ```
|
|
2184
|
+
*
|
|
2185
|
+
* Supported: chat requests (`submit-message` and `regenerate-message`),
|
|
2186
|
+
* cancel, clear, client tool results, approvals, and stream resume after
|
|
2187
|
+
* a reconnect or an eviction. Not supported: `messageConcurrency`
|
|
2188
|
+
* strategies other than queueing, client transcript sync, and the
|
|
2189
|
+
* recovery progress frame.
|
|
2190
|
+
*
|
|
2191
|
+
* @experimental The API may change between releases.
|
|
2192
|
+
*/
|
|
2193
|
+
var ThinkChat = class extends LifecycleCapability {
|
|
2194
|
+
/** @param options - The harness, its WebSockets, and the session to serve. */
|
|
2195
|
+
constructor(options) {
|
|
2196
|
+
super("think-chat");
|
|
2197
|
+
_classPrivateMethodInitSpec(this, _ThinkChat_brand);
|
|
2198
|
+
_classPrivateFieldInitSpec(this, _harness, void 0);
|
|
2199
|
+
_classPrivateFieldInitSpec(this, _webSockets, void 0);
|
|
2200
|
+
_classPrivateFieldInitSpec(this, _session, void 0);
|
|
2201
|
+
_classPrivateFieldInitSpec(this, _framing, /* @__PURE__ */ new Map());
|
|
2202
|
+
_classPrivateFieldInitSpec(this, _awaitingAck, /* @__PURE__ */ new Set());
|
|
2203
|
+
_classPrivateFieldInitSpec(this, _replayed, /* @__PURE__ */ new Map());
|
|
2204
|
+
_classPrivateFieldInitSpec(this, _requesters, /* @__PURE__ */ new Map());
|
|
2205
|
+
_classPrivateFieldInitSpec(this, _pendingProbes, /* @__PURE__ */ new Map());
|
|
2206
|
+
_classPrivateFieldInitSpec(this, _unobserve, void 0);
|
|
2207
|
+
_classPrivateFieldSet2(_harness, this, options.harness);
|
|
2208
|
+
_classPrivateFieldSet2(_webSockets, this, options.webSockets);
|
|
2209
|
+
_classPrivateFieldSet2(_session, this, options.session ?? "");
|
|
2210
|
+
_classPrivateFieldGet2(_webSockets, this).use({
|
|
2211
|
+
onConnect: (connection) => _assertClassBrand(_ThinkChat_brand, this, _onConnect).call(this, connection),
|
|
2212
|
+
onMessage: (connection, message) => _assertClassBrand(_ThinkChat_brand, this, _onMessage).call(this, connection, message),
|
|
2213
|
+
onClose: (connection) => _assertClassBrand(_ThinkChat_brand, this, _forget).call(this, connection),
|
|
2214
|
+
onError: (connection) => _assertClassBrand(_ThinkChat_brand, this, _forget).call(this, connection)
|
|
2215
|
+
});
|
|
2216
|
+
_classPrivateFieldSet2(_unobserve, this, _classPrivateFieldGet2(_harness, this).observe((session, event) => {
|
|
2217
|
+
if (session === _classPrivateFieldGet2(_session, this)) _assertClassBrand(_ThinkChat_brand, this, _onEvent).call(this, event);
|
|
2218
|
+
}));
|
|
2219
|
+
}
|
|
2220
|
+
/** Answer `GET …/get-messages` with the session's transcript. */
|
|
2221
|
+
async onRequest(context) {
|
|
2222
|
+
const { request } = context;
|
|
2223
|
+
if (request.method !== "GET") return void 0;
|
|
2224
|
+
if (!new URL(request.url).pathname.endsWith("/get-messages")) return;
|
|
2225
|
+
return Response.json(await _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this)).messages());
|
|
2226
|
+
}
|
|
2227
|
+
/** Stop listening to the harness. */
|
|
2228
|
+
dispose() {
|
|
2229
|
+
_classPrivateFieldGet2(_unobserve, this).call(this);
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
async function _onConnect(connection) {
|
|
2233
|
+
const session = _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this));
|
|
2234
|
+
send(connection, {
|
|
2235
|
+
type: CHAT_MESSAGE_TYPES.CHAT_MESSAGES,
|
|
2236
|
+
messages: await session.messages(),
|
|
2237
|
+
connect: true
|
|
2238
|
+
});
|
|
2239
|
+
const inFlight = await session.inFlight();
|
|
2240
|
+
if (inFlight) _assertClassBrand(_ThinkChat_brand, this, _offerResume).call(this, connection, inFlight.operationId);
|
|
2241
|
+
}
|
|
2242
|
+
function _forget(connection) {
|
|
2243
|
+
_classPrivateFieldGet2(_awaitingAck, this).delete(connection.id);
|
|
2244
|
+
_classPrivateFieldGet2(_pendingProbes, this).delete(connection.id);
|
|
2245
|
+
_classPrivateFieldGet2(_replayed, this).delete(connection.id);
|
|
2246
|
+
}
|
|
2247
|
+
function _offerResume(connection, operationId, probeId) {
|
|
2248
|
+
_classPrivateFieldGet2(_awaitingAck, this).add(connection.id);
|
|
2249
|
+
send(connection, {
|
|
2250
|
+
type: CHAT_MESSAGE_TYPES.STREAM_RESUMING,
|
|
2251
|
+
id: operationId,
|
|
2252
|
+
...probeId !== void 0 && { probeId }
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
async function _onMessage(connection, raw) {
|
|
2256
|
+
if (typeof raw !== "string") return false;
|
|
2257
|
+
const event = parseProtocolMessage(raw);
|
|
2258
|
+
if (!event) return false;
|
|
2259
|
+
await _assertClassBrand(_ThinkChat_brand, this, _handle).call(this, connection, event);
|
|
2260
|
+
return true;
|
|
2261
|
+
}
|
|
2262
|
+
async function _handle(connection, event) {
|
|
2263
|
+
const session = _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this));
|
|
2264
|
+
switch (event.type) {
|
|
2265
|
+
case "chat-request": {
|
|
2266
|
+
const body = parseBody(event.init.body);
|
|
2267
|
+
const clientTools = body.clientTools ? { clientTools: body.clientTools } : {};
|
|
2268
|
+
_classPrivateFieldGet2(_requesters, this).set(event.id, connection.id);
|
|
2269
|
+
if (body.trigger === "regenerate-message") {
|
|
2270
|
+
await session.regenerate(void 0, { operationId: event.id });
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
const known = new Set((await session.messages()).map((message) => message.id));
|
|
2274
|
+
const fresh = (body.messages ?? []).filter((message) => !known.has(message.id));
|
|
2275
|
+
if (!(await session.submit(fresh, {
|
|
2276
|
+
operationId: event.id,
|
|
2277
|
+
source: "client",
|
|
2278
|
+
...clientTools
|
|
2279
|
+
})).accepted) {
|
|
2280
|
+
const status = await session.inspect(event.id);
|
|
2281
|
+
if (status?.status === "done" || status?.status === "unanswered") {
|
|
2282
|
+
_classPrivateFieldGet2(_requesters, this).delete(event.id);
|
|
2283
|
+
await _assertClassBrand(_ThinkChat_brand, this, _finish).call(this, {
|
|
2284
|
+
operationId: status.operationId,
|
|
2285
|
+
status: status.status,
|
|
2286
|
+
..."reason" in status && status.reason !== void 0 && { reason: status.reason }
|
|
2287
|
+
}, void 0, connection);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
case "cancel":
|
|
2293
|
+
await session.abort(event.id);
|
|
2294
|
+
return;
|
|
2295
|
+
case "clear":
|
|
2296
|
+
await session.reset();
|
|
2297
|
+
for (const other of _classPrivateFieldGet2(_webSockets, this).getConnections()) if (other.id !== connection.id) send(other, { type: CHAT_MESSAGE_TYPES.CHAT_CLEAR });
|
|
2298
|
+
return;
|
|
2299
|
+
case "tool-result": {
|
|
2300
|
+
const operationId = crypto.randomUUID();
|
|
2301
|
+
_classPrivateFieldGet2(_requesters, this).set(operationId, connection.id);
|
|
2302
|
+
await session.submit({
|
|
2303
|
+
type: "tool-result",
|
|
2304
|
+
toolCallId: event.toolCallId,
|
|
2305
|
+
result: event.state === "output-error" ? {
|
|
2306
|
+
ok: false,
|
|
2307
|
+
...event.errorText !== void 0 && { errorText: event.errorText }
|
|
2308
|
+
} : {
|
|
2309
|
+
ok: true,
|
|
2310
|
+
output: event.output ?? null
|
|
2311
|
+
}
|
|
2312
|
+
}, {
|
|
2313
|
+
operationId,
|
|
2314
|
+
source: "client",
|
|
2315
|
+
autoContinue: event.autoContinue ?? true,
|
|
2316
|
+
...event.clientTools && { clientTools: clientToolsOf(event.clientTools) }
|
|
2317
|
+
});
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
case "tool-approval": {
|
|
2321
|
+
const approvalId = await _assertClassBrand(_ThinkChat_brand, this, _approvalId).call(this, event.toolCallId);
|
|
2322
|
+
if (approvalId === void 0) return;
|
|
2323
|
+
const operationId = crypto.randomUUID();
|
|
2324
|
+
_classPrivateFieldGet2(_requesters, this).set(operationId, connection.id);
|
|
2325
|
+
await session.submit({
|
|
2326
|
+
type: "approval",
|
|
2327
|
+
approvalId,
|
|
2328
|
+
approved: event.approved
|
|
2329
|
+
}, {
|
|
2330
|
+
operationId,
|
|
2331
|
+
source: "client",
|
|
2332
|
+
autoContinue: event.autoContinue ?? true
|
|
2333
|
+
});
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
case "stream-resume-request": {
|
|
2337
|
+
const inFlight = await session.inFlight();
|
|
2338
|
+
if (inFlight) _assertClassBrand(_ThinkChat_brand, this, _offerResume).call(this, connection, inFlight.operationId, event.probeId);
|
|
2339
|
+
else if ((await session.pending()).length > 0) {
|
|
2340
|
+
_classPrivateFieldGet2(_pendingProbes, this).set(connection.id, event.probeId);
|
|
2341
|
+
send(connection, {
|
|
2342
|
+
type: CHAT_MESSAGE_TYPES.STREAM_PENDING,
|
|
2343
|
+
...event.probeId !== void 0 && { probeId: event.probeId }
|
|
2344
|
+
});
|
|
2345
|
+
} else send(connection, {
|
|
2346
|
+
type: CHAT_MESSAGE_TYPES.STREAM_RESUME_NONE,
|
|
2347
|
+
reason: STREAM_RESUME_NONE_REASONS.IDLE,
|
|
2348
|
+
...event.probeId !== void 0 && { probeId: event.probeId }
|
|
2349
|
+
});
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
case "stream-resume-ack":
|
|
2353
|
+
await _assertClassBrand(_ThinkChat_brand, this, _replay).call(this, connection, event.id);
|
|
2354
|
+
return;
|
|
2355
|
+
case "messages": return;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
/** Replay a running model call's chunks to one connection, then go live. */
|
|
2359
|
+
async function _replay(connection, operationId) {
|
|
2360
|
+
const inFlight = await _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this)).inFlight();
|
|
2361
|
+
if (!inFlight || inFlight.operationId !== operationId) {
|
|
2362
|
+
_classPrivateFieldGet2(_awaitingAck, this).delete(connection.id);
|
|
2363
|
+
send(connection, {
|
|
2364
|
+
type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
|
|
2365
|
+
id: operationId,
|
|
2366
|
+
body: "",
|
|
2367
|
+
done: true,
|
|
2368
|
+
replay: true
|
|
2369
|
+
});
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
const { continuation, chunks } = inFlight;
|
|
2373
|
+
let seq = 0;
|
|
2374
|
+
for (const chunk of chunks) send(connection, {
|
|
2375
|
+
type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
|
|
2376
|
+
id: operationId,
|
|
2377
|
+
body: JSON.stringify(chunk),
|
|
2378
|
+
done: false,
|
|
2379
|
+
replay: true,
|
|
2380
|
+
seq: seq++,
|
|
2381
|
+
...continuation && { continuation: true }
|
|
2382
|
+
});
|
|
2383
|
+
send(connection, {
|
|
2384
|
+
type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
|
|
2385
|
+
id: operationId,
|
|
2386
|
+
body: "",
|
|
2387
|
+
done: false,
|
|
2388
|
+
replay: true,
|
|
2389
|
+
replayComplete: true,
|
|
2390
|
+
...continuation && { continuation: true }
|
|
2391
|
+
});
|
|
2392
|
+
_classPrivateFieldGet2(_replayed, this).set(connection.id, {
|
|
2393
|
+
operationId,
|
|
2394
|
+
continuation
|
|
2395
|
+
});
|
|
2396
|
+
_classPrivateFieldGet2(_awaitingAck, this).delete(connection.id);
|
|
2397
|
+
}
|
|
2398
|
+
async function _approvalId(toolCallId) {
|
|
2399
|
+
const messages = await _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this)).messages();
|
|
2400
|
+
for (const message of [...messages].reverse()) for (const part of message.parts) if ("toolCallId" in part && part.toolCallId === toolCallId && "approval" in part && part.approval) return part.approval.id;
|
|
2401
|
+
}
|
|
2402
|
+
function _onEvent(event) {
|
|
2403
|
+
switch (event.type) {
|
|
2404
|
+
case "run-start": {
|
|
2405
|
+
const requester = _classPrivateFieldGet2(_requesters, this).get(event.operationId);
|
|
2406
|
+
_classPrivateFieldGet2(_framing, this).set(event.operationId, {
|
|
2407
|
+
continuation: event.continuation,
|
|
2408
|
+
requester,
|
|
2409
|
+
finish: void 0,
|
|
2410
|
+
started: false
|
|
2411
|
+
});
|
|
2412
|
+
for (const connection of _classPrivateFieldGet2(_webSockets, this).getConnections()) {
|
|
2413
|
+
if (!_classPrivateFieldGet2(_pendingProbes, this).has(connection.id)) continue;
|
|
2414
|
+
const probeId = _classPrivateFieldGet2(_pendingProbes, this).get(connection.id);
|
|
2415
|
+
_classPrivateFieldGet2(_pendingProbes, this).delete(connection.id);
|
|
2416
|
+
_assertClassBrand(_ThinkChat_brand, this, _offerResume).call(this, connection, event.operationId, probeId);
|
|
2417
|
+
}
|
|
2418
|
+
if (event.continuation && requester !== void 0) {
|
|
2419
|
+
for (const connection of _classPrivateFieldGet2(_webSockets, this).getConnections()) if (connection.id === requester) _assertClassBrand(_ThinkChat_brand, this, _offerResume).call(this, connection, event.operationId);
|
|
2420
|
+
}
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
case "chunk": {
|
|
2424
|
+
const framing = _classPrivateFieldGet2(_framing, this).get(event.operationId);
|
|
2425
|
+
if (event.chunk.type === "finish") {
|
|
2426
|
+
if (framing) framing.finish = event.chunk;
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (event.chunk.type === "start" && framing) {
|
|
2430
|
+
if (framing.started) return;
|
|
2431
|
+
framing.started = true;
|
|
2432
|
+
}
|
|
2433
|
+
_assertClassBrand(_ThinkChat_brand, this, _broadcastChunk).call(this, event.operationId, event.chunk, framing);
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
case "message": {
|
|
2437
|
+
if (event.operationId !== void 0 && _classPrivateFieldGet2(_framing, this).has(event.operationId)) return;
|
|
2438
|
+
const requester = event.operationId === void 0 ? void 0 : _classPrivateFieldGet2(_requesters, this).get(event.operationId);
|
|
2439
|
+
_assertClassBrand(_ThinkChat_brand, this, _broadcastTranscript).call(this, requester);
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
case "operation": {
|
|
2443
|
+
const { status } = event;
|
|
2444
|
+
if (status.status !== "done" && status.status !== "unanswered") return;
|
|
2445
|
+
const framing = _classPrivateFieldGet2(_framing, this).get(status.operationId);
|
|
2446
|
+
_classPrivateFieldGet2(_framing, this).delete(status.operationId);
|
|
2447
|
+
_classPrivateFieldGet2(_requesters, this).delete(status.operationId);
|
|
2448
|
+
_assertClassBrand(_ThinkChat_brand, this, _finish).call(this, status, framing).then(() => _assertClassBrand(_ThinkChat_brand, this, _resolveProbes).call(this));
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
case "transcript":
|
|
2452
|
+
_assertClassBrand(_ThinkChat_brand, this, _broadcastTranscript).call(this);
|
|
2453
|
+
return;
|
|
2454
|
+
case "reset":
|
|
2455
|
+
for (const connection of _classPrivateFieldGet2(_webSockets, this).getConnections()) send(connection, { type: CHAT_MESSAGE_TYPES.CHAT_CLEAR });
|
|
2456
|
+
return;
|
|
2457
|
+
case "run-end": return;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
function _broadcastChunk(operationId, chunk, framing) {
|
|
2461
|
+
const body = JSON.stringify(chunk);
|
|
2462
|
+
for (const connection of _classPrivateFieldGet2(_webSockets, this).getConnections()) {
|
|
2463
|
+
if (_classPrivateFieldGet2(_awaitingAck, this).has(connection.id)) continue;
|
|
2464
|
+
const replayed = _classPrivateFieldGet2(_replayed, this).get(connection.id);
|
|
2465
|
+
const continuation = replayed?.operationId === operationId ? replayed.continuation : framing?.continuation ?? false;
|
|
2466
|
+
send(connection, {
|
|
2467
|
+
type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
|
|
2468
|
+
id: operationId,
|
|
2469
|
+
body,
|
|
2470
|
+
done: false,
|
|
2471
|
+
...continuation && { continuation: true }
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
async function _broadcastTranscript(except) {
|
|
2476
|
+
const messages = await _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this)).messages();
|
|
2477
|
+
for (const connection of _classPrivateFieldGet2(_webSockets, this).getConnections()) {
|
|
2478
|
+
if (connection.id === except) continue;
|
|
2479
|
+
send(connection, {
|
|
2480
|
+
type: CHAT_MESSAGE_TYPES.CHAT_MESSAGES,
|
|
2481
|
+
messages
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
/** Tell probes still waiting that nothing will stream, once the session is idle. */
|
|
2486
|
+
async function _resolveProbes() {
|
|
2487
|
+
if (_classPrivateFieldGet2(_pendingProbes, this).size === 0) return;
|
|
2488
|
+
if ((await _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this)).pending()).length > 0) return;
|
|
2489
|
+
for (const connection of _classPrivateFieldGet2(_webSockets, this).getConnections()) {
|
|
2490
|
+
if (!_classPrivateFieldGet2(_pendingProbes, this).has(connection.id)) continue;
|
|
2491
|
+
const probeId = _classPrivateFieldGet2(_pendingProbes, this).get(connection.id);
|
|
2492
|
+
_classPrivateFieldGet2(_pendingProbes, this).delete(connection.id);
|
|
2493
|
+
send(connection, {
|
|
2494
|
+
type: CHAT_MESSAGE_TYPES.STREAM_RESUME_NONE,
|
|
2495
|
+
reason: STREAM_RESUME_NONE_REASONS.IDLE,
|
|
2496
|
+
...probeId !== void 0 && { probeId }
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
/** The transcript first, then the terminal frame (#2119). */
|
|
2501
|
+
async function _finish(status, framing, only) {
|
|
2502
|
+
const { operationId } = status;
|
|
2503
|
+
if (framing?.finish) _assertClassBrand(_ThinkChat_brand, this, _broadcastChunk).call(this, operationId, framing.finish, framing);
|
|
2504
|
+
const messages = await _classPrivateFieldGet2(_harness, this).session(_classPrivateFieldGet2(_session, this)).messages();
|
|
2505
|
+
const reason = status.status === "unanswered" ? status.reason : void 0;
|
|
2506
|
+
const outcome = status.status === "done" ? "completed" : reason === "aborted" ? "aborted" : reason === "withdrawn" || reason === "not_waiting" || reason === "not_found" || reason === "empty" ? "skipped" : "error";
|
|
2507
|
+
const connections = only ? [only] : _classPrivateFieldGet2(_webSockets, this).getConnections();
|
|
2508
|
+
for (const connection of connections) {
|
|
2509
|
+
send(connection, {
|
|
2510
|
+
type: CHAT_MESSAGE_TYPES.CHAT_MESSAGES,
|
|
2511
|
+
messages
|
|
2512
|
+
});
|
|
2513
|
+
send(connection, {
|
|
2514
|
+
type: CHAT_MESSAGE_TYPES.USE_CHAT_RESPONSE,
|
|
2515
|
+
id: operationId,
|
|
2516
|
+
body: outcome === "error" ? reason ?? "" : "",
|
|
2517
|
+
done: true,
|
|
2518
|
+
outcome,
|
|
2519
|
+
...outcome === "error" && { error: true },
|
|
2520
|
+
...framing?.continuation && { continuation: true }
|
|
2521
|
+
});
|
|
2522
|
+
_classPrivateFieldGet2(_awaitingAck, this).delete(connection.id);
|
|
2523
|
+
if (_classPrivateFieldGet2(_replayed, this).get(connection.id)?.operationId === operationId) _classPrivateFieldGet2(_replayed, this).delete(connection.id);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
//#endregion
|
|
2527
|
+
export { ROOT_SESSION, SteerNotSupportedError, ThinkChat, ThinkHarness, ThinkSession, ThinkSessions, classifyContextOverflow };
|
|
2528
|
+
|
|
2529
|
+
//# sourceMappingURL=index.js.map
|