agents 0.26.0 → 0.28.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/abortable-C1SpfVoi.js +21 -0
- package/dist/abortable-C1SpfVoi.js.map +1 -0
- package/dist/{agent-routing-0DR0Tk2v.d.ts → agent-routing-QDcchcQP.d.ts} +16 -16
- 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-DHL2XI7u.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/ai-ahruGw7e.d.ts +239 -0
- package/dist/ai-sdk-Bdsf6mSA.js +2444 -0
- package/dist/ai-sdk-Bdsf6mSA.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.d.ts +8 -227
- package/dist/browser/ai.js +259 -1
- package/dist/browser/ai.js.map +1 -0
- package/dist/browser/index.d.ts +56 -46
- package/dist/browser/index.js +4 -361
- package/dist/browser/{ai-sdk.d.ts → tools/ai-sdk.d.ts} +7 -7
- package/dist/browser/{ai-sdk.js → tools/ai-sdk.js} +6 -5
- package/dist/browser/tools/ai-sdk.js.map +1 -0
- package/dist/browser/tools/pi.d.ts +111 -0
- package/dist/browser/tools/pi.js +119 -0
- package/dist/browser/tools/pi.js.map +1 -0
- package/dist/browser/{tanstack-ai.d.ts → tools/tanstack-ai.d.ts} +39 -37
- package/dist/browser/{tanstack-ai.js → tools/tanstack-ai.js} +73 -59
- package/dist/browser/tools/tanstack-ai.js.map +1 -0
- package/dist/browser-CEdNlsaD.js +481 -0
- package/dist/browser-CEdNlsaD.js.map +1 -0
- package/dist/{browser-DE3d-RLk.d.ts → browser-D30VlpcA.d.ts} +63 -37
- package/dist/{browser-tool-CQ10e6Kj.js → browser-tool-4iomavs_.js} +198 -62
- package/dist/browser-tool-4iomavs_.js.map +1 -0
- package/dist/{browser-tool-DuNdlwZ9.d.ts → browser-tool-vtrPOV8e.d.ts} +25 -6
- package/dist/{capability-B4WbF81e.js → capability-DBAKc6UZ.js} +1 -3
- package/dist/capability-DBAKc6UZ.js.map +1 -0
- package/dist/{capability-runner-DBAnGr6L.d.ts → capability-runner-BiVUtjqC.d.ts} +9 -7
- 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-je3koD9Z.d.ts → client-BGIQKJhX.d.ts} +2 -2
- package/dist/{client-fyQrLGkD.js → client-BnYbNT3z.js} +29 -5
- package/dist/client-BnYbNT3z.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +1 -1
- package/dist/{connector-58XDGvc3.js → connector-CtZylXdk.js} +3 -603
- package/dist/connector-CtZylXdk.js.map +1 -0
- package/dist/{connector-Cl-VqPmA.d.ts → connector-DEXzVoaY.d.ts} +9 -374
- package/dist/conversations-FjHDRZyD.d.ts +192 -0
- package/dist/{current-agent-vAk_5JMt.d.ts → current-agent-CKgT5d1O.d.ts} +3 -5
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- 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-DOI_bzCH.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-BFO6vRDn.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 +3 -2
- package/dist/harness/pi/index.js +3 -3
- 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 +2721 -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-BEL3ZV-s.d.ts} +2 -6
- package/dist/index-CBt8oRJt.d.ts +198 -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-BQykTEp_.js +20 -0
- package/dist/input-schema-BQykTEp_.js.map +1 -0
- package/dist/input-schema-D2QAtHZH.js +23 -0
- package/dist/input-schema-D2QAtHZH.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/lifecycle/index.js +2 -2
- package/dist/{lifecycle-CFu6OV03.js → lifecycle-DGorpAmA.js} +2 -4
- package/dist/lifecycle-DGorpAmA.js.map +1 -0
- 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-CiIsJHXR.d.ts +130 -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/queue/index.js +1 -1
- package/dist/{queue-BB-zfIYF.js → queue-CcIjVYTE.js} +2 -2
- package/dist/{queue-BB-zfIYF.js.map → queue-CcIjVYTE.js.map} +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/routing/index.d.ts +2 -2
- package/dist/routing/index.js +1 -1
- package/dist/{scheduler-RdACNXDP.d.ts → scheduler-BCLESHSC.d.ts} +2 -12
- package/dist/{scheduler-DZ3xQfeK.js → scheduler-FIHv2-kJ.js} +2 -4
- package/dist/scheduler-FIHv2-kJ.js.map +1 -0
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/session-store-Do4xkDcf.js +606 -0
- package/dist/session-store-Do4xkDcf.js.map +1 -0
- package/dist/session-store-DoPY12xM.d.ts +383 -0
- package/dist/sessions/index.d.ts +11 -434
- package/dist/sessions/index.js +1 -2271
- package/dist/sessions-_vN5BEZX.js +2272 -0
- package/dist/sessions-_vN5BEZX.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-CQxJFv4l.js} +8 -8
- package/dist/src-CQxJFv4l.js.map +1 -0
- 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/state/index.js +1 -3
- package/dist/state/index.js.map +1 -1
- package/dist/store-BKM4Xm9M.d.ts +332 -0
- package/dist/store-CslVzfN7.js +458 -0
- package/dist/store-CslVzfN7.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-lsHMJhRW.js} +17 -4
- package/dist/streams-lsHMJhRW.js.map +1 -0
- package/dist/{streams-Cmkj9QfW.d.ts → streams-vrCtbUIn.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-BN_5AWqI.js} +3 -2
- package/dist/tasks-BN_5AWqI.js.map +1 -0
- package/dist/{tasks-9YxYtByh.d.ts → tasks-E89KdwkS.d.ts} +2 -2
- package/dist/tool-CTp72Jb_.d.ts +278 -0
- package/dist/tool-CxKsvxjm.js +1126 -0
- package/dist/tool-CxKsvxjm.js.map +1 -0
- package/dist/tool-DoVberpN.d.ts +301 -0
- package/dist/tool-cQRkwSZZ.js +410 -0
- package/dist/tool-cQRkwSZZ.js.map +1 -0
- package/dist/tool-helpers-O5BDVEAY.js +58 -0
- package/dist/tool-helpers-O5BDVEAY.js.map +1 -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/web/fetch/index.d.ts +89 -0
- package/dist/web/fetch/index.js +20 -0
- package/dist/web/fetch/index.js.map +1 -0
- package/dist/web/fetch/tools/ai-sdk.d.ts +80 -0
- package/dist/web/fetch/tools/ai-sdk.js +47 -0
- package/dist/web/fetch/tools/ai-sdk.js.map +1 -0
- package/dist/web/fetch/tools/pi.d.ts +84 -0
- package/dist/web/fetch/tools/pi.js +88 -0
- package/dist/web/fetch/tools/pi.js.map +1 -0
- package/dist/web/fetch/tools/tanstack-ai.d.ts +78 -0
- package/dist/web/fetch/tools/tanstack-ai.js +45 -0
- package/dist/web/fetch/tools/tanstack-ai.js.map +1 -0
- package/dist/web/search/index.d.ts +58 -0
- package/dist/web/search/index.js +2 -0
- package/dist/web/search/tools/ai-sdk.d.ts +77 -0
- package/dist/web/search/tools/ai-sdk.js +46 -0
- package/dist/web/search/tools/ai-sdk.js.map +1 -0
- package/dist/web/search/tools/pi.d.ts +82 -0
- package/dist/web/search/tools/pi.js +89 -0
- package/dist/web/search/tools/pi.js.map +1 -0
- package/dist/web/search/tools/tanstack-ai.d.ts +71 -0
- package/dist/web/search/tools/tanstack-ai.js +46 -0
- package/dist/web/search/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-C3zy8YR0.js} +20 -8
- package/dist/websockets-C3zy8YR0.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 +29 -1
- package/docs/fetch-the-web.md +276 -0
- package/docs/harnesses/pi.md +25 -0
- package/docs/harnesses/think.md +215 -0
- package/docs/index.md +99 -75
- package/docs/lifecycle.md +42 -3
- package/docs/queue.md +2 -3
- package/docs/scheduling.md +2 -6
- package/docs/search-the-web.md +135 -0
- package/package.json +148 -44
- package/dist/ai-fm7xx1BK.js +0 -312
- package/dist/ai-fm7xx1BK.js.map +0 -1
- package/dist/browser/ai-sdk.js.map +0 -1
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/tanstack-ai.js.map +0 -1
- package/dist/browser-tool-CQ10e6Kj.js.map +0 -1
- package/dist/capability-B4WbF81e.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/client-fyQrLGkD.js.map +0 -1
- package/dist/connector-58XDGvc3.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/lifecycle-CFu6OV03.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/scheduler-DZ3xQfeK.js.map +0 -1
- package/dist/sessions/index.js.map +0 -1
- package/dist/src-BZeu-SCD.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,1072 @@
|
|
|
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 { t as LifecycleCapability } from "../../capability-DBAKc6UZ.js";
|
|
4
|
+
import "../../lifecycle-DGorpAmA.js";
|
|
5
|
+
//#region src/harness/table-prefix.ts
|
|
6
|
+
/**
|
|
7
|
+
* A view of `storage` whose SQL is rewritten into `prefix`'s namespace.
|
|
8
|
+
* Everything other than `sql.exec` is the original storage, so transactions
|
|
9
|
+
* and the rest of the API behave as before.
|
|
10
|
+
*
|
|
11
|
+
* @param storage - The Durable Object's storage.
|
|
12
|
+
* @param prefix - The namespace, such as `opencode_`. Letters, digits and
|
|
13
|
+
* underscores, starting with a letter; never `_cf_`, which Durable Objects
|
|
14
|
+
* reserve.
|
|
15
|
+
* @returns The same storage, with `sql.exec` rewritten.
|
|
16
|
+
* @throws When `prefix` is not a valid prefix: a defect in the caller.
|
|
17
|
+
*/
|
|
18
|
+
function prefixTables(storage, prefix) {
|
|
19
|
+
const rewriter = new TablePrefixRewriter(storage.sql, prefix);
|
|
20
|
+
const sql = new Proxy(storage.sql, { get(target, property) {
|
|
21
|
+
if (property === "exec") return rewriter.exec;
|
|
22
|
+
const value = Reflect.get(target, property, target);
|
|
23
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
24
|
+
} });
|
|
25
|
+
return new Proxy(storage, { get(target, property) {
|
|
26
|
+
if (property === "sql") return sql;
|
|
27
|
+
const value = Reflect.get(target, property, target);
|
|
28
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
29
|
+
} });
|
|
30
|
+
}
|
|
31
|
+
/** Words after which an identifier names a table or index in DDL. */
|
|
32
|
+
const DDL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
33
|
+
"TABLE",
|
|
34
|
+
"INDEX",
|
|
35
|
+
"REFERENCES"
|
|
36
|
+
]);
|
|
37
|
+
/** Words after which an identifier names a table in DML. */
|
|
38
|
+
const DML_KEYWORDS = /* @__PURE__ */ new Set([
|
|
39
|
+
"FROM",
|
|
40
|
+
"JOIN",
|
|
41
|
+
"INTO",
|
|
42
|
+
"UPDATE"
|
|
43
|
+
]);
|
|
44
|
+
/** Words that may follow `sqlite_master` without being its alias. */
|
|
45
|
+
const CLAUSE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
46
|
+
"WHERE",
|
|
47
|
+
"ORDER",
|
|
48
|
+
"GROUP",
|
|
49
|
+
"LIMIT",
|
|
50
|
+
"JOIN",
|
|
51
|
+
"LEFT",
|
|
52
|
+
"INNER",
|
|
53
|
+
"CROSS",
|
|
54
|
+
"ON",
|
|
55
|
+
"UNION",
|
|
56
|
+
"EXCEPT",
|
|
57
|
+
"INTERSECT",
|
|
58
|
+
"HAVING",
|
|
59
|
+
"WINDOW"
|
|
60
|
+
]);
|
|
61
|
+
const MASTER_TABLES = /* @__PURE__ */ new Set(["sqlite_master", "sqlite_schema"]);
|
|
62
|
+
/** PRAGMAs whose argument is a table or index name. */
|
|
63
|
+
const SCHEMA_PRAGMAS = /* @__PURE__ */ new Set([
|
|
64
|
+
"table_info",
|
|
65
|
+
"table_xinfo",
|
|
66
|
+
"index_list",
|
|
67
|
+
"index_info",
|
|
68
|
+
"index_xinfo",
|
|
69
|
+
"foreign_key_list",
|
|
70
|
+
"foreign_key_check",
|
|
71
|
+
"integrity_check",
|
|
72
|
+
"quick_check"
|
|
73
|
+
]);
|
|
74
|
+
/**
|
|
75
|
+
* Bare words that follow a table keyword without naming a table:
|
|
76
|
+
* `TABLE IF NOT EXISTS`, `INSERT OR REPLACE INTO`, `FROM (SELECT …)`.
|
|
77
|
+
*/
|
|
78
|
+
const NOT_NAMES = /* @__PURE__ */ new Set([
|
|
79
|
+
"IF",
|
|
80
|
+
"NOT",
|
|
81
|
+
"EXISTS",
|
|
82
|
+
"SELECT",
|
|
83
|
+
"VALUES",
|
|
84
|
+
"OR"
|
|
85
|
+
]);
|
|
86
|
+
var _sql = /* @__PURE__ */ new WeakMap();
|
|
87
|
+
var _prefix = /* @__PURE__ */ new WeakMap();
|
|
88
|
+
var _masterView = /* @__PURE__ */ new WeakMap();
|
|
89
|
+
var _names = /* @__PURE__ */ new WeakMap();
|
|
90
|
+
var _cache = /* @__PURE__ */ new WeakMap();
|
|
91
|
+
var _TablePrefixRewriter_brand = /* @__PURE__ */ new WeakSet();
|
|
92
|
+
var TablePrefixRewriter = class {
|
|
93
|
+
constructor(sql, prefix) {
|
|
94
|
+
_classPrivateMethodInitSpec(this, _TablePrefixRewriter_brand);
|
|
95
|
+
_classPrivateFieldInitSpec(this, _sql, void 0);
|
|
96
|
+
_classPrivateFieldInitSpec(this, _prefix, void 0);
|
|
97
|
+
_classPrivateFieldInitSpec(this, _masterView, void 0);
|
|
98
|
+
_classPrivateFieldInitSpec(this, _names, void 0);
|
|
99
|
+
_classPrivateFieldInitSpec(this, _cache, /* @__PURE__ */ new Map());
|
|
100
|
+
this.exec = (query, ...bindings) => _classPrivateFieldGet2(_sql, this).exec(this.rewrite(query), ...bindings);
|
|
101
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(prefix)) throw new Error(`Invalid table prefix ${JSON.stringify(prefix)}`);
|
|
102
|
+
if (prefix.toLowerCase().startsWith("_cf_")) throw new Error("A table prefix must not start with _cf_");
|
|
103
|
+
_classPrivateFieldSet2(_sql, this, sql);
|
|
104
|
+
_classPrivateFieldSet2(_prefix, this, prefix);
|
|
105
|
+
const length = prefix.length;
|
|
106
|
+
_classPrivateFieldSet2(_masterView, this, `(SELECT type, substr(name, ${length + 1}) AS name, substr(tbl_name, ${length + 1}) AS tbl_name, rootpage, sql FROM sqlite_master WHERE substr(name, 1, ${length}) = '${prefix}')`);
|
|
107
|
+
}
|
|
108
|
+
rewrite(query) {
|
|
109
|
+
const cached = _classPrivateFieldGet2(_cache, this).get(query);
|
|
110
|
+
if (cached !== void 0) return cached;
|
|
111
|
+
const names = _assertClassBrand(_TablePrefixRewriter_brand, this, _known$1).call(this);
|
|
112
|
+
const before = names.size;
|
|
113
|
+
const rewritten = _assertClassBrand(_TablePrefixRewriter_brand, this, _rewrite).call(this, tokenize(query), names);
|
|
114
|
+
if (names.size === before) _classPrivateFieldGet2(_cache, this).set(query, rewritten);
|
|
115
|
+
else _classPrivateFieldGet2(_cache, this).clear();
|
|
116
|
+
return rewritten;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
function _known$1() {
|
|
120
|
+
if (_classPrivateFieldGet2(_names, this)) return _classPrivateFieldGet2(_names, this);
|
|
121
|
+
const rows = _classPrivateFieldGet2(_sql, this).exec("SELECT name FROM sqlite_master WHERE substr(name, 1, ?) = ?", _classPrivateFieldGet2(_prefix, this).length, _classPrivateFieldGet2(_prefix, this)).toArray();
|
|
122
|
+
_classPrivateFieldSet2(_names, this, new Set(rows.map((row) => row.name.slice(_classPrivateFieldGet2(_prefix, this).length).toLowerCase())));
|
|
123
|
+
return _classPrivateFieldGet2(_names, this);
|
|
124
|
+
}
|
|
125
|
+
function _rewrite(tokens, names) {
|
|
126
|
+
const significant = tokens.map((token, index) => ({
|
|
127
|
+
token,
|
|
128
|
+
index
|
|
129
|
+
})).filter(({ token }) => token.kind !== "space");
|
|
130
|
+
const out = tokens.map((token) => token.text);
|
|
131
|
+
const firstWord = upper(significant[0]?.token);
|
|
132
|
+
const isCreateIndex = firstWord === "CREATE" && significant.slice(1, 4).some(({ token }) => upper(token) === "INDEX");
|
|
133
|
+
for (let i = 0; i < significant.length; i++) {
|
|
134
|
+
const { token, index } = significant[i];
|
|
135
|
+
if (token.kind !== "word" && token.kind !== "quoted") continue;
|
|
136
|
+
if (token.kind === "word" && NOT_NAMES.has(token.text.toUpperCase())) continue;
|
|
137
|
+
const lower = (token.kind === "word" ? token.text : token.name).toLowerCase();
|
|
138
|
+
const previous = significant[i - 1]?.token;
|
|
139
|
+
const next = significant[i + 1]?.token;
|
|
140
|
+
if (previous?.text === ".") continue;
|
|
141
|
+
const position = tablePosition(significant, i, isCreateIndex);
|
|
142
|
+
if (position === "dml" && MASTER_TABLES.has(lower)) {
|
|
143
|
+
out[index] = upper(next) === "AS" || next !== void 0 && (next.kind === "word" || next.kind === "quoted") && !CLAUSE_KEYWORDS.has(upper(next)) ? _classPrivateFieldGet2(_masterView, this) : `${_classPrivateFieldGet2(_masterView, this)} AS ${token.text}`;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (lower.startsWith("sqlite_")) continue;
|
|
147
|
+
if (position === "ddl") {
|
|
148
|
+
names.add(lower);
|
|
149
|
+
out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (position === "dml" && names.has(lower)) {
|
|
153
|
+
out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (next?.text === "." && significant[i + 2] !== void 0 && names.has(lower)) {
|
|
157
|
+
out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (lower.startsWith("pragma_") && next?.text === "(") {
|
|
161
|
+
const argument = significant[i + 2];
|
|
162
|
+
if (argument?.token.kind === "string") {
|
|
163
|
+
const inner = argument.token.text.slice(1, -1);
|
|
164
|
+
if (names.has(inner.toLowerCase())) out[argument.index] = `'${_classPrivateFieldGet2(_prefix, this)}${inner}'`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (firstWord === "PRAGMA" && previous?.text === "(" && names.has(lower)) out[index] = _assertClassBrand(_TablePrefixRewriter_brand, this, _prefixed).call(this, token);
|
|
168
|
+
}
|
|
169
|
+
const pragma = significant[2]?.token.text === "." ? significant[3]?.token : significant[1]?.token;
|
|
170
|
+
const pragmaName = pragma?.kind === "word" ? pragma.text : pragma?.kind === "quoted" ? pragma.name : "";
|
|
171
|
+
if (firstWord === "PRAGMA" && SCHEMA_PRAGMAS.has(pragmaName.toLowerCase())) for (let i = 1; i < significant.length; i++) {
|
|
172
|
+
const { token, index } = significant[i];
|
|
173
|
+
if (token.kind !== "string" || significant[i - 1]?.token.text !== "(") continue;
|
|
174
|
+
const inner = token.text.slice(1, -1);
|
|
175
|
+
if (names.has(inner.toLowerCase())) out[index] = `'${_classPrivateFieldGet2(_prefix, this)}${inner}'`;
|
|
176
|
+
}
|
|
177
|
+
return out.join("");
|
|
178
|
+
}
|
|
179
|
+
function _prefixed(token) {
|
|
180
|
+
return token.kind === "word" ? `${_classPrivateFieldGet2(_prefix, this)}${token.text}` : `${token.open}${_classPrivateFieldGet2(_prefix, this)}${token.name}${token.close}`;
|
|
181
|
+
}
|
|
182
|
+
/** Whether the identifier at `i` names a table in DDL, DML, or neither. */
|
|
183
|
+
function tablePosition(significant, i, isCreateIndex) {
|
|
184
|
+
let j = i - 1;
|
|
185
|
+
if (upper(significant[j]?.token) === "EXISTS") {
|
|
186
|
+
j -= 1;
|
|
187
|
+
if (upper(significant[j]?.token) === "NOT") j -= 1;
|
|
188
|
+
if (upper(significant[j]?.token) !== "IF") return void 0;
|
|
189
|
+
j -= 1;
|
|
190
|
+
}
|
|
191
|
+
const keyword = upper(significant[j]?.token);
|
|
192
|
+
if (DDL_KEYWORDS.has(keyword)) return "ddl";
|
|
193
|
+
if (keyword === "TO" && upper(significant[j - 1]?.token) === "RENAME") return "ddl";
|
|
194
|
+
if (keyword === "ON" && isCreateIndex) return "ddl";
|
|
195
|
+
if (DML_KEYWORDS.has(keyword)) return "dml";
|
|
196
|
+
}
|
|
197
|
+
function upper(token) {
|
|
198
|
+
return token?.kind === "word" ? token.text.toUpperCase() : "";
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Splits SQL into identifiers, literals, and everything else. Comments are
|
|
202
|
+
* kept as whitespace so positions stay intact.
|
|
203
|
+
*/
|
|
204
|
+
function tokenize(sql) {
|
|
205
|
+
const tokens = [];
|
|
206
|
+
let i = 0;
|
|
207
|
+
while (i < sql.length) {
|
|
208
|
+
const char = sql[i];
|
|
209
|
+
if (/\s/.test(char)) {
|
|
210
|
+
let j = i + 1;
|
|
211
|
+
while (j < sql.length && /\s/.test(sql[j])) j++;
|
|
212
|
+
tokens.push({
|
|
213
|
+
kind: "space",
|
|
214
|
+
text: sql.slice(i, j)
|
|
215
|
+
});
|
|
216
|
+
i = j;
|
|
217
|
+
} else if (char === "-" && sql[i + 1] === "-") {
|
|
218
|
+
const end = sql.indexOf("\n", i);
|
|
219
|
+
const j = end === -1 ? sql.length : end;
|
|
220
|
+
tokens.push({
|
|
221
|
+
kind: "space",
|
|
222
|
+
text: sql.slice(i, j)
|
|
223
|
+
});
|
|
224
|
+
i = j;
|
|
225
|
+
} else if (char === "/" && sql[i + 1] === "*") {
|
|
226
|
+
const end = sql.indexOf("*/", i + 2);
|
|
227
|
+
const j = end === -1 ? sql.length : end + 2;
|
|
228
|
+
tokens.push({
|
|
229
|
+
kind: "space",
|
|
230
|
+
text: sql.slice(i, j)
|
|
231
|
+
});
|
|
232
|
+
i = j;
|
|
233
|
+
} else if (char === "'") {
|
|
234
|
+
const j = closing(sql, i, "'");
|
|
235
|
+
tokens.push({
|
|
236
|
+
kind: "string",
|
|
237
|
+
text: sql.slice(i, j)
|
|
238
|
+
});
|
|
239
|
+
i = j;
|
|
240
|
+
} else if (char === "\"" || char === "`" || char === "[") {
|
|
241
|
+
const close = char === "[" ? "]" : char;
|
|
242
|
+
const j = closing(sql, i, close);
|
|
243
|
+
const text = sql.slice(i, j);
|
|
244
|
+
const name = text.slice(1, -1).replaceAll(close === "]" ? "]]" : close + close, close);
|
|
245
|
+
tokens.push({
|
|
246
|
+
kind: "quoted",
|
|
247
|
+
text,
|
|
248
|
+
name,
|
|
249
|
+
open: char,
|
|
250
|
+
close
|
|
251
|
+
});
|
|
252
|
+
i = j;
|
|
253
|
+
} else if (/[A-Za-z_]/.test(char)) {
|
|
254
|
+
let j = i + 1;
|
|
255
|
+
while (j < sql.length && /[A-Za-z0-9_$]/.test(sql[j])) j++;
|
|
256
|
+
tokens.push({
|
|
257
|
+
kind: "word",
|
|
258
|
+
text: sql.slice(i, j)
|
|
259
|
+
});
|
|
260
|
+
i = j;
|
|
261
|
+
} else {
|
|
262
|
+
tokens.push({
|
|
263
|
+
kind: "punct",
|
|
264
|
+
text: char
|
|
265
|
+
});
|
|
266
|
+
i++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return tokens;
|
|
270
|
+
}
|
|
271
|
+
/** The index just past the quote that closes the one at `start`. */
|
|
272
|
+
function closing(sql, start, close) {
|
|
273
|
+
let j = start + 1;
|
|
274
|
+
while (j < sql.length) {
|
|
275
|
+
if (sql[j] === close) {
|
|
276
|
+
if (close !== "]" && sql[j + 1] === close) {
|
|
277
|
+
j += 2;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
return j + 1;
|
|
281
|
+
}
|
|
282
|
+
j++;
|
|
283
|
+
}
|
|
284
|
+
return sql.length;
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/harness/opencode/harness.ts
|
|
288
|
+
/** The root session's id. OpenCode session ids start with `ses`. */
|
|
289
|
+
const ROOT_SESSION = "ses_root";
|
|
290
|
+
/** Every table and index OpenCode creates is stored under this prefix. */
|
|
291
|
+
const TABLE_PREFIX = "opencode_";
|
|
292
|
+
/**
|
|
293
|
+
* Longest one wake waits on OpenCode. It waits inside an alarm invocation,
|
|
294
|
+
* which has a 15 minute wall-time limit, so a longer run is waited on across
|
|
295
|
+
* several alarms.
|
|
296
|
+
*/
|
|
297
|
+
const WAIT_BUDGET_MS = 10 * 6e4;
|
|
298
|
+
/**
|
|
299
|
+
* The wake job's heartbeat while it waits. If the object is evicted, the job
|
|
300
|
+
* is still due and its alarm restarts the object.
|
|
301
|
+
*/
|
|
302
|
+
const HEARTBEAT_MS = 3e4;
|
|
303
|
+
/**
|
|
304
|
+
* How long OpenCode stays open after the harness last used it. An open
|
|
305
|
+
* OpenCode host runs periodic background work (catalog refreshes, local
|
|
306
|
+
* provider discovery, cleanups) whose timers keep the object in memory, so
|
|
307
|
+
* the harness closes it once nothing is running and reopens it on the next
|
|
308
|
+
* call.
|
|
309
|
+
*/
|
|
310
|
+
const IDLE_CLOSE_MS = 3e4;
|
|
311
|
+
/** Messages read per page when looking an operation up. */
|
|
312
|
+
const MESSAGE_PAGE = 50;
|
|
313
|
+
const WAKE_FN = "wake";
|
|
314
|
+
const CLOSE_FN = "close";
|
|
315
|
+
const CLOSE_JOB_ID = "opencode-close";
|
|
316
|
+
function wakeJobId(session) {
|
|
317
|
+
return `opencode-wake:${session}`;
|
|
318
|
+
}
|
|
319
|
+
function sessionOfJob(payload) {
|
|
320
|
+
return typeof payload === "object" && payload !== null && "session" in payload && typeof payload.session === "string" ? payload.session : void 0;
|
|
321
|
+
}
|
|
322
|
+
const OPERATION_ID = /^[A-Za-z0-9._~-]+$/;
|
|
323
|
+
/** OpenCode's id for the user message an operation submits. */
|
|
324
|
+
function messageIdOf(operationId) {
|
|
325
|
+
if (!OPERATION_ID.test(operationId)) throw new TypeError(`Invalid operation id ${JSON.stringify(operationId)}: use letters, digits, ".", "_", "~" and "-"`);
|
|
326
|
+
return `msg_${operationId}`;
|
|
327
|
+
}
|
|
328
|
+
/** The operation id of a message or inbox item, the inverse of `messageIdOf`. */
|
|
329
|
+
function operationIdOf(messageId) {
|
|
330
|
+
return messageId.startsWith("msg_") ? messageId.slice(4) : messageId;
|
|
331
|
+
}
|
|
332
|
+
/** Whether an OpenCode client error is the tagged error `tag`. */
|
|
333
|
+
function isTagged(error, tag) {
|
|
334
|
+
return typeof error === "object" && error !== null && "_tag" in error && error._tag === tag;
|
|
335
|
+
}
|
|
336
|
+
/** OpenCode's client errors are tagged objects; give them a message. */
|
|
337
|
+
function describe(error) {
|
|
338
|
+
if (error instanceof Error) return error.message;
|
|
339
|
+
if (typeof error === "object" && error !== null) return `${"_tag" in error ? String(error._tag) : "Error"}${"message" in error && typeof error.message === "string" ? `: ${error.message}` : ""}`;
|
|
340
|
+
return String(error);
|
|
341
|
+
}
|
|
342
|
+
function textOf(message) {
|
|
343
|
+
if (message?.type !== "assistant") return "";
|
|
344
|
+
return message.content.map((part) => part.type === "text" ? part.text : "").join("");
|
|
345
|
+
}
|
|
346
|
+
function sessionIdOf(event) {
|
|
347
|
+
if (!("data" in event)) return void 0;
|
|
348
|
+
const data = event.data;
|
|
349
|
+
return typeof data === "object" && data !== null && "sessionID" in data && typeof data.sessionID === "string" ? data.sessionID : void 0;
|
|
350
|
+
}
|
|
351
|
+
var _options = /* @__PURE__ */ new WeakMap();
|
|
352
|
+
var _waits = /* @__PURE__ */ new WeakMap();
|
|
353
|
+
var _admitting = /* @__PURE__ */ new WeakMap();
|
|
354
|
+
var _waitBudgetMs = /* @__PURE__ */ new WeakMap();
|
|
355
|
+
var _heartbeatMs = /* @__PURE__ */ new WeakMap();
|
|
356
|
+
var _idleCloseMs = /* @__PURE__ */ new WeakMap();
|
|
357
|
+
var _leases = /* @__PURE__ */ new WeakMap();
|
|
358
|
+
var _opening = /* @__PURE__ */ new WeakMap();
|
|
359
|
+
var _OpenCodeHarness_brand = /* @__PURE__ */ new WeakSet();
|
|
360
|
+
/**
|
|
361
|
+
* OpenCode v2 hosted in a Durable Object, behind the same harness interface
|
|
362
|
+
* as `PiHarness`: `harness.prompt()`, `harness.sessions`, `harness.session(id)`.
|
|
363
|
+
* How a session reaches a client (sockets, SSE, RPC) is the host's glue,
|
|
364
|
+
* built on `session.events()` and `session.log()`.
|
|
365
|
+
*
|
|
366
|
+
* OpenCode owns everything about a run: the transcript, the inbox of steers
|
|
367
|
+
* and follow-ups, model and tool steps, retries, and crash recovery. It keeps
|
|
368
|
+
* all of it in its own tables in this object's SQLite database, under the
|
|
369
|
+
* `opencode_` prefix.
|
|
370
|
+
*
|
|
371
|
+
* What OpenCode cannot do on a Durable Object is wake itself: it resumes an
|
|
372
|
+
* interrupted turn when it boots, but an evicted object does not boot until
|
|
373
|
+
* something calls it. The harness is that wake, with one Lifecycle job per
|
|
374
|
+
* session. Input goes to OpenCode once, in `submit()`, after the session's
|
|
375
|
+
* job is scheduled. The job waits while the session runs, rescheduling itself
|
|
376
|
+
* as a heartbeat, and completes when the session is idle with nothing left
|
|
377
|
+
* to do. An eviction mid-run leaves the job due, so its alarm restarts the
|
|
378
|
+
* object, OpenCode boots and resumes the turn, and the job waits again.
|
|
379
|
+
*
|
|
380
|
+
* @experimental The API may change between releases.
|
|
381
|
+
*/
|
|
382
|
+
var OpenCodeHarness = class extends LifecycleCapability {
|
|
383
|
+
/**
|
|
384
|
+
* @param options - How to open OpenCode, and what new sessions start with.
|
|
385
|
+
*/
|
|
386
|
+
constructor(options) {
|
|
387
|
+
super("opencode-harness");
|
|
388
|
+
_classPrivateMethodInitSpec(this, _OpenCodeHarness_brand);
|
|
389
|
+
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
390
|
+
_classPrivateFieldInitSpec(this, _waits, /* @__PURE__ */ new Map());
|
|
391
|
+
_classPrivateFieldInitSpec(this, _admitting, /* @__PURE__ */ new Map());
|
|
392
|
+
_classPrivateFieldInitSpec(this, _waitBudgetMs, WAIT_BUDGET_MS);
|
|
393
|
+
_classPrivateFieldInitSpec(this, _heartbeatMs, HEARTBEAT_MS);
|
|
394
|
+
_classPrivateFieldInitSpec(this, _idleCloseMs, IDLE_CLOSE_MS);
|
|
395
|
+
_classPrivateFieldInitSpec(this, _leases, 0);
|
|
396
|
+
_classPrivateFieldInitSpec(this, _opening, void 0);
|
|
397
|
+
_classPrivateFieldSet2(_options, this, options);
|
|
398
|
+
this.sessions = new OpenCodeSessions(this);
|
|
399
|
+
}
|
|
400
|
+
/** Open OpenCode, which resumes interrupted turns, and wake running sessions. */
|
|
401
|
+
async onStart(_context) {
|
|
402
|
+
const opencode = await _assertClassBrand(_OpenCodeHarness_brand, this, _open).call(this);
|
|
403
|
+
for (const session of Object.keys(await opencode.sessions.active())) await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
|
|
404
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _scheduleClose).call(this);
|
|
405
|
+
}
|
|
406
|
+
/** Run one step of a session's wake job. */
|
|
407
|
+
async onJob(context) {
|
|
408
|
+
if (context.job.fn === CLOSE_FN) return _assertClassBrand(_OpenCodeHarness_brand, this, _closeStep).call(this);
|
|
409
|
+
if (context.job.fn !== WAKE_FN) return;
|
|
410
|
+
const session = sessionOfJob(context.job.payload);
|
|
411
|
+
if (session !== void 0) return _assertClassBrand(_OpenCodeHarness_brand, this, _wakeStep).call(this, session);
|
|
412
|
+
}
|
|
413
|
+
/** Close OpenCode's in-memory resources. Durable state is untouched. */
|
|
414
|
+
async dispose() {
|
|
415
|
+
const opening = _classPrivateFieldGet2(_opening, this);
|
|
416
|
+
_classPrivateFieldSet2(_opening, this, void 0);
|
|
417
|
+
await (await opening?.catch(() => void 0))?.close();
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* A handle on one session. No I/O until you call it.
|
|
421
|
+
*
|
|
422
|
+
* @param id - The session; default the root session.
|
|
423
|
+
*/
|
|
424
|
+
session(id = ROOT_SESSION) {
|
|
425
|
+
return new OpenCodeSession(this, id);
|
|
426
|
+
}
|
|
427
|
+
/** Submit a prompt and wait for its answer. */
|
|
428
|
+
prompt(input, options = {}) {
|
|
429
|
+
return this.session(options.session).prompt(input, options);
|
|
430
|
+
}
|
|
431
|
+
/** Durably submit a prompt. Resolves before the model runs. */
|
|
432
|
+
submit(input, options = {}) {
|
|
433
|
+
return this.session(options.session).submit(input, options);
|
|
434
|
+
}
|
|
435
|
+
/** Stop one operation, or everything running in a session. */
|
|
436
|
+
abort(options = {}) {
|
|
437
|
+
return this.session(options.session).abort(options.operationId);
|
|
438
|
+
}
|
|
439
|
+
/** Wait for an operation to settle. Aborting `signal` stops only the wait. */
|
|
440
|
+
wait(operationId, options = {}) {
|
|
441
|
+
return this.session(options.session).wait(operationId, options.signal);
|
|
442
|
+
}
|
|
443
|
+
/** The session's active transcript, as OpenCode's messages. */
|
|
444
|
+
messages(options = {}) {
|
|
445
|
+
return this.session(options.session).messages();
|
|
446
|
+
}
|
|
447
|
+
/** Submissions OpenCode has not settled yet, oldest first. */
|
|
448
|
+
pending(options = {}) {
|
|
449
|
+
return this.use((opencode) => _assertClassBrand(_OpenCodeHarness_brand, this, _pending).call(this, opencode, options.session));
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* The opened OpenCode host, for anything the interface does not cover.
|
|
453
|
+
* The harness closes an idle host, so call this again each time rather
|
|
454
|
+
* than keeping the result; work done through it does not keep it open.
|
|
455
|
+
*/
|
|
456
|
+
opencode() {
|
|
457
|
+
return this.use(async (opencode) => opencode);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* @internal Run `call` with OpenCode open. The host is not closed while
|
|
461
|
+
* any call holds it.
|
|
462
|
+
*/
|
|
463
|
+
async use(call) {
|
|
464
|
+
const release = this.lease();
|
|
465
|
+
try {
|
|
466
|
+
return await call(await _assertClassBrand(_OpenCodeHarness_brand, this, _open).call(this));
|
|
467
|
+
} finally {
|
|
468
|
+
await release();
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* @internal Hold OpenCode open until the returned release is called, for
|
|
473
|
+
* work that outlives one call, such as an event stream.
|
|
474
|
+
*/
|
|
475
|
+
lease() {
|
|
476
|
+
_classPrivateFieldSet2(_leases, this, _classPrivateFieldGet2(_leases, this) + 1);
|
|
477
|
+
let released = false;
|
|
478
|
+
return async () => {
|
|
479
|
+
if (released) return;
|
|
480
|
+
released = true;
|
|
481
|
+
_classPrivateFieldSet2(_leases, this, _classPrivateFieldGet2(_leases, this) - 1);
|
|
482
|
+
if (_classPrivateFieldGet2(_leases, this) === 0) await _assertClassBrand(_OpenCodeHarness_brand, this, _scheduleClose).call(this);
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/** @internal Admit an input to OpenCode once, behind the session's wake. */
|
|
486
|
+
async enqueue(session, input, options) {
|
|
487
|
+
const operationId = options.operationId ?? crypto.randomUUID();
|
|
488
|
+
const messageId = messageIdOf(operationId);
|
|
489
|
+
return this.use(async (opencode) => {
|
|
490
|
+
const content = typeof input === "string" ? { text: input } : input;
|
|
491
|
+
_classPrivateFieldGet2(_admitting, this).set(session, (_classPrivateFieldGet2(_admitting, this).get(session) ?? 0) + 1);
|
|
492
|
+
let accepted;
|
|
493
|
+
try {
|
|
494
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
|
|
495
|
+
accepted = !await _assertClassBrand(_OpenCodeHarness_brand, this, _known).call(this, opencode, session, messageId);
|
|
496
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.prompt({
|
|
497
|
+
...content,
|
|
498
|
+
sessionID: session,
|
|
499
|
+
id: messageId,
|
|
500
|
+
delivery: options.whenBusy === "steer" ? "steer" : "queue",
|
|
501
|
+
resume: true
|
|
502
|
+
}));
|
|
503
|
+
} finally {
|
|
504
|
+
const left = (_classPrivateFieldGet2(_admitting, this).get(session) ?? 1) - 1;
|
|
505
|
+
if (left === 0) _classPrivateFieldGet2(_admitting, this).delete(session);
|
|
506
|
+
else _classPrivateFieldGet2(_admitting, this).set(session, left);
|
|
507
|
+
}
|
|
508
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
|
|
509
|
+
return {
|
|
510
|
+
operationId,
|
|
511
|
+
session,
|
|
512
|
+
accepted
|
|
513
|
+
};
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/** @internal Withdraw a queued input, or interrupt the run it joined. */
|
|
517
|
+
async withdraw(session, operationId) {
|
|
518
|
+
const messageId = messageIdOf(operationId);
|
|
519
|
+
return this.use(async (opencode) => {
|
|
520
|
+
switch ((await _assertClassBrand(_OpenCodeHarness_brand, this, _state).call(this, opencode, session, messageId))._tag) {
|
|
521
|
+
case "queued":
|
|
522
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.cancel({
|
|
523
|
+
sessionID: session,
|
|
524
|
+
inboxID: messageId
|
|
525
|
+
}));
|
|
526
|
+
return true;
|
|
527
|
+
case "running": return (await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.interrupt({ sessionID: session }))).interrupted;
|
|
528
|
+
case "missing":
|
|
529
|
+
case "settled": return false;
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
/** @internal Withdraw every queued input and interrupt the running work. */
|
|
534
|
+
interrupt(session) {
|
|
535
|
+
return this.use(async (opencode) => {
|
|
536
|
+
for (const item of await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.list({ sessionID: session }))) await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.cancel({
|
|
537
|
+
sessionID: session,
|
|
538
|
+
inboxID: item.id
|
|
539
|
+
}));
|
|
540
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.interrupt({ sessionID: session }));
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/** @internal Wait for OpenCode to settle an operation, by its id. */
|
|
544
|
+
async settled(session, operationId, signal) {
|
|
545
|
+
const messageId = messageIdOf(operationId);
|
|
546
|
+
return this.use(async (opencode) => {
|
|
547
|
+
for (;;) {
|
|
548
|
+
signal?.throwIfAborted();
|
|
549
|
+
const state = await _assertClassBrand(_OpenCodeHarness_brand, this, _state).call(this, opencode, session, messageId);
|
|
550
|
+
switch (state._tag) {
|
|
551
|
+
case "missing": return {
|
|
552
|
+
operationId,
|
|
553
|
+
session,
|
|
554
|
+
status: "unanswered",
|
|
555
|
+
reason: "not_found"
|
|
556
|
+
};
|
|
557
|
+
case "settled": return state.outcome === "succeeded" ? {
|
|
558
|
+
operationId,
|
|
559
|
+
session,
|
|
560
|
+
status: "done",
|
|
561
|
+
text: state.text
|
|
562
|
+
} : {
|
|
563
|
+
operationId,
|
|
564
|
+
session,
|
|
565
|
+
status: "unanswered",
|
|
566
|
+
reason: state.outcome === "failed" ? state.error ?? "failed" : "interrupted"
|
|
567
|
+
};
|
|
568
|
+
case "queued":
|
|
569
|
+
case "running":
|
|
570
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.wait({ sessionID: session }, signal ? { signal } : void 0));
|
|
571
|
+
if ((await opencode.sessions.active())[session] === void 0) {
|
|
572
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
|
|
573
|
+
await delay(_classPrivateFieldGet2(_heartbeatMs, this) / 30, signal);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
/** @internal Create a session with the harness defaults. */
|
|
580
|
+
async create(options = {}) {
|
|
581
|
+
return this.use((opencode) => _assertClassBrand(_OpenCodeHarness_brand, this, _createSession).call(this, opencode, options.id));
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
async function _pending(opencode, only) {
|
|
585
|
+
const sessions = only === void 0 ? (await listSessions(opencode)).map((info) => info.id) : [only];
|
|
586
|
+
const active = await opencode.sessions.active();
|
|
587
|
+
const pending = [];
|
|
588
|
+
for (const session of sessions) {
|
|
589
|
+
if (active[session] !== void 0 || await unsettled(opencode, session)) for (const operationId of await _assertClassBrand(_OpenCodeHarness_brand, this, _running).call(this, opencode, session)) pending.push({
|
|
590
|
+
operationId,
|
|
591
|
+
session,
|
|
592
|
+
status: "running"
|
|
593
|
+
});
|
|
594
|
+
for (const item of await opencode.sessions.inbox.list({ sessionID: session })) pending.push({
|
|
595
|
+
operationId: operationIdOf(item.id),
|
|
596
|
+
session,
|
|
597
|
+
status: "queued"
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
return pending;
|
|
601
|
+
}
|
|
602
|
+
/** Call OpenCode, turning its tagged errors into `Error`s. */
|
|
603
|
+
async function _call(call) {
|
|
604
|
+
try {
|
|
605
|
+
return await call();
|
|
606
|
+
} catch (error) {
|
|
607
|
+
if (error instanceof Error) throw error;
|
|
608
|
+
throw new Error(`OpenCode: ${describe(error)}`, { cause: error });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
/** The session's info, or undefined when it does not exist. */
|
|
612
|
+
async function _info(opencode, session) {
|
|
613
|
+
try {
|
|
614
|
+
return await opencode.sessions.get({ sessionID: session });
|
|
615
|
+
} catch (error) {
|
|
616
|
+
if (isTagged(error, "SessionNotFoundError")) return void 0;
|
|
617
|
+
throw new Error(`OpenCode: ${describe(error)}`, { cause: error });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function _createSession(opencode, id) {
|
|
621
|
+
const defaults = _classPrivateFieldGet2(_options, this).defaults;
|
|
622
|
+
return (await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.create({
|
|
623
|
+
...id === void 0 ? {} : { id },
|
|
624
|
+
...defaults?.model === void 0 ? {} : { model: {
|
|
625
|
+
providerID: defaults.model.providerID,
|
|
626
|
+
id: defaults.model.id
|
|
627
|
+
} },
|
|
628
|
+
...defaults?.agent === void 0 ? {} : { agent: defaults.agent }
|
|
629
|
+
}))).id;
|
|
630
|
+
}
|
|
631
|
+
/** Schedule the session's wake job now, or pull it forward. */
|
|
632
|
+
function _wake(session, time = Date.now()) {
|
|
633
|
+
return this.lifecycle.jobs.push({
|
|
634
|
+
id: wakeJobId(session),
|
|
635
|
+
fn: WAKE_FN,
|
|
636
|
+
time,
|
|
637
|
+
payload: { session },
|
|
638
|
+
singleflight: true,
|
|
639
|
+
recoveryLoop: true
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* One run of a session's wake job. It never admits anything new: it waits
|
|
644
|
+
* while the session runs and completes when it is idle with nothing left
|
|
645
|
+
* to do. OpenCode does all the work in between.
|
|
646
|
+
*/
|
|
647
|
+
async function _wakeStep(session) {
|
|
648
|
+
const heartbeat = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_heartbeatMs, this) };
|
|
649
|
+
if (_classPrivateFieldGet2(_waits, this).has(session)) return heartbeat;
|
|
650
|
+
const opencode = await _assertClassBrand(_OpenCodeHarness_brand, this, _open).call(this);
|
|
651
|
+
if (await _assertClassBrand(_OpenCodeHarness_brand, this, _info).call(this, opencode, session) === void 0) return void 0;
|
|
652
|
+
const inbox = await opencode.sessions.inbox.list({ sessionID: session });
|
|
653
|
+
const open = await unsettled(opencode, session);
|
|
654
|
+
if (!((await opencode.sessions.active())[session] !== void 0)) {
|
|
655
|
+
const first = inbox.find((item) => item.type !== "move");
|
|
656
|
+
if (first !== void 0) await _assertClassBrand(_OpenCodeHarness_brand, this, _ring).call(this, opencode, session, first);
|
|
657
|
+
else if (open) return heartbeat;
|
|
658
|
+
else return _classPrivateFieldGet2(_admitting, this).has(session) ? heartbeat : void 0;
|
|
659
|
+
}
|
|
660
|
+
const wait = _assertClassBrand(_OpenCodeHarness_brand, this, _waitForIdle).call(this, opencode, session).finally(() => {
|
|
661
|
+
_classPrivateFieldGet2(_waits, this).delete(session);
|
|
662
|
+
_assertClassBrand(_OpenCodeHarness_brand, this, _wake).call(this, session);
|
|
663
|
+
});
|
|
664
|
+
_classPrivateFieldGet2(_waits, this).set(session, wait);
|
|
665
|
+
this.lifecycle.trackAlarmWork(wait);
|
|
666
|
+
return heartbeat;
|
|
667
|
+
}
|
|
668
|
+
async function _waitForIdle(opencode, session) {
|
|
669
|
+
const budget = new AbortController();
|
|
670
|
+
const timer = setTimeout(() => budget.abort(), _classPrivateFieldGet2(_waitBudgetMs, this));
|
|
671
|
+
try {
|
|
672
|
+
await opencode.sessions.wait({ sessionID: session }, { signal: budget.signal });
|
|
673
|
+
} catch (error) {
|
|
674
|
+
if (!budget.signal.aborted) this.lifecycle.events.emit("opencode:wake_error", {
|
|
675
|
+
session,
|
|
676
|
+
error: describe(error)
|
|
677
|
+
});
|
|
678
|
+
} finally {
|
|
679
|
+
clearTimeout(timer);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/** Re-admit an inbox item by its id, which starts the session again. */
|
|
683
|
+
async function _ring(opencode, session, item) {
|
|
684
|
+
switch (item.type) {
|
|
685
|
+
case "user":
|
|
686
|
+
await opencode.sessions.prompt({
|
|
687
|
+
text: item.payload.text,
|
|
688
|
+
sessionID: session,
|
|
689
|
+
id: item.id,
|
|
690
|
+
delivery: item.delivery,
|
|
691
|
+
resume: true
|
|
692
|
+
});
|
|
693
|
+
return;
|
|
694
|
+
case "synthetic":
|
|
695
|
+
await opencode.sessions.synthetic({
|
|
696
|
+
text: item.payload.text,
|
|
697
|
+
sessionID: session,
|
|
698
|
+
id: item.id,
|
|
699
|
+
delivery: item.delivery,
|
|
700
|
+
resume: true
|
|
701
|
+
});
|
|
702
|
+
return;
|
|
703
|
+
case "compaction":
|
|
704
|
+
await opencode.sessions.compact({
|
|
705
|
+
sessionID: session,
|
|
706
|
+
id: item.id,
|
|
707
|
+
delivery: item.delivery
|
|
708
|
+
});
|
|
709
|
+
return;
|
|
710
|
+
case "move": return;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/** Schedule the idle close, unless one is already due late enough. */
|
|
714
|
+
async function _scheduleClose() {
|
|
715
|
+
const time = Date.now() + _classPrivateFieldGet2(_idleCloseMs, this);
|
|
716
|
+
const pending = this.lifecycle.jobs.get(CLOSE_JOB_ID);
|
|
717
|
+
if (pending !== void 0 && pending.time >= time - _classPrivateFieldGet2(_idleCloseMs, this) / 2) return;
|
|
718
|
+
await this.lifecycle.jobs.push({
|
|
719
|
+
id: CLOSE_JOB_ID,
|
|
720
|
+
fn: CLOSE_FN,
|
|
721
|
+
time,
|
|
722
|
+
payload: {},
|
|
723
|
+
singleflight: true
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
/** Close OpenCode if nothing is using it or running in it. */
|
|
727
|
+
async function _closeStep() {
|
|
728
|
+
const opening = _classPrivateFieldGet2(_opening, this);
|
|
729
|
+
if (opening === void 0) return void 0;
|
|
730
|
+
const later = { rescheduleAt: Date.now() + _classPrivateFieldGet2(_idleCloseMs, this) };
|
|
731
|
+
const busy = () => _classPrivateFieldGet2(_leases, this) > 0 || _classPrivateFieldGet2(_waits, this).size > 0 || _classPrivateFieldGet2(_admitting, this).size > 0 || this.lifecycle.jobs.list().some((job) => job.fn === WAKE_FN);
|
|
732
|
+
if (busy()) return later;
|
|
733
|
+
const opencode = await opening.catch(() => void 0);
|
|
734
|
+
if (opencode === void 0) return void 0;
|
|
735
|
+
if (Object.keys(await opencode.sessions.active()).length > 0) return later;
|
|
736
|
+
if (busy() || _classPrivateFieldGet2(_opening, this) !== opening) return later;
|
|
737
|
+
_classPrivateFieldSet2(_opening, this, void 0);
|
|
738
|
+
await opencode.close();
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* OpenCode, opened. Waits for Lifecycle startup first, so it is only ever
|
|
742
|
+
* opened inside it: startup holds the input gate (`blockConcurrencyWhile`)
|
|
743
|
+
* and awaits the open in `onStart`, so an open begun before startup would
|
|
744
|
+
* be awaited behind a closed gate and never finish.
|
|
745
|
+
*/
|
|
746
|
+
async function _open() {
|
|
747
|
+
await this.lifecycle.ready();
|
|
748
|
+
_classPrivateFieldGet2(_opening, this) ?? _classPrivateFieldSet2(_opening, this, _assertClassBrand(_OpenCodeHarness_brand, this, _doOpen).call(this).catch((error) => {
|
|
749
|
+
_classPrivateFieldSet2(_opening, this, void 0);
|
|
750
|
+
throw error;
|
|
751
|
+
}));
|
|
752
|
+
return _classPrivateFieldGet2(_opening, this);
|
|
753
|
+
}
|
|
754
|
+
async function _doOpen() {
|
|
755
|
+
const storage = prefixTables(this.lifecycle.storage, TABLE_PREFIX);
|
|
756
|
+
const opencode = await _classPrivateFieldGet2(_options, this).opencode({ storage });
|
|
757
|
+
try {
|
|
758
|
+
await opencode.sessions.get({ sessionID: ROOT_SESSION });
|
|
759
|
+
} catch (error) {
|
|
760
|
+
if (!isTagged(error, "SessionNotFoundError")) {
|
|
761
|
+
await opencode.close();
|
|
762
|
+
throw new Error(`OpenCode: ${describe(error)}`, { cause: error });
|
|
763
|
+
}
|
|
764
|
+
await _assertClassBrand(_OpenCodeHarness_brand, this, _createSession).call(this, opencode, ROOT_SESSION);
|
|
765
|
+
}
|
|
766
|
+
return opencode;
|
|
767
|
+
}
|
|
768
|
+
/** Whether OpenCode has the message, as an inbox item or in the transcript. */
|
|
769
|
+
async function _known(opencode, session, messageId) {
|
|
770
|
+
return (await _assertClassBrand(_OpenCodeHarness_brand, this, _state).call(this, opencode, session, messageId))._tag !== "missing";
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Where an operation is. Reads the transcript newest first, back to the
|
|
774
|
+
* operation's message, then finds the first idle marker after it: the end
|
|
775
|
+
* of the run that answered it.
|
|
776
|
+
*/
|
|
777
|
+
async function _state(opencode, session, messageId) {
|
|
778
|
+
if ((await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.sessions.inbox.list({ sessionID: session }))).some((item) => item.id === messageId)) return { _tag: "queued" };
|
|
779
|
+
const after = await _assertClassBrand(_OpenCodeHarness_brand, this, _messagesAfter).call(this, opencode, session, messageId);
|
|
780
|
+
if (after === void 0) return { _tag: "missing" };
|
|
781
|
+
let last;
|
|
782
|
+
for (const message of after) {
|
|
783
|
+
if (message.type === "idle") return settledState(message.outcome, last);
|
|
784
|
+
if (message.type === "user" && last !== void 0) return settledState("succeeded", last);
|
|
785
|
+
if (message.type === "assistant") last = message;
|
|
786
|
+
}
|
|
787
|
+
return { _tag: "running" };
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* The messages after `messageId`, oldest first, or undefined when the
|
|
791
|
+
* transcript has no such message.
|
|
792
|
+
*/
|
|
793
|
+
async function _messagesAfter(opencode, session, messageId) {
|
|
794
|
+
const newer = [];
|
|
795
|
+
let cursor;
|
|
796
|
+
for (;;) {
|
|
797
|
+
const page = await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.message.list(cursor === void 0 ? {
|
|
798
|
+
sessionID: session,
|
|
799
|
+
order: "desc",
|
|
800
|
+
limit: MESSAGE_PAGE
|
|
801
|
+
} : {
|
|
802
|
+
sessionID: session,
|
|
803
|
+
cursor
|
|
804
|
+
}));
|
|
805
|
+
for (const message of page.data) {
|
|
806
|
+
if (message.id === messageId) return newer.reverse();
|
|
807
|
+
newer.push(message);
|
|
808
|
+
}
|
|
809
|
+
const next = page.cursor.next;
|
|
810
|
+
if (next === void 0 || next === null) return void 0;
|
|
811
|
+
cursor = next;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Operations the running turn is answering: the newest user messages, back
|
|
816
|
+
* to the assistant message or idle marker before them. Earlier inputs of
|
|
817
|
+
* the same run already have their answer.
|
|
818
|
+
*/
|
|
819
|
+
async function _running(opencode, session) {
|
|
820
|
+
const running = [];
|
|
821
|
+
let cursor;
|
|
822
|
+
for (;;) {
|
|
823
|
+
const page = await _assertClassBrand(_OpenCodeHarness_brand, this, _call).call(this, () => opencode.message.list(cursor === void 0 ? {
|
|
824
|
+
sessionID: session,
|
|
825
|
+
order: "desc",
|
|
826
|
+
limit: MESSAGE_PAGE
|
|
827
|
+
} : {
|
|
828
|
+
sessionID: session,
|
|
829
|
+
cursor
|
|
830
|
+
}));
|
|
831
|
+
for (const message of page.data) if (message.type === "user") running.push(operationIdOf(message.id));
|
|
832
|
+
else if (message.type === "idle" || message.type === "assistant" && running.length > 0) return running.reverse();
|
|
833
|
+
const next = page.cursor.next;
|
|
834
|
+
if (next === void 0 || next === null) return running.reverse();
|
|
835
|
+
cursor = next;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Whether the transcript ends in a turn with no idle marker after it: a run
|
|
840
|
+
* in progress, or one a restart cut off that OpenCode resumes on boot.
|
|
841
|
+
*/
|
|
842
|
+
async function unsettled(opencode, session) {
|
|
843
|
+
let cursor;
|
|
844
|
+
for (;;) {
|
|
845
|
+
const page = await opencode.message.list(cursor === void 0 ? {
|
|
846
|
+
sessionID: session,
|
|
847
|
+
order: "desc",
|
|
848
|
+
limit: MESSAGE_PAGE
|
|
849
|
+
} : {
|
|
850
|
+
sessionID: session,
|
|
851
|
+
cursor
|
|
852
|
+
});
|
|
853
|
+
for (const message of page.data) {
|
|
854
|
+
if (message.type === "idle") return false;
|
|
855
|
+
if (message.type === "user" || message.type === "synthetic") return true;
|
|
856
|
+
}
|
|
857
|
+
const next = page.cursor.next;
|
|
858
|
+
if (next === void 0 || next === null) return false;
|
|
859
|
+
cursor = next;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Whether the session has work: running, input waiting in its inbox, or a
|
|
864
|
+
* turn a restart cut off, which OpenCode resumes in the background on boot
|
|
865
|
+
* and so is not running yet.
|
|
866
|
+
*/
|
|
867
|
+
async function sessionBusy(opencode, session, active) {
|
|
868
|
+
if (active[session] !== void 0) return true;
|
|
869
|
+
return (await opencode.sessions.inbox.list({ sessionID: session })).length > 0 || await unsettled(opencode, session);
|
|
870
|
+
}
|
|
871
|
+
/** Every session, with whether it has work. */
|
|
872
|
+
async function listSessions(opencode) {
|
|
873
|
+
const active = await opencode.sessions.active();
|
|
874
|
+
const sessions = [];
|
|
875
|
+
let cursor;
|
|
876
|
+
for (;;) {
|
|
877
|
+
const page = await opencode.sessions.list(cursor === void 0 ? {} : { cursor });
|
|
878
|
+
for (const info of page.data) {
|
|
879
|
+
const parent = info.fork?.sessionID ?? info.parentID;
|
|
880
|
+
sessions.push({
|
|
881
|
+
id: info.id,
|
|
882
|
+
...parent === void 0 ? {} : { parent },
|
|
883
|
+
...info.title === void 0 ? {} : { title: info.title },
|
|
884
|
+
busy: await sessionBusy(opencode, info.id, active)
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
const next = page.cursor.next;
|
|
888
|
+
if (next === void 0 || next === null) return sessions;
|
|
889
|
+
cursor = next;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/** An operation's end, from the last assistant message of its run. */
|
|
893
|
+
function settledState(outcome, last) {
|
|
894
|
+
const error = last?.type === "assistant" ? last.error?.message : void 0;
|
|
895
|
+
return {
|
|
896
|
+
_tag: "settled",
|
|
897
|
+
outcome: error !== void 0 && outcome === "succeeded" ? "failed" : outcome,
|
|
898
|
+
text: textOf(last),
|
|
899
|
+
error
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
/** Resolve after `ms`, or reject when `signal` aborts. */
|
|
903
|
+
function delay(ms, signal) {
|
|
904
|
+
return new Promise((resolve, reject) => {
|
|
905
|
+
const timer = setTimeout(resolve, ms);
|
|
906
|
+
signal?.addEventListener("abort", () => {
|
|
907
|
+
clearTimeout(timer);
|
|
908
|
+
reject(signal.reason);
|
|
909
|
+
}, { once: true });
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
var _harness = /* @__PURE__ */ new WeakMap();
|
|
913
|
+
/** One OpenCode session, addressed through the harness. */
|
|
914
|
+
var OpenCodeSession = class {
|
|
915
|
+
/**
|
|
916
|
+
* @param harness - The harness that hosts the session.
|
|
917
|
+
* @param id - The session's id.
|
|
918
|
+
*/
|
|
919
|
+
constructor(harness, id) {
|
|
920
|
+
_classPrivateFieldInitSpec(this, _harness, void 0);
|
|
921
|
+
_classPrivateFieldSet2(_harness, this, harness);
|
|
922
|
+
this.id = id;
|
|
923
|
+
}
|
|
924
|
+
/** Durably submit a prompt. Resolves before the model runs. */
|
|
925
|
+
submit(input, options = {}) {
|
|
926
|
+
return _classPrivateFieldGet2(_harness, this).enqueue(this.id, input, options);
|
|
927
|
+
}
|
|
928
|
+
/** Submit and wait for the answer and the updated transcript. */
|
|
929
|
+
async prompt(input, options = {}) {
|
|
930
|
+
const receipt = await this.submit(input, options);
|
|
931
|
+
return {
|
|
932
|
+
...await this.wait(receipt.operationId),
|
|
933
|
+
messages: await this.messages()
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
/** Join the running work at its next step. */
|
|
937
|
+
steer(input, options = {}) {
|
|
938
|
+
return this.submit(input, {
|
|
939
|
+
...options,
|
|
940
|
+
whenBusy: "steer"
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
/** Wait for an operation to settle. Aborting `signal` stops only the wait. */
|
|
944
|
+
wait(operationId, signal) {
|
|
945
|
+
return _classPrivateFieldGet2(_harness, this).settled(this.id, operationId, signal);
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Withdraw one queued operation (or interrupt the run it joined), or, with
|
|
949
|
+
* no id, abort the session: OpenCode's queued inputs are withdrawn and the
|
|
950
|
+
* running work is interrupted.
|
|
951
|
+
*/
|
|
952
|
+
async abort(operationId) {
|
|
953
|
+
if (operationId !== void 0) return _classPrivateFieldGet2(_harness, this).withdraw(this.id, operationId);
|
|
954
|
+
await _classPrivateFieldGet2(_harness, this).interrupt(this.id);
|
|
955
|
+
return true;
|
|
956
|
+
}
|
|
957
|
+
/** Change this session's model; `agents/models/opencode` makes them. */
|
|
958
|
+
async setModel(model) {
|
|
959
|
+
await _classPrivateFieldGet2(_harness, this).use((opencode) => opencode.sessions.switchModel({
|
|
960
|
+
sessionID: this.id,
|
|
961
|
+
model: {
|
|
962
|
+
providerID: model.providerID,
|
|
963
|
+
id: model.id
|
|
964
|
+
}
|
|
965
|
+
}));
|
|
966
|
+
}
|
|
967
|
+
/** Change this session's OpenCode agent, such as `build` or `plan`. */
|
|
968
|
+
async setAgent(agent) {
|
|
969
|
+
await _classPrivateFieldGet2(_harness, this).use((opencode) => opencode.sessions.switchAgent({
|
|
970
|
+
sessionID: this.id,
|
|
971
|
+
agent
|
|
972
|
+
}));
|
|
973
|
+
}
|
|
974
|
+
/** The active transcript: OpenCode's messages since the newest compaction. */
|
|
975
|
+
async messages() {
|
|
976
|
+
return _classPrivateFieldGet2(_harness, this).use((opencode) => opencode.sessions.context({ sessionID: this.id }));
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* The whole transcript, oldest first, compacted messages included.
|
|
980
|
+
* OpenCode compacts a long session on its own; `messages()` is what the
|
|
981
|
+
* model sees after that, this is everything the session has said.
|
|
982
|
+
*/
|
|
983
|
+
history() {
|
|
984
|
+
return _classPrivateFieldGet2(_harness, this).use(async (opencode) => {
|
|
985
|
+
const messages = [];
|
|
986
|
+
let cursor;
|
|
987
|
+
for (;;) {
|
|
988
|
+
const page = await opencode.message.list(cursor === void 0 ? {
|
|
989
|
+
sessionID: this.id,
|
|
990
|
+
order: "asc",
|
|
991
|
+
limit: 100
|
|
992
|
+
} : {
|
|
993
|
+
sessionID: this.id,
|
|
994
|
+
cursor
|
|
995
|
+
});
|
|
996
|
+
messages.push(...page.data);
|
|
997
|
+
const next = page.cursor.next;
|
|
998
|
+
if (next === void 0 || next === null) return messages;
|
|
999
|
+
cursor = next;
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* OpenCode's events for this session as they happen, durable and live
|
|
1005
|
+
* (text deltas, tool input), until `signal` aborts. Read the transcript
|
|
1006
|
+
* with `messages()` first for a snapshot; use `log()` to resume from a
|
|
1007
|
+
* durable position instead.
|
|
1008
|
+
*/
|
|
1009
|
+
async *events(signal) {
|
|
1010
|
+
const release = _classPrivateFieldGet2(_harness, this).lease();
|
|
1011
|
+
try {
|
|
1012
|
+
const opencode = await _classPrivateFieldGet2(_harness, this).opencode();
|
|
1013
|
+
for await (const event of opencode.events.subscribe(signal ? { signal } : {})) if (sessionIdOf(event) === this.id) yield event;
|
|
1014
|
+
} finally {
|
|
1015
|
+
await release();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* The session's durable events after sequence number `after` (from the
|
|
1020
|
+
* start by default), then, with `follow`, new ones as they are committed,
|
|
1021
|
+
* until `signal` aborts.
|
|
1022
|
+
*/
|
|
1023
|
+
async *log(options = {}) {
|
|
1024
|
+
const release = _classPrivateFieldGet2(_harness, this).lease();
|
|
1025
|
+
try {
|
|
1026
|
+
yield* (await _classPrivateFieldGet2(_harness, this).opencode()).sessions.log({
|
|
1027
|
+
sessionID: this.id,
|
|
1028
|
+
...options.after === void 0 ? {} : { after: options.after },
|
|
1029
|
+
...options.follow === void 0 ? {} : { follow: options.follow }
|
|
1030
|
+
}, options.signal ? { signal: options.signal } : void 0);
|
|
1031
|
+
} finally {
|
|
1032
|
+
await release();
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Whether the session has work: running, input waiting, or a turn a
|
|
1037
|
+
* restart cut off that OpenCode is about to resume.
|
|
1038
|
+
*/
|
|
1039
|
+
async busy() {
|
|
1040
|
+
return _classPrivateFieldGet2(_harness, this).use(async (opencode) => sessionBusy(opencode, this.id, await opencode.sessions.active()));
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
var _harness2 = /* @__PURE__ */ new WeakMap();
|
|
1044
|
+
/** Every OpenCode session in this object. */
|
|
1045
|
+
var OpenCodeSessions = class {
|
|
1046
|
+
/** @param harness - The harness that hosts the sessions. */
|
|
1047
|
+
constructor(harness) {
|
|
1048
|
+
_classPrivateFieldInitSpec(this, _harness2, void 0);
|
|
1049
|
+
_classPrivateFieldSet2(_harness2, this, harness);
|
|
1050
|
+
}
|
|
1051
|
+
/** A handle on one session. No I/O until you call it. */
|
|
1052
|
+
get(id) {
|
|
1053
|
+
return _classPrivateFieldGet2(_harness2, this).session(id);
|
|
1054
|
+
}
|
|
1055
|
+
/** A new top-level session, configured like the root. */
|
|
1056
|
+
async create() {
|
|
1057
|
+
return _classPrivateFieldGet2(_harness2, this).session(await _classPrivateFieldGet2(_harness2, this).create());
|
|
1058
|
+
}
|
|
1059
|
+
/** A new session that sees `from`'s history up to now. */
|
|
1060
|
+
async fork(from) {
|
|
1061
|
+
const fork = await _classPrivateFieldGet2(_harness2, this).use((opencode) => opencode.sessions.fork({ sessionID: from }));
|
|
1062
|
+
return _classPrivateFieldGet2(_harness2, this).session(fork.id);
|
|
1063
|
+
}
|
|
1064
|
+
/** Every session, including subagent sessions OpenCode spawned. */
|
|
1065
|
+
list() {
|
|
1066
|
+
return _classPrivateFieldGet2(_harness2, this).use(listSessions);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
//#endregion
|
|
1070
|
+
export { OpenCodeHarness, OpenCodeSession, OpenCodeSessions, ROOT_SESSION };
|
|
1071
|
+
|
|
1072
|
+
//# sourceMappingURL=index.js.map
|