@junejs/server 0.1.2-dev.9 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-durable.d.ts +3 -39
- package/dist/agent-durable.js +26 -297
- package/dist/agent-native.d.ts +0 -2
- package/dist/agent-native.js +10 -28
- package/dist/worker.js +1 -1
- package/package.json +3 -3
- package/src/agent-durable.ts +25 -438
- package/src/agent-native.ts +13 -27
- package/src/worker.ts +5 -2
- package/dist/core-version.d.ts +0 -5
- package/dist/core-version.js +0 -12
- package/src/core-version.ts +0 -34
package/dist/agent-durable.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Channel, ChannelFactory } from "@junejs/core/agent-config";
|
|
2
|
-
import {
|
|
2
|
+
import { Model, Msg, SessionStore, Tool } from "@junejs/core/agent-runtime";
|
|
3
3
|
import { Resources } from "@junejs/core/resources";
|
|
4
4
|
//#region src/agent-durable.d.ts
|
|
5
5
|
type SqlStorageValue = ArrayBuffer | string | number | null;
|
|
@@ -30,14 +30,11 @@ declare class DoSessionStore implements SessionStore {
|
|
|
30
30
|
private get sql();
|
|
31
31
|
appendMessage(m: Msg): void;
|
|
32
32
|
messages(): Msg[];
|
|
33
|
-
|
|
33
|
+
hasUserTurn(turnId: string): boolean;
|
|
34
34
|
getStep(id: string): unknown | undefined;
|
|
35
35
|
putStep(id: string, output: unknown): void;
|
|
36
|
-
delStep(id: string): void;
|
|
37
36
|
getStatus(): string;
|
|
38
37
|
setStatus(s: string): void;
|
|
39
|
-
getSessionKey(): string | undefined;
|
|
40
|
-
setSessionKey(key: string): void;
|
|
41
38
|
tx<T>(fn: () => T): T;
|
|
42
39
|
unwrap<H = unknown>(): H;
|
|
43
40
|
}
|
|
@@ -46,53 +43,21 @@ type DoAgentDef = {
|
|
|
46
43
|
model: Model;
|
|
47
44
|
tools: Tool[];
|
|
48
45
|
instructions?: string;
|
|
49
|
-
channelInstructions?: Record<string, string>;
|
|
50
|
-
channels?: (Channel | ChannelFactory)[];
|
|
51
|
-
env?: unknown;
|
|
52
46
|
resources?: Resources;
|
|
53
47
|
services?: unknown;
|
|
54
|
-
onTurnError?: (failure: {
|
|
55
|
-
turnId: string;
|
|
56
|
-
error: TurnError;
|
|
57
|
-
phase?: TurnFailurePhase;
|
|
58
|
-
step?: string;
|
|
59
|
-
}) => void | Promise<void>;
|
|
60
48
|
};
|
|
61
49
|
declare class AgentDurableObject {
|
|
62
50
|
private session;
|
|
63
|
-
private sessionKey;
|
|
64
|
-
private readonly name;
|
|
65
|
-
private readonly store;
|
|
66
|
-
private readonly sink;
|
|
67
|
-
private readonly model;
|
|
68
|
-
private readonly tools;
|
|
69
|
-
private readonly channelInstructions?;
|
|
70
51
|
private readonly resources;
|
|
71
52
|
private readonly services;
|
|
72
53
|
constructor(state: DurableObjectState, def: DoAgentDef);
|
|
73
|
-
private resolveSession;
|
|
74
54
|
turn(input: {
|
|
75
55
|
turnId?: string;
|
|
76
56
|
userText: string;
|
|
77
|
-
event?: InboundEvent;
|
|
78
|
-
trigger?: ProactiveTrigger;
|
|
79
|
-
session?: string;
|
|
80
57
|
}): Promise<string>;
|
|
81
|
-
start(input: {
|
|
82
|
-
turnId?: string;
|
|
83
|
-
userText: string;
|
|
84
|
-
event?: InboundEvent;
|
|
85
|
-
trigger?: ProactiveTrigger;
|
|
86
|
-
session?: string;
|
|
87
|
-
}): Promise<{
|
|
88
|
-
turnId: string;
|
|
89
|
-
}>;
|
|
90
58
|
transcript(): import("@junejs/core/agent-runtime").Turn[];
|
|
91
59
|
fetch(req: Request): Promise<Response>;
|
|
92
60
|
}
|
|
93
|
-
declare function sseTurnFinalText(res: Response): Promise<string>;
|
|
94
|
-
declare function sseTurnEvents(res: Response): AsyncIterable<TurnEvent>;
|
|
95
|
-
declare const SESSION_HEADER = "x-june-session";
|
|
96
61
|
declare function durableFetch(namespace: DurableObjectNamespace, agent: string, session: string, req: Request): Promise<Response>;
|
|
97
62
|
declare function durableAgentSurface(getNamespace: () => DurableObjectNamespace | undefined, opts: {
|
|
98
63
|
agentName: string;
|
|
@@ -102,8 +67,7 @@ declare function durableChannelSurface(getNamespace: () => DurableObjectNamespac
|
|
|
102
67
|
agentName: string;
|
|
103
68
|
channels: (Channel | ChannelFactory)[];
|
|
104
69
|
env: unknown;
|
|
105
|
-
services?: (env: unknown) => unknown;
|
|
106
70
|
waitUntil?: (p: Promise<unknown>) => void;
|
|
107
71
|
}): (req: Request) => Promise<Response | null>;
|
|
108
72
|
//#endregion
|
|
109
|
-
export { AgentDurableObject, DoAgentDef, DoSessionStore, DurableObjectNamespace, DurableObjectState, DurableObjectStub, DurableStorage,
|
|
73
|
+
export { AgentDurableObject, DoAgentDef, DoSessionStore, DurableObjectNamespace, DurableObjectState, DurableObjectStub, DurableStorage, SqlStorage, SqlStorageCursor, SqlStorageValue, durableAgentSurface, durableChannelSurface, durableFetch };
|
package/dist/agent-durable.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { assertCoreRuntimeVersion } from "./core-version.js";
|
|
2
1
|
import { channelDispatch, resolveChannel } from "@junejs/core/agent-config";
|
|
3
|
-
import { AgentSession,
|
|
2
|
+
import { AgentSession, withSystem } from "@junejs/core/agent-runtime";
|
|
4
3
|
import { ensureScope, runInScope } from "@junejs/db";
|
|
5
4
|
//#region src/agent-durable.ts
|
|
6
5
|
var DoSessionStore = class {
|
|
@@ -21,8 +20,8 @@ var DoSessionStore = class {
|
|
|
21
20
|
messages() {
|
|
22
21
|
return this.sql.exec("SELECT body FROM agent_messages ORDER BY seq").toArray().map((r) => JSON.parse(r.body));
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
return this.messages().some((m) =>
|
|
23
|
+
hasUserTurn(turnId) {
|
|
24
|
+
return this.messages().some((m) => m.role === "user" && m.turnId === turnId);
|
|
26
25
|
}
|
|
27
26
|
getStep(id) {
|
|
28
27
|
const rows = this.sql.exec("SELECT output FROM agent_steps WHERE id = ?", id).toArray();
|
|
@@ -31,9 +30,6 @@ var DoSessionStore = class {
|
|
|
31
30
|
putStep(id, output) {
|
|
32
31
|
this.sql.exec("INSERT INTO agent_steps (id, output) VALUES (?, ?)", id, JSON.stringify(output));
|
|
33
32
|
}
|
|
34
|
-
delStep(id) {
|
|
35
|
-
this.sql.exec("DELETE FROM agent_steps WHERE id = ?", id);
|
|
36
|
-
}
|
|
37
33
|
getStatus() {
|
|
38
34
|
const rows = this.sql.exec("SELECT v FROM agent_meta WHERE k = 'status'").toArray();
|
|
39
35
|
return rows.length ? rows[0].v : "new";
|
|
@@ -41,13 +37,6 @@ var DoSessionStore = class {
|
|
|
41
37
|
setStatus(s) {
|
|
42
38
|
this.sql.exec("INSERT INTO agent_meta (k, v) VALUES ('status', ?) ON CONFLICT(k) DO UPDATE SET v = ?", s, s);
|
|
43
39
|
}
|
|
44
|
-
getSessionKey() {
|
|
45
|
-
const rows = this.sql.exec("SELECT v FROM agent_meta WHERE k = 'session_key'").toArray();
|
|
46
|
-
return rows.length ? rows[0].v : void 0;
|
|
47
|
-
}
|
|
48
|
-
setSessionKey(key) {
|
|
49
|
-
this.sql.exec("INSERT INTO agent_meta (k, v) VALUES ('session_key', ?) ON CONFLICT(k) DO UPDATE SET v = ?", key, key);
|
|
50
|
-
}
|
|
51
40
|
tx(fn) {
|
|
52
41
|
return this.storage.transactionSync(fn);
|
|
53
42
|
}
|
|
@@ -55,12 +44,12 @@ var DoSessionStore = class {
|
|
|
55
44
|
return this.sql;
|
|
56
45
|
}
|
|
57
46
|
};
|
|
58
|
-
var
|
|
47
|
+
var InProcBroadcaster = class {
|
|
59
48
|
subs = /* @__PURE__ */ new Set();
|
|
60
|
-
|
|
49
|
+
publish(turnId) {
|
|
61
50
|
this.subs.forEach((cb) => {
|
|
62
51
|
try {
|
|
63
|
-
cb(
|
|
52
|
+
cb(turnId);
|
|
64
53
|
} catch {}
|
|
65
54
|
});
|
|
66
55
|
}
|
|
@@ -74,235 +63,40 @@ const crossDoUnsupported = { session() {
|
|
|
74
63
|
} };
|
|
75
64
|
var AgentDurableObject = class {
|
|
76
65
|
session;
|
|
77
|
-
sessionKey;
|
|
78
|
-
name;
|
|
79
|
-
store;
|
|
80
|
-
sink;
|
|
81
|
-
model;
|
|
82
|
-
tools;
|
|
83
|
-
channelInstructions;
|
|
84
66
|
resources;
|
|
85
67
|
services;
|
|
86
68
|
constructor(state, def) {
|
|
87
|
-
assertCoreRuntimeVersion(`AgentDurableObject(${def.name ?? "agent"})`);
|
|
88
69
|
const store = new DoSessionStore(state.storage);
|
|
89
70
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
90
71
|
this.resources = def.resources ?? {};
|
|
91
72
|
this.services = def.services;
|
|
92
|
-
|
|
93
|
-
const sink = new InProcEventSink();
|
|
94
|
-
sink.subscribe((e) => {
|
|
95
|
-
if (e.type !== "turn.failed") return;
|
|
96
|
-
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(" ← ")}` : ""}`);
|
|
97
|
-
if (def.onTurnError) try {
|
|
98
|
-
const out = def.onTurnError({
|
|
99
|
-
turnId: e.turnId,
|
|
100
|
-
error: e.error,
|
|
101
|
-
phase: e.phase,
|
|
102
|
-
step: e.step
|
|
103
|
-
});
|
|
104
|
-
if (out && typeof out.then === "function") out.then(void 0, (hookErr) => {
|
|
105
|
-
console.error(`[june] agent "${name}": onTurnError hook rejected:`, hookErr);
|
|
106
|
-
defaultLog();
|
|
107
|
-
});
|
|
108
|
-
return;
|
|
109
|
-
} catch (hookErr) {
|
|
110
|
-
console.error(`[june] agent "${name}": onTurnError hook threw:`, hookErr);
|
|
111
|
-
}
|
|
112
|
-
defaultLog();
|
|
113
|
-
});
|
|
114
|
-
const channelTools = (def.channels ?? []).flatMap((c) => resolveChannel(c, def.env).tools?.() ?? []);
|
|
115
|
-
const tools = [...def.tools];
|
|
116
|
-
const seen = new Set(tools.map((t) => t.spec.name));
|
|
117
|
-
for (const t of channelTools) {
|
|
118
|
-
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.`);
|
|
119
|
-
seen.add(t.spec.name);
|
|
120
|
-
tools.push(t);
|
|
121
|
-
}
|
|
122
|
-
this.name = name;
|
|
123
|
-
this.store = store;
|
|
124
|
-
this.sink = sink;
|
|
125
|
-
this.model = model;
|
|
126
|
-
this.tools = tools;
|
|
127
|
-
this.channelInstructions = def.channelInstructions;
|
|
128
|
-
}
|
|
129
|
-
resolveSession(key) {
|
|
130
|
-
if (key !== void 0) assertSessionKey(key);
|
|
131
|
-
if (this.session) {
|
|
132
|
-
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)`);
|
|
133
|
-
return this.session;
|
|
134
|
-
}
|
|
135
|
-
const stored = this.store.getSessionKey();
|
|
136
|
-
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)`);
|
|
137
|
-
const resolved = key ?? stored ?? "self";
|
|
138
|
-
if (key !== void 0 && key !== "self" && stored === void 0) this.store.setSessionKey(key);
|
|
139
|
-
this.sessionKey = resolved;
|
|
140
|
-
this.session = new AgentSession(this.name, resolved, this.store, this.sink, this.model, this.tools, crossDoUnsupported, this.channelInstructions);
|
|
141
|
-
return this.session;
|
|
73
|
+
this.session = new AgentSession(def.name ?? "agent", "self", store, new InProcBroadcaster(), model, def.tools, crossDoUnsupported);
|
|
142
74
|
}
|
|
143
75
|
async turn(input) {
|
|
144
76
|
await ensureScope();
|
|
145
|
-
const session = this.resolveSession(input.session);
|
|
146
77
|
return runInScope({
|
|
147
78
|
resources: this.resources,
|
|
148
79
|
services: this.services
|
|
149
|
-
}, () => session.turn(input));
|
|
150
|
-
}
|
|
151
|
-
async start(input) {
|
|
152
|
-
await ensureScope();
|
|
153
|
-
const session = this.resolveSession(input.session);
|
|
154
|
-
return runInScope({
|
|
155
|
-
resources: this.resources,
|
|
156
|
-
services: this.services
|
|
157
|
-
}, () => session.start(input));
|
|
80
|
+
}, () => this.session.turn(input));
|
|
158
81
|
}
|
|
159
82
|
transcript() {
|
|
160
|
-
|
|
161
|
-
if (this.store.getSessionKey() !== void 0) return this.resolveSession().transcript();
|
|
162
|
-
return new AgentSession(this.name, "self", this.store, this.sink, this.model, this.tools, crossDoUnsupported, this.channelInstructions).transcript();
|
|
83
|
+
return this.session.transcript();
|
|
163
84
|
}
|
|
164
85
|
async fetch(req) {
|
|
165
86
|
const url = new URL(req.url);
|
|
166
|
-
const key = req.headers.get("x-june-session") ?? void 0;
|
|
167
87
|
if (req.method === "POST" && url.pathname.endsWith("/turn")) {
|
|
168
|
-
const { userText, turnId
|
|
169
|
-
await
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
session = this.resolveSession(key);
|
|
174
|
-
started = runInScope({
|
|
175
|
-
resources: this.resources,
|
|
176
|
-
services: this.services
|
|
177
|
-
}, () => session.start({
|
|
178
|
-
userText,
|
|
179
|
-
turnId,
|
|
180
|
-
event,
|
|
181
|
-
trigger
|
|
182
|
-
}));
|
|
183
|
-
} catch (err) {
|
|
184
|
-
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
185
|
-
}
|
|
186
|
-
if (url.searchParams.get("detach") === "1") return Response.json({ turnId: started.turnId }, { status: 202 });
|
|
187
|
-
return new Response(sseTurnStream(session, started.turnId), { headers: SSE_HEADERS });
|
|
88
|
+
const { userText, turnId } = await req.json();
|
|
89
|
+
return Response.json({ text: await this.turn({
|
|
90
|
+
userText,
|
|
91
|
+
turnId
|
|
92
|
+
}) });
|
|
188
93
|
}
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
await ensureScope();
|
|
192
|
-
let session;
|
|
193
|
-
try {
|
|
194
|
-
session = this.resolveSession(key);
|
|
195
|
-
runInScope({
|
|
196
|
-
resources: this.resources,
|
|
197
|
-
services: this.services
|
|
198
|
-
}, () => session.resume(turnId, inputId, input, { by }));
|
|
199
|
-
} catch (err) {
|
|
200
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
201
|
-
return Response.json({ error: message }, { status: err instanceof ResumeAuthorizationError ? 403 : 409 });
|
|
202
|
-
}
|
|
203
|
-
return new Response(sseTurnStream(session, turnId), { headers: SSE_HEADERS });
|
|
204
|
-
}
|
|
205
|
-
if (url.pathname.endsWith("/transcript")) {
|
|
206
|
-
if (key !== void 0) try {
|
|
207
|
-
return Response.json({ transcript: this.resolveSession(key).transcript() });
|
|
208
|
-
} catch (err) {
|
|
209
|
-
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 409 });
|
|
210
|
-
}
|
|
211
|
-
return Response.json({ transcript: this.transcript() });
|
|
212
|
-
}
|
|
213
|
-
return new Response("agent DO — POST /turn, POST /resume, or GET /transcript", { status: 404 });
|
|
94
|
+
if (url.pathname.endsWith("/transcript")) return Response.json({ transcript: this.transcript() });
|
|
95
|
+
return new Response("agent DO — POST /turn or GET /transcript", { status: 404 });
|
|
214
96
|
}
|
|
215
97
|
};
|
|
216
|
-
const SSE_HEADERS = {
|
|
217
|
-
"content-type": "text/event-stream",
|
|
218
|
-
"cache-control": "no-store"
|
|
219
|
-
};
|
|
220
|
-
function sseTurnStream(session, turnId) {
|
|
221
|
-
const enc = new TextEncoder();
|
|
222
|
-
let unsub;
|
|
223
|
-
let heartbeat;
|
|
224
|
-
const stop = () => {
|
|
225
|
-
unsub?.();
|
|
226
|
-
clearInterval(heartbeat);
|
|
227
|
-
};
|
|
228
|
-
return new ReadableStream({
|
|
229
|
-
start(controller) {
|
|
230
|
-
heartbeat = setInterval(() => {
|
|
231
|
-
try {
|
|
232
|
-
controller.enqueue(enc.encode(":hb\n\n"));
|
|
233
|
-
} catch {
|
|
234
|
-
clearInterval(heartbeat);
|
|
235
|
-
}
|
|
236
|
-
}, 2e4);
|
|
237
|
-
unsub = session.observe((e) => {
|
|
238
|
-
controller.enqueue(enc.encode(`data: ${JSON.stringify(e)}\n\n`));
|
|
239
|
-
if (e.type === "turn.completed" || e.type === "turn.failed" || e.type === "input.requested") {
|
|
240
|
-
stop();
|
|
241
|
-
controller.close();
|
|
242
|
-
}
|
|
243
|
-
}, { turnId });
|
|
244
|
-
},
|
|
245
|
-
cancel() {
|
|
246
|
-
stop();
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
async function sseTurnFinalText(res) {
|
|
251
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
252
|
-
if (!res.body || !ct.includes("text/event-stream")) {
|
|
253
|
-
const detail = res.body ? (await res.text()).slice(0, 200) : "no body";
|
|
254
|
-
throw new Error(`turn stream: expected an SSE response, got ${ct || "no content-type"} (status ${res.status}): ${detail}`);
|
|
255
|
-
}
|
|
256
|
-
const reader = res.body.getReader();
|
|
257
|
-
const dec = new TextDecoder();
|
|
258
|
-
let buf = "";
|
|
259
|
-
for (;;) {
|
|
260
|
-
const { done, value } = await reader.read();
|
|
261
|
-
if (value) buf += dec.decode(value, { stream: true });
|
|
262
|
-
let i;
|
|
263
|
-
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
264
|
-
const line = buf.slice(0, i).split("\n").find((l) => l.startsWith("data:"));
|
|
265
|
-
buf = buf.slice(i + 2);
|
|
266
|
-
if (!line) continue;
|
|
267
|
-
const e = JSON.parse(line.slice(5).trim());
|
|
268
|
-
if (e.type === "turn.completed") return e.text;
|
|
269
|
-
if (e.type === "turn.failed") throw new Error(e.error.message, { cause: e.error });
|
|
270
|
-
}
|
|
271
|
-
if (done) break;
|
|
272
|
-
}
|
|
273
|
-
throw new Error("turn stream ended without a terminal event");
|
|
274
|
-
}
|
|
275
|
-
async function* sseTurnEvents(res) {
|
|
276
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
277
|
-
if (!res.body || !ct.includes("text/event-stream")) {
|
|
278
|
-
const detail = res.body ? (await res.text()).slice(0, 200) : "no body";
|
|
279
|
-
throw new Error(`turn stream: expected an SSE response, got ${ct || "no content-type"} (status ${res.status}): ${detail}`);
|
|
280
|
-
}
|
|
281
|
-
const reader = res.body.getReader();
|
|
282
|
-
const dec = new TextDecoder();
|
|
283
|
-
let buf = "";
|
|
284
|
-
for (;;) {
|
|
285
|
-
const { done, value } = await reader.read();
|
|
286
|
-
if (value) buf += dec.decode(value, { stream: true });
|
|
287
|
-
let i;
|
|
288
|
-
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
289
|
-
const line = buf.slice(0, i).split("\n").find((l) => l.startsWith("data:"));
|
|
290
|
-
buf = buf.slice(i + 2);
|
|
291
|
-
if (line) yield JSON.parse(line.slice(5).trim());
|
|
292
|
-
}
|
|
293
|
-
if (done) break;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
const SESSION_HEADER = "x-june-session";
|
|
297
|
-
const SESSION_KEY_RE = /^[\x20-\x7E]+$/;
|
|
298
|
-
function assertSessionKey(session) {
|
|
299
|
-
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)`);
|
|
300
|
-
}
|
|
301
98
|
function durableFetch(namespace, agent, session, req) {
|
|
302
|
-
|
|
303
|
-
const headers = new Headers(req.headers);
|
|
304
|
-
headers.set(SESSION_HEADER, session);
|
|
305
|
-
return namespace.get(namespace.idFromName(`${agent}:${session}`)).fetch(new Request(req, { headers }));
|
|
99
|
+
return namespace.get(namespace.idFromName(`${agent}:${session}`)).fetch(req);
|
|
306
100
|
}
|
|
307
101
|
function durableAgentSurface(getNamespace, opts) {
|
|
308
102
|
const chatPath = opts.chatPath ?? "/message";
|
|
@@ -312,17 +106,10 @@ function durableAgentSurface(getNamespace, opts) {
|
|
|
312
106
|
const namespace = getNamespace();
|
|
313
107
|
if (!namespace) return null;
|
|
314
108
|
const { message, session } = await req.json();
|
|
315
|
-
|
|
316
|
-
assertSessionKey(session);
|
|
317
|
-
} catch (err) {
|
|
318
|
-
return Response.json({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });
|
|
319
|
-
}
|
|
320
|
-
const res = await durableFetch(namespace, opts.agentName, session ?? "default", new Request("https://do/turn", {
|
|
109
|
+
return durableFetch(namespace, opts.agentName, session ?? "default", new Request("https://do/turn", {
|
|
321
110
|
method: "POST",
|
|
322
111
|
body: JSON.stringify({ userText: message })
|
|
323
112
|
}));
|
|
324
|
-
if (req.headers.get("accept")?.includes("text/event-stream")) return res;
|
|
325
|
-
return Response.json({ text: await sseTurnFinalText(res) });
|
|
326
113
|
};
|
|
327
114
|
}
|
|
328
115
|
function durableChannelSurface(getNamespace, opts) {
|
|
@@ -335,78 +122,20 @@ function durableChannelSurface(getNamespace, opts) {
|
|
|
335
122
|
channels: [],
|
|
336
123
|
connections: []
|
|
337
124
|
},
|
|
338
|
-
services: opts.services?.(opts.env),
|
|
339
125
|
waitUntil: opts.waitUntil,
|
|
340
126
|
run: async (message, o) => {
|
|
341
127
|
const namespace = getNamespace();
|
|
342
128
|
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
343
|
-
|
|
129
|
+
const { text } = await (await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn", {
|
|
344
130
|
method: "POST",
|
|
345
|
-
body:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const res = await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn?detach=1", {
|
|
352
|
-
method: "POST",
|
|
353
|
-
body: serializeTurn(message, o)
|
|
354
|
-
}));
|
|
355
|
-
if (res.status !== 202) {
|
|
356
|
-
const detail = (await res.text()).slice(0, 200);
|
|
357
|
-
throw new Error(`runDetached: turn was not accepted (status ${res.status}): ${detail}`);
|
|
358
|
-
}
|
|
359
|
-
return await res.json();
|
|
360
|
-
},
|
|
361
|
-
runStream: async function* (message, o) {
|
|
362
|
-
const namespace = getNamespace();
|
|
363
|
-
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
364
|
-
yield* sseTurnEvents(await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn", {
|
|
365
|
-
method: "POST",
|
|
366
|
-
body: serializeTurn(message, o)
|
|
367
|
-
})));
|
|
368
|
-
},
|
|
369
|
-
resumeStream: async function* (o) {
|
|
370
|
-
const namespace = getNamespace();
|
|
371
|
-
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
372
|
-
yield* sseTurnEvents(await durableFetch(namespace, opts.agentName, o.session ?? "default", new Request("https://do/resume", {
|
|
373
|
-
method: "POST",
|
|
374
|
-
body: serializeResume(o)
|
|
375
|
-
})));
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
userText: message,
|
|
133
|
+
turnId: o?.turnId
|
|
134
|
+
})
|
|
135
|
+
}))).json();
|
|
136
|
+
return text;
|
|
376
137
|
}
|
|
377
138
|
});
|
|
378
139
|
}
|
|
379
|
-
function serializeResume(o) {
|
|
380
|
-
try {
|
|
381
|
-
return JSON.stringify({
|
|
382
|
-
turnId: o.turnId,
|
|
383
|
-
inputId: o.inputId,
|
|
384
|
-
input: o.input,
|
|
385
|
-
by: o.by
|
|
386
|
-
});
|
|
387
|
-
} catch (err) {
|
|
388
|
-
throw new Error(`resumeStream: input is not JSON-serializable (${err.message}) — a resume answer must round-trip to the DO`);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
function serializeTurn(userText, o) {
|
|
392
|
-
const payload = {
|
|
393
|
-
userText,
|
|
394
|
-
turnId: o?.turnId,
|
|
395
|
-
event: o?.event,
|
|
396
|
-
trigger: o?.trigger
|
|
397
|
-
};
|
|
398
|
-
try {
|
|
399
|
-
return JSON.stringify(payload);
|
|
400
|
-
} catch {
|
|
401
|
-
const event = o?.event ? {
|
|
402
|
-
...o.event,
|
|
403
|
-
raw: void 0
|
|
404
|
-
} : void 0;
|
|
405
|
-
return JSON.stringify({
|
|
406
|
-
...payload,
|
|
407
|
-
event
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
140
|
//#endregion
|
|
412
|
-
export { AgentDurableObject, DoSessionStore,
|
|
141
|
+
export { AgentDurableObject, DoSessionStore, durableAgentSurface, durableChannelSurface, durableFetch };
|
package/dist/agent-native.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ type AgentDef = {
|
|
|
6
6
|
model: Model;
|
|
7
7
|
tools: Tool[];
|
|
8
8
|
instructions?: string;
|
|
9
|
-
channelInstructions?: Record<string, string>;
|
|
10
9
|
};
|
|
11
10
|
declare class NativeRuntime implements Runtime {
|
|
12
11
|
private actors;
|
|
@@ -31,7 +30,6 @@ declare function createAgentRuntime(agents: Record<string, AgentDef>, opts?: {
|
|
|
31
30
|
declare function mountAgent(agent: AgentDefinition, runtime: Runtime, opts?: {
|
|
32
31
|
chatPath?: string;
|
|
33
32
|
channels?: boolean;
|
|
34
|
-
services?: unknown;
|
|
35
33
|
}): {
|
|
36
34
|
surface: (req: Request) => Promise<Response | null>;
|
|
37
35
|
fetch: (req: Request) => Promise<Response | null>;
|
package/dist/agent-native.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assertCoreRuntimeVersion } from "./core-version.js";
|
|
2
1
|
import { openLocalSqliteSync } from "./sqlite-driver.js";
|
|
3
2
|
import { channelFetch } from "@junejs/core/agent-config";
|
|
4
3
|
import { AgentSession, withSystem } from "@junejs/core/agent-runtime";
|
|
@@ -21,8 +20,8 @@ var SqliteSessionStore = class {
|
|
|
21
20
|
messages() {
|
|
22
21
|
return this.db.query("SELECT body FROM agent_messages WHERE session_id = ? ORDER BY seq").all(this.sid).map((r) => JSON.parse(r.body));
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
return this.messages().some((m) =>
|
|
23
|
+
hasUserTurn(turnId) {
|
|
24
|
+
return this.messages().some((m) => m.role === "user" && m.turnId === turnId);
|
|
26
25
|
}
|
|
27
26
|
getStep(id) {
|
|
28
27
|
const r = this.db.query("SELECT output FROM agent_steps WHERE session_id = ? AND id = ?").get(this.sid, id);
|
|
@@ -31,9 +30,6 @@ var SqliteSessionStore = class {
|
|
|
31
30
|
putStep(id, output) {
|
|
32
31
|
this.db.query("INSERT INTO agent_steps (session_id, id, output) VALUES (?, ?, ?)").run(this.sid, id, JSON.stringify(output));
|
|
33
32
|
}
|
|
34
|
-
delStep(id) {
|
|
35
|
-
this.db.query("DELETE FROM agent_steps WHERE session_id = ? AND id = ?").run(this.sid, id);
|
|
36
|
-
}
|
|
37
33
|
getStatus() {
|
|
38
34
|
return this.db.query("SELECT status FROM agent_sessions WHERE session_id = ?").get(this.sid)?.status ?? "new";
|
|
39
35
|
}
|
|
@@ -55,12 +51,12 @@ var SqliteSessionStore = class {
|
|
|
55
51
|
return this.db;
|
|
56
52
|
}
|
|
57
53
|
};
|
|
58
|
-
var
|
|
54
|
+
var InProcBroadcaster = class {
|
|
59
55
|
subs = /* @__PURE__ */ new Set();
|
|
60
|
-
|
|
56
|
+
publish(turnId) {
|
|
61
57
|
this.subs.forEach((cb) => {
|
|
62
58
|
try {
|
|
63
|
-
cb(
|
|
59
|
+
cb(turnId);
|
|
64
60
|
} catch {}
|
|
65
61
|
});
|
|
66
62
|
}
|
|
@@ -74,7 +70,6 @@ var NativeRuntime = class {
|
|
|
74
70
|
agents;
|
|
75
71
|
db;
|
|
76
72
|
constructor(agents, db) {
|
|
77
|
-
assertCoreRuntimeVersion("NativeRuntime");
|
|
78
73
|
this.agents = agents;
|
|
79
74
|
this.db = db;
|
|
80
75
|
initSchema(db);
|
|
@@ -86,7 +81,7 @@ var NativeRuntime = class {
|
|
|
86
81
|
const def = this.agents[agent];
|
|
87
82
|
if (!def) throw new Error(`unknown agent: ${agent}`);
|
|
88
83
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
89
|
-
a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new
|
|
84
|
+
a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcBroadcaster(), model, def.tools, this);
|
|
90
85
|
this.actors.set(key, a);
|
|
91
86
|
}
|
|
92
87
|
return a;
|
|
@@ -105,8 +100,8 @@ var MemorySessionStore = class {
|
|
|
105
100
|
messages() {
|
|
106
101
|
return this.msgs.slice();
|
|
107
102
|
}
|
|
108
|
-
|
|
109
|
-
return this.msgs.some((m) =>
|
|
103
|
+
hasUserTurn(turnId) {
|
|
104
|
+
return this.msgs.some((m) => m.role === "user" && m.turnId === turnId);
|
|
110
105
|
}
|
|
111
106
|
getStep(id) {
|
|
112
107
|
return this.steps.has(id) ? this.steps.get(id) : void 0;
|
|
@@ -114,9 +109,6 @@ var MemorySessionStore = class {
|
|
|
114
109
|
putStep(id, output) {
|
|
115
110
|
this.steps.set(id, output);
|
|
116
111
|
}
|
|
117
|
-
delStep(id) {
|
|
118
|
-
this.steps.delete(id);
|
|
119
|
-
}
|
|
120
112
|
getStatus() {
|
|
121
113
|
return this.status;
|
|
122
114
|
}
|
|
@@ -133,7 +125,6 @@ var MemoryRuntime = class {
|
|
|
133
125
|
stores = /* @__PURE__ */ new Map();
|
|
134
126
|
agents;
|
|
135
127
|
constructor(agents) {
|
|
136
|
-
assertCoreRuntimeVersion("MemoryRuntime");
|
|
137
128
|
this.agents = agents;
|
|
138
129
|
}
|
|
139
130
|
session(agent, id) {
|
|
@@ -144,7 +135,7 @@ var MemoryRuntime = class {
|
|
|
144
135
|
if (!def) throw new Error(`unknown agent: ${agent}`);
|
|
145
136
|
const store = new MemorySessionStore();
|
|
146
137
|
this.stores.set(key, store);
|
|
147
|
-
a = new AgentSession(agent, id, store, new
|
|
138
|
+
a = new AgentSession(agent, id, store, new InProcBroadcaster(), def.model, def.tools, this);
|
|
148
139
|
this.actors.set(key, a);
|
|
149
140
|
}
|
|
150
141
|
return a;
|
|
@@ -161,18 +152,9 @@ function mountAgent(agent, runtime, opts = {}) {
|
|
|
161
152
|
const channelsOn = opts.channels ?? true;
|
|
162
153
|
const ctx = {
|
|
163
154
|
agent,
|
|
164
|
-
services: opts.services,
|
|
165
155
|
run: (message, o) => runtime.session(agent.name, o?.session ?? "default").turn({
|
|
166
156
|
turnId: o?.turnId,
|
|
167
|
-
userText: message
|
|
168
|
-
event: o?.event,
|
|
169
|
-
trigger: o?.trigger
|
|
170
|
-
}),
|
|
171
|
-
runDetached: async (message, o) => runtime.session(agent.name, o?.session ?? "default").start({
|
|
172
|
-
turnId: o?.turnId,
|
|
173
|
-
userText: message,
|
|
174
|
-
event: o?.event,
|
|
175
|
-
trigger: o?.trigger
|
|
157
|
+
userText: message
|
|
176
158
|
})
|
|
177
159
|
};
|
|
178
160
|
const channels = channelFetch(agent, ctx);
|
package/dist/worker.js
CHANGED
|
@@ -159,7 +159,7 @@ function withAssets(pipeline, opts = {}) {
|
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
if (assets) {
|
|
162
|
+
if (assets && (request.method === "GET" || request.method === "HEAD")) {
|
|
163
163
|
const a = await assets.fetch(request);
|
|
164
164
|
if (a.status !== 404) {
|
|
165
165
|
const ct = a.headers.get("content-type") ?? "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.1.2
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -125,14 +125,14 @@
|
|
|
125
125
|
"build": "tsdown"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@junejs/
|
|
128
|
+
"@junejs/core": ">=0.1.0",
|
|
129
|
+
"@junejs/db": ">=0.0.33",
|
|
129
130
|
"@momiji-rs/sparkdown": "^0.0.4",
|
|
130
131
|
"lightningcss": "^1.27.0",
|
|
131
132
|
"oxc-parser": "^0.137.0",
|
|
132
133
|
"rolldown": "^1.1.0"
|
|
133
134
|
},
|
|
134
135
|
"peerDependencies": {
|
|
135
|
-
"@junejs/core": ">=0.1.1-dev.16",
|
|
136
136
|
"react": "^19.2.0",
|
|
137
137
|
"react-dom": "^19.2.0",
|
|
138
138
|
"react-server-dom-webpack": "19.2.7",
|