@junejs/server 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icon-latin.LICENSE.txt +92 -0
- package/assets/icon-latin.ttf +0 -0
- package/dist/adapter-D0UnYBuN.d.ts +64 -0
- package/dist/adapter.d.ts +2 -56
- package/dist/adapter.js +107 -6
- package/dist/agent-compile.d.ts +38 -0
- package/dist/agent-compile.js +145 -0
- package/dist/agent-discover.d.ts +4 -4
- package/dist/agent-discover.js +30 -44
- package/dist/agent-durable.d.ts +94 -19
- package/dist/agent-durable.js +647 -32
- package/dist/agent-native.d.ts +44 -13
- package/dist/agent-native.js +264 -27
- package/dist/app.d.ts +5 -5
- package/dist/app.js +42 -10
- package/dist/assets.d.ts +23 -0
- package/dist/assets.js +123 -0
- package/dist/blob.d.ts +4 -5
- package/dist/build.d.ts +8 -30
- package/dist/build.js +143 -298
- package/dist/client-bundle.d.ts +8 -9
- package/dist/client-bundle.js +1 -1
- package/dist/config-freeze-r5p-vTWv.d.ts +17 -0
- package/dist/config-freeze.d.ts +2 -0
- package/dist/config-freeze.js +54 -0
- package/dist/config-loader.d.ts +3 -4
- package/dist/connection-auth.d.ts +38 -0
- package/dist/connection-auth.js +31 -0
- package/dist/content-freeze-CyiW2fRK.d.ts +4 -0
- package/dist/content-freeze.d.ts +2 -0
- package/dist/content-freeze.js +83 -0
- package/dist/content.d.ts +22 -9
- package/dist/content.js +43 -3
- package/dist/core-version.d.ts +4 -0
- package/dist/core-version.js +12 -0
- package/dist/css-modules-ambient.d.ts +2 -1
- package/dist/css-modules.d.ts +10 -11
- package/dist/css.d.ts +9 -10
- package/dist/d1-migrate.d.ts +6 -7
- package/dist/d1-migrate.js +3 -2
- package/dist/db.d.ts +2 -2
- package/dist/deploy.d.ts +3 -4
- package/dist/deploy.js +3 -2
- package/dist/dev-reload.d.ts +3 -4
- package/dist/dev.d.ts +8 -5
- package/dist/dev.js +40 -9
- package/dist/favicon.d.ts +37 -0
- package/dist/favicon.js +248 -0
- package/dist/host.d.ts +6 -5
- package/dist/host.js +2 -1
- package/dist/icon-letter.d.ts +3 -0
- package/dist/icon-letter.js +8 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +9 -3
- package/dist/instrumentation.d.ts +2 -3
- package/dist/instrumentation.js +2 -1
- package/dist/island-registry.d.ts +6 -7
- package/dist/isolate-local.d.ts +5 -0
- package/dist/isolate-local.js +12 -0
- package/dist/kv.d.ts +3 -4
- package/dist/llms-links.d.ts +9 -0
- package/dist/llms-links.js +65 -0
- package/dist/manifest-CwTT1zVs.d.ts +11 -0
- package/dist/manifest.d.ts +2 -0
- package/dist/manifest.js +113 -0
- package/dist/negotiate.d.ts +4 -3
- package/dist/negotiate.js +24 -14
- package/dist/{pipeline-CCp9idQ4.d.ts → pipeline-BHUjBO0G.d.ts} +13 -1
- package/dist/pipeline.d.ts +2 -2
- package/dist/pipeline.js +286 -35
- package/dist/resources.d.ts +5 -6
- package/dist/route-rank.d.ts +6 -7
- package/dist/route-scan-BMbBzPIB.d.ts +20 -0
- package/dist/route-scan.d.ts +2 -0
- package/dist/route-scan.js +100 -0
- package/dist/router.d.ts +12 -13
- package/dist/router.js +51 -38
- package/dist/rsc-build.d.ts +6 -7
- package/dist/rsc-bundle.d.ts +6 -7
- package/dist/rsc-manifest.d.ts +5 -6
- package/dist/rsc-runtime/flight-render-routes.d.ts +3 -4
- package/dist/rsc-runtime/flight-render.d.ts +2 -3
- package/dist/rsc-runtime/flight-to-html.d.ts +2 -3
- package/dist/rsc-runtime/flight-to-html.js +3 -2
- package/dist/rsc-runtime/rsc-worker.js +11 -8
- package/dist/rsc-runtime/rsdw-shims.d.ts +2 -1
- package/dist/segment.d.ts +4 -5
- package/dist/sqlite-driver.d.ts +11 -11
- package/dist/sqlite-driver.js +17 -4
- package/dist/static-files.d.ts +4 -5
- package/dist/tsconfig-jsx.d.ts +4 -4
- package/dist/tsconfig-jsx.js +1 -1
- package/dist/turn-events.d.ts +4 -0
- package/dist/turn-events.js +46 -0
- package/dist/web-manifest.d.ts +5 -0
- package/dist/web-manifest.js +42 -0
- package/dist/worker-2qbQjj2c.d.ts +64 -0
- package/dist/worker.d.ts +2 -57
- package/dist/worker.js +69 -27
- package/package.json +28 -9
- package/src/adapter.ts +149 -4
- package/src/agent-compile.ts +271 -0
- package/src/agent-discover.ts +77 -63
- package/src/agent-durable.ts +998 -30
- package/src/agent-native.ts +370 -35
- package/src/app.ts +75 -6
- package/src/assets.ts +200 -0
- package/src/build.ts +241 -449
- package/src/config-freeze.ts +109 -0
- package/src/connection-auth.ts +101 -0
- package/src/content-freeze.ts +151 -0
- package/src/content.ts +55 -4
- package/src/core-version.ts +34 -0
- package/src/dev.ts +41 -8
- package/src/favicon.ts +325 -0
- package/src/host.ts +4 -3
- package/src/icon-letter.ts +14 -0
- package/src/index.ts +22 -0
- package/src/isolate-local.ts +33 -0
- package/src/llms-links.ts +96 -0
- package/src/manifest.ts +130 -0
- package/src/negotiate.ts +39 -15
- package/src/pipeline.ts +494 -44
- package/src/route-scan.ts +161 -0
- package/src/router.ts +66 -54
- package/src/rsc-runtime/rsc-worker.tsx +5 -4
- package/src/sqlite-driver.ts +36 -6
- package/src/tsconfig-jsx.ts +2 -2
- package/src/turn-events.ts +43 -0
- package/src/web-manifest.ts +38 -0
- package/src/worker.ts +131 -35
package/dist/agent-durable.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { observeTurnEvents } from "./turn-events.js";
|
|
2
|
+
import { isolateLocal } from "./isolate-local.js";
|
|
3
|
+
import { assertCoreRuntimeVersion } from "./core-version.js";
|
|
4
|
+
import { DeliverUnsupportedError, actionToTool, channelDispatch, resolveChannel } from "@junejs/core/agent-config";
|
|
5
|
+
import { AgentSession, FatalToolError, ResumeAuthorizationError, grantAnswer, withSystem } from "@junejs/core/agent-runtime";
|
|
6
|
+
import { blobFileStore } from "@junejs/core/agent-files";
|
|
7
|
+
import { connectAll } from "@junejs/core/connections";
|
|
3
8
|
import { ensureScope, runInScope } from "@junejs/db";
|
|
4
9
|
//#region src/agent-durable.ts
|
|
5
10
|
var DoSessionStore = class {
|
|
@@ -20,8 +25,8 @@ var DoSessionStore = class {
|
|
|
20
25
|
messages() {
|
|
21
26
|
return this.sql.exec("SELECT body FROM agent_messages ORDER BY seq").toArray().map((r) => JSON.parse(r.body));
|
|
22
27
|
}
|
|
23
|
-
|
|
24
|
-
return this.messages().some((m) => m.role === "user" && m.turnId === turnId);
|
|
28
|
+
hasOpeningMessage(turnId) {
|
|
29
|
+
return this.messages().some((m) => (m.role === "user" || m.role === "trigger") && m.turnId === turnId);
|
|
25
30
|
}
|
|
26
31
|
getStep(id) {
|
|
27
32
|
const rows = this.sql.exec("SELECT output FROM agent_steps WHERE id = ?", id).toArray();
|
|
@@ -30,6 +35,9 @@ var DoSessionStore = class {
|
|
|
30
35
|
putStep(id, output) {
|
|
31
36
|
this.sql.exec("INSERT INTO agent_steps (id, output) VALUES (?, ?)", id, JSON.stringify(output));
|
|
32
37
|
}
|
|
38
|
+
delStep(id) {
|
|
39
|
+
this.sql.exec("DELETE FROM agent_steps WHERE id = ?", id);
|
|
40
|
+
}
|
|
33
41
|
getStatus() {
|
|
34
42
|
const rows = this.sql.exec("SELECT v FROM agent_meta WHERE k = 'status'").toArray();
|
|
35
43
|
return rows.length ? rows[0].v : "new";
|
|
@@ -37,19 +45,43 @@ var DoSessionStore = class {
|
|
|
37
45
|
setStatus(s) {
|
|
38
46
|
this.sql.exec("INSERT INTO agent_meta (k, v) VALUES ('status', ?) ON CONFLICT(k) DO UPDATE SET v = ?", s, s);
|
|
39
47
|
}
|
|
48
|
+
getSessionKey() {
|
|
49
|
+
const rows = this.sql.exec("SELECT v FROM agent_meta WHERE k = 'session_key'").toArray();
|
|
50
|
+
return rows.length ? rows[0].v : void 0;
|
|
51
|
+
}
|
|
52
|
+
setSessionKey(key) {
|
|
53
|
+
this.sql.exec("INSERT INTO agent_meta (k, v) VALUES ('session_key', ?) ON CONFLICT(k) DO UPDATE SET v = ?", key, key);
|
|
54
|
+
}
|
|
40
55
|
tx(fn) {
|
|
41
56
|
return this.storage.transactionSync(fn);
|
|
42
57
|
}
|
|
58
|
+
reset(inTx) {
|
|
59
|
+
return this.storage.transactionSync(() => {
|
|
60
|
+
const sql = this.sql;
|
|
61
|
+
sql.exec(`CREATE TABLE IF NOT EXISTS agent_messages_archive (generation INTEGER, seq INTEGER, body TEXT)`);
|
|
62
|
+
sql.exec(`CREATE TABLE IF NOT EXISTS agent_steps_archive (generation INTEGER, id TEXT, output TEXT)`);
|
|
63
|
+
const rows = this.sql.exec("SELECT v FROM agent_meta WHERE k = 'generation'").toArray();
|
|
64
|
+
const generation = rows.length ? Number(rows[0].v) : 0;
|
|
65
|
+
sql.exec("INSERT INTO agent_messages_archive (generation, seq, body) SELECT ?, seq, body FROM agent_messages", generation);
|
|
66
|
+
sql.exec("DELETE FROM agent_messages");
|
|
67
|
+
sql.exec("INSERT INTO agent_steps_archive (generation, id, output) SELECT ?, id, output FROM agent_steps", generation);
|
|
68
|
+
sql.exec("DELETE FROM agent_steps");
|
|
69
|
+
this.setStatus("new");
|
|
70
|
+
sql.exec("INSERT INTO agent_meta (k, v) VALUES ('generation', ?) ON CONFLICT(k) DO UPDATE SET v = ?", String(generation + 1), String(generation + 1));
|
|
71
|
+
inTx?.();
|
|
72
|
+
return generation;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
43
75
|
unwrap() {
|
|
44
76
|
return this.sql;
|
|
45
77
|
}
|
|
46
78
|
};
|
|
47
|
-
var
|
|
79
|
+
var InProcEventSink = class {
|
|
48
80
|
subs = /* @__PURE__ */ new Set();
|
|
49
|
-
|
|
81
|
+
emit(e) {
|
|
50
82
|
this.subs.forEach((cb) => {
|
|
51
83
|
try {
|
|
52
|
-
cb(
|
|
84
|
+
cb(e);
|
|
53
85
|
} catch {}
|
|
54
86
|
});
|
|
55
87
|
}
|
|
@@ -63,40 +95,493 @@ const crossDoUnsupported = { session() {
|
|
|
63
95
|
} };
|
|
64
96
|
var AgentDurableObject = class {
|
|
65
97
|
session;
|
|
66
|
-
|
|
98
|
+
sessionKey;
|
|
99
|
+
name;
|
|
100
|
+
store;
|
|
101
|
+
sink;
|
|
102
|
+
model;
|
|
103
|
+
tools;
|
|
104
|
+
channels;
|
|
105
|
+
channelInstructions;
|
|
106
|
+
resourcesInput;
|
|
107
|
+
resourcesOpened;
|
|
108
|
+
connectionsInput;
|
|
109
|
+
connectionsOpened;
|
|
110
|
+
doEnv;
|
|
67
111
|
services;
|
|
112
|
+
authorizeAnswer;
|
|
113
|
+
onInputAnnouncement;
|
|
114
|
+
storage;
|
|
115
|
+
files;
|
|
116
|
+
announceFailures = 0;
|
|
117
|
+
resolveResources() {
|
|
118
|
+
return this.resourcesOpened ??= Promise.resolve(typeof this.resourcesInput === "function" ? this.resourcesInput(this.doEnv) : this.resourcesInput ?? {});
|
|
119
|
+
}
|
|
120
|
+
resolveConnections() {
|
|
121
|
+
return this.connectionsOpened ??= (async () => {
|
|
122
|
+
if (!this.connectionsInput?.length) return;
|
|
123
|
+
const { actions, report } = await connectAll(this.connectionsInput);
|
|
124
|
+
for (const r of report) if (r.error) console.error(`[june] agent "${this.name}": connection "${r.name}" (${r.url}) failed: ${r.error}`);
|
|
125
|
+
const seen = new Set(this.tools.map((t) => t.spec.name));
|
|
126
|
+
for (const a of actions) {
|
|
127
|
+
const tool = actionToTool(a);
|
|
128
|
+
if (seen.has(tool.spec.name)) throw new Error(`AgentDurableObject(${this.name}): duplicate tool name "${tool.spec.name}" from a connection — rename so dispatch is unambiguous.`);
|
|
129
|
+
seen.add(tool.spec.name);
|
|
130
|
+
this.tools.push(tool);
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
133
|
+
}
|
|
134
|
+
async ready() {
|
|
135
|
+
const [resources] = await Promise.all([this.resolveResources(), this.resolveConnections()]);
|
|
136
|
+
return resources;
|
|
137
|
+
}
|
|
68
138
|
constructor(state, def) {
|
|
139
|
+
assertCoreRuntimeVersion(`AgentDurableObject(${def.name ?? "agent"})`);
|
|
69
140
|
const store = new DoSessionStore(state.storage);
|
|
70
141
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
71
|
-
this.
|
|
142
|
+
this.resourcesInput = def.resources;
|
|
143
|
+
this.connectionsInput = def.connections;
|
|
144
|
+
this.doEnv = def.env;
|
|
72
145
|
this.services = def.services;
|
|
73
|
-
this.
|
|
146
|
+
this.authorizeAnswer = def.authorizeAnswer;
|
|
147
|
+
this.onInputAnnouncement = def.onInputAnnouncement;
|
|
148
|
+
this.storage = state.storage;
|
|
149
|
+
const name = def.name ?? "agent";
|
|
150
|
+
this.files = def.files ?? blobFileStore(async () => {
|
|
151
|
+
const blob = (await this.resolveResources()).blob;
|
|
152
|
+
if (!blob) throw new FatalToolError(`agent "${name}": no file store — declare a blob resource (DoAgentDef.resources) or pass DoAgentDef.files`);
|
|
153
|
+
return blob;
|
|
154
|
+
});
|
|
155
|
+
const sink = new InProcEventSink();
|
|
156
|
+
sink.subscribe((e) => {
|
|
157
|
+
if (e.type === "turn.started") this.armWatchdog();
|
|
158
|
+
});
|
|
159
|
+
sink.subscribe((e) => {
|
|
160
|
+
if (e.type !== "turn.failed") return;
|
|
161
|
+
const defaultLog = () => console.error(`[june] agent "${name}" turn ${e.turnId} failed${e.step ? ` at ${e.step}` : ""}: ${e.error.stack ?? e.error.message}${e.error.causeChain?.length ? `\ncaused by: ${e.error.causeChain.join(" ← ")}` : ""}`);
|
|
162
|
+
if (def.onTurnError) try {
|
|
163
|
+
const out = def.onTurnError({
|
|
164
|
+
turnId: e.turnId,
|
|
165
|
+
error: e.error,
|
|
166
|
+
phase: e.phase,
|
|
167
|
+
step: e.step
|
|
168
|
+
});
|
|
169
|
+
if (out && typeof out.then === "function") out.then(void 0, (hookErr) => {
|
|
170
|
+
console.error(`[june] agent "${name}": onTurnError hook rejected:`, hookErr);
|
|
171
|
+
defaultLog();
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
} catch (hookErr) {
|
|
175
|
+
console.error(`[june] agent "${name}": onTurnError hook threw:`, hookErr);
|
|
176
|
+
}
|
|
177
|
+
defaultLog();
|
|
178
|
+
});
|
|
179
|
+
const channels = (def.channels ?? []).map((c) => resolveChannel(c, def.env));
|
|
180
|
+
const channelTools = channels.flatMap((c) => c.tools?.() ?? []);
|
|
181
|
+
const tools = [...def.tools];
|
|
182
|
+
const seen = new Set(tools.map((t) => t.spec.name));
|
|
183
|
+
for (const t of channelTools) {
|
|
184
|
+
if (seen.has(t.spec.name)) throw new Error(`AgentDurableObject(${def.name ?? "agent"}): duplicate tool name "${t.spec.name}" from a channel — rename so dispatch is unambiguous.`);
|
|
185
|
+
seen.add(t.spec.name);
|
|
186
|
+
tools.push(t);
|
|
187
|
+
}
|
|
188
|
+
this.name = name;
|
|
189
|
+
this.store = store;
|
|
190
|
+
this.sink = sink;
|
|
191
|
+
this.model = model;
|
|
192
|
+
this.tools = tools;
|
|
193
|
+
this.channels = channels;
|
|
194
|
+
this.channelInstructions = def.channelInstructions;
|
|
195
|
+
if (def.channelInstructions) {
|
|
196
|
+
const names = new Set(channels.map((c) => c.name));
|
|
197
|
+
for (const source of Object.keys(def.channelInstructions)) if (!names.has(source)) console.warn(`[june] agent "${name}": surface policy "${source}" matches no channel wired into this DO (${[...names].join(", ") || "none"}) — it will never fire.`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
resolveSession(key) {
|
|
201
|
+
if (key !== void 0) assertSessionKey(key);
|
|
202
|
+
if (this.session) {
|
|
203
|
+
if (key !== void 0 && this.sessionKey !== key) throw new Error(this.sessionKey === "self" ? `agent "${this.name}": request session "${key}" arrived, but this session is bound to the placeholder "self" (first used without a real key) — carry the key on every path (route through durableFetch, or pass turn({ session }) on the direct API)` : `agent "${this.name}": request session "${key}" does not match this object's session "${this.sessionKey}" — one DO is one session; address each session by its own key (durableFetch, or turn({ session }) on the direct API)`);
|
|
204
|
+
return this.session;
|
|
205
|
+
}
|
|
206
|
+
const stored = this.store.getSessionKey();
|
|
207
|
+
if (key !== void 0 && stored !== void 0 && key !== stored) throw new Error(`agent "${this.name}": request session "${key}" does not match this object's session "${stored}" — one DO is one session; address each session by its own key (durableFetch, or turn({ session }) on the direct API)`);
|
|
208
|
+
const resolved = key ?? stored ?? "self";
|
|
209
|
+
if (key !== void 0 && key !== "self" && stored === void 0) this.store.setSessionKey(key);
|
|
210
|
+
this.sessionKey = resolved;
|
|
211
|
+
this.session = new AgentSession(this.name, resolved, this.store, this.sink, this.model, this.tools, crossDoUnsupported, this.channelInstructions);
|
|
212
|
+
this.session.files = this.files;
|
|
213
|
+
this.session.onDequeue = (heldTurnId, held) => {
|
|
214
|
+
if (held.hostContext?.deliver && held.event) this.deliverHeld(this.session, heldTurnId, held.event);
|
|
215
|
+
};
|
|
216
|
+
const announce = this.onInputAnnouncement;
|
|
217
|
+
if (announce) {
|
|
218
|
+
this.session.onAnnounce = async (a) => {
|
|
219
|
+
await ensureScope();
|
|
220
|
+
const resources = await this.ready();
|
|
221
|
+
try {
|
|
222
|
+
await runInScope({
|
|
223
|
+
resources,
|
|
224
|
+
services: this.services
|
|
225
|
+
}, () => announce(a));
|
|
226
|
+
} catch (err) {
|
|
227
|
+
this.scheduleAlarm(Math.min(5e3 * 2 ** this.announceFailures, 3e5));
|
|
228
|
+
this.announceFailures++;
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
this.announceFailures = 0;
|
|
232
|
+
};
|
|
233
|
+
this.session.flushAnnouncements();
|
|
234
|
+
}
|
|
235
|
+
return this.session;
|
|
236
|
+
}
|
|
237
|
+
async alarm() {
|
|
238
|
+
if (!this.onInputAnnouncement) return;
|
|
239
|
+
await this.resolveSession().flushAnnouncements();
|
|
240
|
+
}
|
|
241
|
+
armWatchdog() {
|
|
242
|
+
if (this.onInputAnnouncement) this.scheduleAlarm(6e4);
|
|
243
|
+
}
|
|
244
|
+
scheduleAlarm(inMs) {
|
|
245
|
+
const failed = (err) => console.error(`[june] agent "${this.name}": could not schedule the announcement alarm — delivery waits for the next announcement, turn or rebuild:`, err);
|
|
246
|
+
try {
|
|
247
|
+
Promise.resolve(this.storage.setAlarm?.(Date.now() + inMs)).catch(failed);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
failed(err);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
deliverHeld(session, turnId, event) {
|
|
253
|
+
const logRenderFailure = (err) => console.error(`[june] agent "${this.name}": delivered render for held turn ${turnId} failed:`, err);
|
|
254
|
+
try {
|
|
255
|
+
const channel = this.channels.find((c) => c.name === event.source);
|
|
256
|
+
if (!channel?.deliver) {
|
|
257
|
+
logRenderFailure(/* @__PURE__ */ new Error(`no deliver()-capable channel named "${event.source}" is wired into this DO — the held turn runs, but its reply is not rendered`));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const it = observeTurnEvents(session, turnId)[Symbol.asyncIterator]();
|
|
261
|
+
const events = { [Symbol.asyncIterator]: () => it };
|
|
262
|
+
const target = {
|
|
263
|
+
channelId: event.channelId,
|
|
264
|
+
threadId: event.threadId,
|
|
265
|
+
recipientUserId: event.user?.id,
|
|
266
|
+
recipientTeamId: event.teamId
|
|
267
|
+
};
|
|
268
|
+
this.ready().then((resources) => runInScope({
|
|
269
|
+
resources,
|
|
270
|
+
services: this.services
|
|
271
|
+
}, () => channel.deliver(target, events, {
|
|
272
|
+
session: this.sessionKey,
|
|
273
|
+
files: this.files
|
|
274
|
+
}))).catch(logRenderFailure).finally(() => {
|
|
275
|
+
it.return?.();
|
|
276
|
+
});
|
|
277
|
+
} catch (err) {
|
|
278
|
+
logRenderFailure(err);
|
|
279
|
+
}
|
|
74
280
|
}
|
|
75
281
|
async turn(input) {
|
|
76
282
|
await ensureScope();
|
|
283
|
+
const resources = await this.ready();
|
|
284
|
+
const session = this.resolveSession(input.session);
|
|
77
285
|
return runInScope({
|
|
78
|
-
resources
|
|
286
|
+
resources,
|
|
79
287
|
services: this.services
|
|
80
|
-
}, () =>
|
|
288
|
+
}, () => session.turn(input));
|
|
289
|
+
}
|
|
290
|
+
async start(input) {
|
|
291
|
+
await ensureScope();
|
|
292
|
+
const resources = await this.ready();
|
|
293
|
+
const session = this.resolveSession(input.session);
|
|
294
|
+
return runInScope({
|
|
295
|
+
resources,
|
|
296
|
+
services: this.services
|
|
297
|
+
}, () => session.start(input));
|
|
298
|
+
}
|
|
299
|
+
note(input) {
|
|
300
|
+
const { session, ...note } = input;
|
|
301
|
+
return this.resolveSession(session).note(note);
|
|
302
|
+
}
|
|
303
|
+
reset(opts) {
|
|
304
|
+
const session = this.resolveSession(opts?.session);
|
|
305
|
+
this.armWatchdog();
|
|
306
|
+
return session.reset();
|
|
81
307
|
}
|
|
82
308
|
transcript() {
|
|
83
|
-
return this.session.transcript();
|
|
309
|
+
if (this.session) return this.session.transcript();
|
|
310
|
+
if (this.store.getSessionKey() !== void 0) return this.resolveSession().transcript();
|
|
311
|
+
return new AgentSession(this.name, "self", this.store, this.sink, this.model, this.tools, crossDoUnsupported, this.channelInstructions).transcript();
|
|
84
312
|
}
|
|
85
313
|
async fetch(req) {
|
|
86
314
|
const url = new URL(req.url);
|
|
315
|
+
const key = req.headers.get("x-june-session") ?? void 0;
|
|
87
316
|
if (req.method === "POST" && url.pathname.endsWith("/turn")) {
|
|
88
|
-
const { userText, turnId } = await req.json();
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
317
|
+
const { userText, turnId, event, trigger, ifSuspended } = await req.json();
|
|
318
|
+
await ensureScope();
|
|
319
|
+
const replace = url.searchParams.get("replace") === "1";
|
|
320
|
+
const wantsDeliver = url.searchParams.get("deliver") === "1";
|
|
321
|
+
const detached = url.searchParams.get("detach") === "1";
|
|
322
|
+
if (ifSuspended === "queue" && !wantsDeliver && !detached) return Response.json({ error: `ifSuspended "queue" needs deliver=1 or detach=1: a held turn runs later, when no one is streaming it` }, { status: 400 });
|
|
323
|
+
if (ifSuspended === "queue" && replace) return Response.json({ error: `ifSuspended "queue" and replace=1 cannot be combined — replacing would drop held turns that were accepted` }, { status: 400 });
|
|
324
|
+
let deliverChannel;
|
|
325
|
+
if (wantsDeliver) {
|
|
326
|
+
if (!event?.channelId) return Response.json({ error: "deliver=1 needs an inbound event (event.channelId names the reply target)" }, { status: 400 });
|
|
327
|
+
deliverChannel = this.channels.find((c) => c.name === event.source);
|
|
328
|
+
if (!deliverChannel?.deliver) return Response.json({ error: `agent "${this.name}": no deliver()-capable channel named "${event.source}" is wired into this DO (DoAgentDef.channels) — the turn was NOT started` }, { status: 501 });
|
|
329
|
+
}
|
|
330
|
+
const resources = await this.ready();
|
|
331
|
+
let session;
|
|
332
|
+
let started;
|
|
333
|
+
try {
|
|
334
|
+
session = this.resolveSession(key);
|
|
335
|
+
this.armWatchdog();
|
|
336
|
+
started = runInScope({
|
|
337
|
+
resources,
|
|
338
|
+
services: this.services
|
|
339
|
+
}, () => session.start({
|
|
340
|
+
userText,
|
|
341
|
+
turnId,
|
|
342
|
+
event,
|
|
343
|
+
trigger,
|
|
344
|
+
replace,
|
|
345
|
+
ifSuspended,
|
|
346
|
+
hostContext: wantsDeliver ? { deliver: true } : void 0
|
|
347
|
+
}));
|
|
348
|
+
} catch (err) {
|
|
349
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
350
|
+
}
|
|
351
|
+
if (started.queued) return Response.json({
|
|
352
|
+
turnId: started.turnId,
|
|
353
|
+
queued: true
|
|
354
|
+
}, { status: 202 });
|
|
355
|
+
if (wantsDeliver) {
|
|
356
|
+
const logRenderFailure = (err) => console.error(`[june] agent "${this.name}": delivered render for turn ${started.turnId} failed:`, err);
|
|
357
|
+
try {
|
|
358
|
+
const it = observeTurnEvents(session, started.turnId)[Symbol.asyncIterator]();
|
|
359
|
+
const events = { [Symbol.asyncIterator]: () => it };
|
|
360
|
+
const target = {
|
|
361
|
+
channelId: event.channelId,
|
|
362
|
+
threadId: event.threadId,
|
|
363
|
+
recipientUserId: event.user?.id,
|
|
364
|
+
recipientTeamId: event.teamId
|
|
365
|
+
};
|
|
366
|
+
Promise.resolve().then(() => runInScope({
|
|
367
|
+
resources,
|
|
368
|
+
services: this.services
|
|
369
|
+
}, () => deliverChannel.deliver(target, events, {
|
|
370
|
+
session: this.sessionKey,
|
|
371
|
+
files: this.files
|
|
372
|
+
}))).catch(logRenderFailure).finally(() => {
|
|
373
|
+
it.return?.();
|
|
374
|
+
});
|
|
375
|
+
} catch (err) {
|
|
376
|
+
logRenderFailure(err);
|
|
377
|
+
}
|
|
378
|
+
return Response.json({ turnId: started.turnId }, { status: 202 });
|
|
379
|
+
}
|
|
380
|
+
if (detached) return Response.json({ turnId: started.turnId }, { status: 202 });
|
|
381
|
+
return new Response(sseTurnStream(session, started.turnId), { headers: SSE_HEADERS });
|
|
93
382
|
}
|
|
94
|
-
if (url.pathname.endsWith("/
|
|
95
|
-
|
|
383
|
+
if (req.method === "POST" && url.pathname.endsWith("/resume")) {
|
|
384
|
+
const { turnId, inputId, input, by, principal, source, target } = await req.json();
|
|
385
|
+
await ensureScope();
|
|
386
|
+
const wantsDeliver = url.searchParams.get("deliver") === "1";
|
|
387
|
+
let resumeChannel;
|
|
388
|
+
if (wantsDeliver) {
|
|
389
|
+
if (!source || !target?.channelId || !target.messageTs) return Response.json({ error: "resume deliver=1 needs `source` (the channel name) and `target` (channelId + messageTs of the prompt message)" }, { status: 400 });
|
|
390
|
+
resumeChannel = this.channels.find((c) => c.name === source);
|
|
391
|
+
if (!resumeChannel?.deliverResume) return Response.json({ error: `agent "${this.name}": no deliverResume()-capable channel named "${source}" is wired into this DO (DoAgentDef.channels) — the answer was NOT applied` }, { status: 501 });
|
|
392
|
+
}
|
|
393
|
+
const resources = await this.ready();
|
|
394
|
+
let session;
|
|
395
|
+
try {
|
|
396
|
+
session = this.resolveSession(key);
|
|
397
|
+
} catch (err) {
|
|
398
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
399
|
+
}
|
|
400
|
+
let granted;
|
|
401
|
+
try {
|
|
402
|
+
granted = await runInScope({
|
|
403
|
+
resources,
|
|
404
|
+
services: this.services
|
|
405
|
+
}, () => grantAnswer(session, {
|
|
406
|
+
turnId,
|
|
407
|
+
inputId,
|
|
408
|
+
by,
|
|
409
|
+
principal
|
|
410
|
+
}, this.authorizeAnswer));
|
|
411
|
+
} catch (err) {
|
|
412
|
+
console.error(`[june] agent "${this.name}": authorizeAnswer failed for input "${inputId}" of turn ${turnId}:`, err);
|
|
413
|
+
return Response.json({ error: `authorizeAnswer failed: ${err instanceof Error ? err.message : String(err)} — the answer was NOT applied` }, { status: 500 });
|
|
414
|
+
}
|
|
415
|
+
this.armWatchdog();
|
|
416
|
+
try {
|
|
417
|
+
runInScope({
|
|
418
|
+
resources,
|
|
419
|
+
services: this.services
|
|
420
|
+
}, () => session.resume(turnId, inputId, input, {
|
|
421
|
+
by,
|
|
422
|
+
granted
|
|
423
|
+
}));
|
|
424
|
+
} catch (err) {
|
|
425
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
426
|
+
return Response.json({ error: message }, { status: err instanceof ResumeAuthorizationError ? 403 : 409 });
|
|
427
|
+
}
|
|
428
|
+
if (wantsDeliver) {
|
|
429
|
+
const logRenderFailure = (err) => console.error(`[june] agent "${this.name}": delivered resume render for turn ${turnId} failed:`, err);
|
|
430
|
+
try {
|
|
431
|
+
const it = observeTurnEvents(session, turnId)[Symbol.asyncIterator]();
|
|
432
|
+
const events = { [Symbol.asyncIterator]: () => it };
|
|
433
|
+
Promise.resolve().then(() => runInScope({
|
|
434
|
+
resources,
|
|
435
|
+
services: this.services
|
|
436
|
+
}, () => resumeChannel.deliverResume(target, events, {
|
|
437
|
+
session: this.sessionKey,
|
|
438
|
+
files: this.files
|
|
439
|
+
}))).catch(logRenderFailure).finally(() => {
|
|
440
|
+
it.return?.();
|
|
441
|
+
});
|
|
442
|
+
} catch (err) {
|
|
443
|
+
logRenderFailure(err);
|
|
444
|
+
}
|
|
445
|
+
return Response.json({ turnId }, { status: 202 });
|
|
446
|
+
}
|
|
447
|
+
return new Response(sseTurnStream(session, turnId), { headers: SSE_HEADERS });
|
|
448
|
+
}
|
|
449
|
+
if (req.method === "POST" && url.pathname.endsWith("/reset")) {
|
|
450
|
+
let session;
|
|
451
|
+
try {
|
|
452
|
+
session = this.resolveSession(key);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
455
|
+
}
|
|
456
|
+
this.armWatchdog();
|
|
457
|
+
try {
|
|
458
|
+
return Response.json(await session.reset());
|
|
459
|
+
} catch (err) {
|
|
460
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (req.method === "POST" && url.pathname.endsWith("/note")) {
|
|
464
|
+
let input;
|
|
465
|
+
try {
|
|
466
|
+
input = await req.json();
|
|
467
|
+
} catch {
|
|
468
|
+
return Response.json({ error: "note: the body must be JSON { by, kind, text }" }, { status: 400 });
|
|
469
|
+
}
|
|
470
|
+
let session;
|
|
471
|
+
try {
|
|
472
|
+
session = this.resolveSession(key);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
return Response.json(await session.note(input));
|
|
478
|
+
} catch (err) {
|
|
479
|
+
if (err instanceof TypeError) return Response.json({ error: err.message }, { status: 400 });
|
|
480
|
+
throw err;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (url.pathname.endsWith("/transcript")) {
|
|
484
|
+
if (key !== void 0) try {
|
|
485
|
+
return Response.json({ transcript: this.resolveSession(key).transcript() });
|
|
486
|
+
} catch (err) {
|
|
487
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
488
|
+
}
|
|
489
|
+
return Response.json({ transcript: this.transcript() });
|
|
490
|
+
}
|
|
491
|
+
return new Response("agent DO — POST /turn, POST /resume, POST /reset, POST /note, or GET /transcript", { status: 404 });
|
|
96
492
|
}
|
|
97
493
|
};
|
|
494
|
+
const SSE_HEADERS = {
|
|
495
|
+
"content-type": "text/event-stream",
|
|
496
|
+
"cache-control": "no-store"
|
|
497
|
+
};
|
|
498
|
+
function sseTurnStream(session, turnId) {
|
|
499
|
+
const enc = new TextEncoder();
|
|
500
|
+
let unsub;
|
|
501
|
+
let heartbeat;
|
|
502
|
+
const stop = () => {
|
|
503
|
+
unsub?.();
|
|
504
|
+
clearInterval(heartbeat);
|
|
505
|
+
};
|
|
506
|
+
return new ReadableStream({
|
|
507
|
+
start(controller) {
|
|
508
|
+
heartbeat = setInterval(() => {
|
|
509
|
+
try {
|
|
510
|
+
controller.enqueue(enc.encode(":hb\n\n"));
|
|
511
|
+
} catch {
|
|
512
|
+
clearInterval(heartbeat);
|
|
513
|
+
}
|
|
514
|
+
}, 2e4);
|
|
515
|
+
unsub = session.observe((e) => {
|
|
516
|
+
controller.enqueue(enc.encode(`data: ${JSON.stringify(e)}\n\n`));
|
|
517
|
+
if (e.type === "turn.completed" || e.type === "turn.failed" || e.type === "turn.cancelled" || e.type === "input.requested") {
|
|
518
|
+
stop();
|
|
519
|
+
controller.close();
|
|
520
|
+
}
|
|
521
|
+
}, { turnId });
|
|
522
|
+
},
|
|
523
|
+
cancel() {
|
|
524
|
+
stop();
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
async function sseTurnFinalText(res) {
|
|
529
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
530
|
+
if (!res.body || !ct.includes("text/event-stream")) {
|
|
531
|
+
const detail = res.body ? (await res.text()).slice(0, 200) : "no body";
|
|
532
|
+
throw new Error(`turn stream: expected an SSE response, got ${ct || "no content-type"} (status ${res.status}): ${detail}`);
|
|
533
|
+
}
|
|
534
|
+
const reader = res.body.getReader();
|
|
535
|
+
const dec = new TextDecoder();
|
|
536
|
+
let buf = "";
|
|
537
|
+
for (;;) {
|
|
538
|
+
const { done, value } = await reader.read();
|
|
539
|
+
if (value) buf += dec.decode(value, { stream: true });
|
|
540
|
+
let i;
|
|
541
|
+
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
542
|
+
const line = buf.slice(0, i).split("\n").find((l) => l.startsWith("data:"));
|
|
543
|
+
buf = buf.slice(i + 2);
|
|
544
|
+
if (!line) continue;
|
|
545
|
+
const e = JSON.parse(line.slice(5).trim());
|
|
546
|
+
if (e.type === "turn.completed") return e.text;
|
|
547
|
+
if (e.type === "turn.failed") throw new Error(e.error.message, { cause: e.error });
|
|
548
|
+
if (e.type === "turn.cancelled") throw new Error(`turn ${e.turnId} was cancelled (${e.reason})`);
|
|
549
|
+
}
|
|
550
|
+
if (done) break;
|
|
551
|
+
}
|
|
552
|
+
throw new Error("turn stream ended without a terminal event");
|
|
553
|
+
}
|
|
554
|
+
async function* sseTurnEvents(res) {
|
|
555
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
556
|
+
if (!res.body || !ct.includes("text/event-stream")) {
|
|
557
|
+
const detail = res.body ? (await res.text()).slice(0, 200) : "no body";
|
|
558
|
+
throw new Error(`turn stream: expected an SSE response, got ${ct || "no content-type"} (status ${res.status}): ${detail}`);
|
|
559
|
+
}
|
|
560
|
+
const reader = res.body.getReader();
|
|
561
|
+
const dec = new TextDecoder();
|
|
562
|
+
let buf = "";
|
|
563
|
+
for (;;) {
|
|
564
|
+
const { done, value } = await reader.read();
|
|
565
|
+
if (value) buf += dec.decode(value, { stream: true });
|
|
566
|
+
let i;
|
|
567
|
+
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
568
|
+
const line = buf.slice(0, i).split("\n").find((l) => l.startsWith("data:"));
|
|
569
|
+
buf = buf.slice(i + 2);
|
|
570
|
+
if (line) yield JSON.parse(line.slice(5).trim());
|
|
571
|
+
}
|
|
572
|
+
if (done) break;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const SESSION_HEADER = "x-june-session";
|
|
576
|
+
const SESSION_KEY_RE = /^[\x20-\x7E]+$/;
|
|
577
|
+
function assertSessionKey(session) {
|
|
578
|
+
if (typeof session !== "string" || !SESSION_KEY_RE.test(session)) throw new Error(`invalid session key ${JSON.stringify(session)} — a session key must be a non-empty printable-ASCII string (it rides an HTTP header and the DO name)`);
|
|
579
|
+
}
|
|
98
580
|
function durableFetch(namespace, agent, session, req) {
|
|
99
|
-
|
|
581
|
+
assertSessionKey(session);
|
|
582
|
+
const headers = new Headers(req.headers);
|
|
583
|
+
headers.set(SESSION_HEADER, session);
|
|
584
|
+
return namespace.get(namespace.idFromName(`${agent}:${session}`)).fetch(new Request(req, { headers }));
|
|
100
585
|
}
|
|
101
586
|
function durableAgentSurface(getNamespace, opts) {
|
|
102
587
|
const chatPath = opts.chatPath ?? "/message";
|
|
@@ -106,14 +591,34 @@ function durableAgentSurface(getNamespace, opts) {
|
|
|
106
591
|
const namespace = getNamespace();
|
|
107
592
|
if (!namespace) return null;
|
|
108
593
|
const { message, session } = await req.json();
|
|
109
|
-
|
|
594
|
+
if (session !== void 0) try {
|
|
595
|
+
assertSessionKey(session);
|
|
596
|
+
} catch (err) {
|
|
597
|
+
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });
|
|
598
|
+
}
|
|
599
|
+
const res = await durableFetch(namespace, opts.agentName, session ?? "default", new Request("https://do/turn", {
|
|
110
600
|
method: "POST",
|
|
111
601
|
body: JSON.stringify({ userText: message })
|
|
112
602
|
}));
|
|
603
|
+
if (req.headers.get("accept")?.includes("text/event-stream")) return res;
|
|
604
|
+
return Response.json({ text: await sseTurnFinalText(res) });
|
|
113
605
|
};
|
|
114
606
|
}
|
|
607
|
+
function resolveServices(factory, env, agentName) {
|
|
608
|
+
if (!factory) return void 0;
|
|
609
|
+
if (typeof env !== "object" || env === null) return factory(env);
|
|
610
|
+
const cache = isolateLocal("june.durableChannelSurface.services", () => /* @__PURE__ */ new WeakMap());
|
|
611
|
+
let perAgent = cache.get(env);
|
|
612
|
+
if (!perAgent) {
|
|
613
|
+
perAgent = /* @__PURE__ */ new Map();
|
|
614
|
+
cache.set(env, perAgent);
|
|
615
|
+
}
|
|
616
|
+
if (!perAgent.has(agentName)) perAgent.set(agentName, factory(env));
|
|
617
|
+
return perAgent.get(agentName);
|
|
618
|
+
}
|
|
115
619
|
function durableChannelSurface(getNamespace, opts) {
|
|
116
|
-
|
|
620
|
+
const resolved = opts.channels.map((c) => resolveChannel(c, opts.env));
|
|
621
|
+
const ctx = {
|
|
117
622
|
agent: {
|
|
118
623
|
name: opts.agentName,
|
|
119
624
|
instructions: "",
|
|
@@ -122,20 +627,130 @@ function durableChannelSurface(getNamespace, opts) {
|
|
|
122
627
|
channels: [],
|
|
123
628
|
connections: []
|
|
124
629
|
},
|
|
630
|
+
services: resolveServices(opts.services, opts.env, opts.agentName),
|
|
125
631
|
waitUntil: opts.waitUntil,
|
|
632
|
+
files: opts.files,
|
|
126
633
|
run: async (message, o) => {
|
|
127
634
|
const namespace = getNamespace();
|
|
128
635
|
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
129
|
-
|
|
636
|
+
return sseTurnFinalText(await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request(`https://do/turn${o?.replace ? "?replace=1" : ""}`, {
|
|
637
|
+
method: "POST",
|
|
638
|
+
body: serializeTurn(message, o)
|
|
639
|
+
})));
|
|
640
|
+
},
|
|
641
|
+
runDetached: async (message, o) => {
|
|
642
|
+
const namespace = getNamespace();
|
|
643
|
+
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
644
|
+
const res = await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request(`https://do/turn?detach=1${o?.replace ? "&replace=1" : ""}`, {
|
|
645
|
+
method: "POST",
|
|
646
|
+
body: serializeTurn(message, o)
|
|
647
|
+
}));
|
|
648
|
+
if (res.status !== 202) {
|
|
649
|
+
const detail = (await res.text()).slice(0, 200);
|
|
650
|
+
throw new Error(`runDetached: turn was not accepted (status ${res.status}): ${detail}`);
|
|
651
|
+
}
|
|
652
|
+
return await res.json();
|
|
653
|
+
},
|
|
654
|
+
runDelivered: async (message, o) => {
|
|
655
|
+
const namespace = getNamespace();
|
|
656
|
+
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
657
|
+
const res = await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request(`https://do/turn?deliver=1${o?.replace ? "&replace=1" : ""}`, {
|
|
658
|
+
method: "POST",
|
|
659
|
+
body: serializeTurn(message, o)
|
|
660
|
+
}));
|
|
661
|
+
if (res.status === 501) throw new DeliverUnsupportedError(`runDelivered: ${(await res.json().catch(() => void 0))?.error ?? "the turn host cannot deliver this turn"}`);
|
|
662
|
+
if (res.status !== 202) {
|
|
663
|
+
const detail = (await res.text()).slice(0, 200);
|
|
664
|
+
throw new Error(`runDelivered: delivered turn was not accepted (status ${res.status}): ${detail}`);
|
|
665
|
+
}
|
|
666
|
+
return await res.json();
|
|
667
|
+
},
|
|
668
|
+
runStream: async function* (message, o) {
|
|
669
|
+
const namespace = getNamespace();
|
|
670
|
+
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
671
|
+
yield* sseTurnEvents(await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request(`https://do/turn${o?.replace ? "?replace=1" : ""}`, {
|
|
672
|
+
method: "POST",
|
|
673
|
+
body: serializeTurn(message, o)
|
|
674
|
+
})));
|
|
675
|
+
},
|
|
676
|
+
resumeStream: async function* (o) {
|
|
677
|
+
const namespace = getNamespace();
|
|
678
|
+
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
679
|
+
yield* sseTurnEvents(await durableFetch(namespace, opts.agentName, o.session ?? "default", new Request("https://do/resume", {
|
|
130
680
|
method: "POST",
|
|
131
|
-
body:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
681
|
+
body: serializeResume(o)
|
|
682
|
+
})));
|
|
683
|
+
},
|
|
684
|
+
resumeDelivered: async (o) => {
|
|
685
|
+
const namespace = getNamespace();
|
|
686
|
+
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
687
|
+
const res = await durableFetch(namespace, opts.agentName, o.session ?? "default", new Request("https://do/resume?deliver=1", {
|
|
688
|
+
method: "POST",
|
|
689
|
+
body: serializeResume(o)
|
|
690
|
+
}));
|
|
691
|
+
if (res.status === 501) throw new DeliverUnsupportedError(`resumeDelivered: ${(await res.json().catch(() => void 0))?.error ?? "the turn host cannot deliver this continuation"}`);
|
|
692
|
+
if (res.status !== 202) {
|
|
693
|
+
const detail = (await res.text()).slice(0, 200);
|
|
694
|
+
throw new Error(`resumeDelivered: resume was not accepted (status ${res.status}): ${detail}`);
|
|
695
|
+
}
|
|
696
|
+
return await res.json();
|
|
697
|
+
},
|
|
698
|
+
resetSession: async (o) => {
|
|
699
|
+
const namespace = getNamespace();
|
|
700
|
+
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
701
|
+
const res = await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/reset", { method: "POST" }));
|
|
702
|
+
if (!res.ok) {
|
|
703
|
+
const detail = (await res.text()).slice(0, 200);
|
|
704
|
+
throw new Error(`resetSession: reset was not accepted (status ${res.status}): ${detail}`);
|
|
705
|
+
}
|
|
706
|
+
return await res.json();
|
|
137
707
|
}
|
|
138
|
-
}
|
|
708
|
+
};
|
|
709
|
+
return channelDispatch(resolved, ctx);
|
|
710
|
+
}
|
|
711
|
+
function serializeResume(o) {
|
|
712
|
+
try {
|
|
713
|
+
return JSON.stringify({
|
|
714
|
+
turnId: o.turnId,
|
|
715
|
+
inputId: o.inputId,
|
|
716
|
+
input: o.input,
|
|
717
|
+
by: o.by,
|
|
718
|
+
principal: o.principal,
|
|
719
|
+
source: o.source,
|
|
720
|
+
target: o.target
|
|
721
|
+
});
|
|
722
|
+
} catch (err) {
|
|
723
|
+
const culprit = ["input", "principal"].find((field) => {
|
|
724
|
+
try {
|
|
725
|
+
JSON.stringify(o[field]);
|
|
726
|
+
return false;
|
|
727
|
+
} catch {
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
}) ?? "payload";
|
|
731
|
+
throw new Error(`resume: ${culprit} is not JSON-serializable (${err.message}) — a resume must round-trip to the DO`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function serializeTurn(userText, o) {
|
|
735
|
+
const payload = {
|
|
736
|
+
userText,
|
|
737
|
+
turnId: o?.turnId,
|
|
738
|
+
event: o?.event,
|
|
739
|
+
trigger: o?.trigger,
|
|
740
|
+
ifSuspended: o?.ifSuspended
|
|
741
|
+
};
|
|
742
|
+
try {
|
|
743
|
+
return JSON.stringify(payload);
|
|
744
|
+
} catch {
|
|
745
|
+
const event = o?.event ? {
|
|
746
|
+
...o.event,
|
|
747
|
+
raw: void 0
|
|
748
|
+
} : void 0;
|
|
749
|
+
return JSON.stringify({
|
|
750
|
+
...payload,
|
|
751
|
+
event
|
|
752
|
+
});
|
|
753
|
+
}
|
|
139
754
|
}
|
|
140
755
|
//#endregion
|
|
141
|
-
export { AgentDurableObject, DoSessionStore, durableAgentSurface, durableChannelSurface, durableFetch };
|
|
756
|
+
export { AgentDurableObject, DoSessionStore, SESSION_HEADER, durableAgentSurface, durableChannelSurface, durableFetch, sseTurnEvents, sseTurnFinalText };
|