@junejs/server 0.1.1 → 0.1.2-dev.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/dist/agent-durable.d.ts +5 -1
- package/dist/agent-durable.js +32 -7
- package/dist/agent-native.d.ts +1 -0
- package/dist/agent-native.js +3 -2
- package/package.json +3 -3
- package/src/agent-durable.ts +43 -5
- package/src/agent-native.ts +3 -3
package/dist/agent-durable.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Channel, ChannelFactory } from "@junejs/core/agent-config";
|
|
2
|
-
import { Model, Msg, SessionStore, Tool } from "@junejs/core/agent-runtime";
|
|
2
|
+
import { InboundEvent, 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;
|
|
@@ -43,6 +43,9 @@ type DoAgentDef = {
|
|
|
43
43
|
model: Model;
|
|
44
44
|
tools: Tool[];
|
|
45
45
|
instructions?: string;
|
|
46
|
+
channelInstructions?: Record<string, string>;
|
|
47
|
+
channels?: (Channel | ChannelFactory)[];
|
|
48
|
+
env?: unknown;
|
|
46
49
|
resources?: Resources;
|
|
47
50
|
services?: unknown;
|
|
48
51
|
};
|
|
@@ -54,6 +57,7 @@ declare class AgentDurableObject {
|
|
|
54
57
|
turn(input: {
|
|
55
58
|
turnId?: string;
|
|
56
59
|
userText: string;
|
|
60
|
+
event?: InboundEvent;
|
|
57
61
|
}): Promise<string>;
|
|
58
62
|
transcript(): import("@junejs/core/agent-runtime").Turn[];
|
|
59
63
|
fetch(req: Request): Promise<Response>;
|
package/dist/agent-durable.js
CHANGED
|
@@ -70,7 +70,15 @@ var AgentDurableObject = class {
|
|
|
70
70
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
71
71
|
this.resources = def.resources ?? {};
|
|
72
72
|
this.services = def.services;
|
|
73
|
-
|
|
73
|
+
const channelTools = (def.channels ?? []).flatMap((c) => resolveChannel(c, def.env).tools?.() ?? []);
|
|
74
|
+
const tools = [...def.tools];
|
|
75
|
+
const seen = new Set(tools.map((t) => t.spec.name));
|
|
76
|
+
for (const t of channelTools) {
|
|
77
|
+
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.`);
|
|
78
|
+
seen.add(t.spec.name);
|
|
79
|
+
tools.push(t);
|
|
80
|
+
}
|
|
81
|
+
this.session = new AgentSession(def.name ?? "agent", "self", store, new InProcBroadcaster(), model, tools, crossDoUnsupported, def.channelInstructions);
|
|
74
82
|
}
|
|
75
83
|
async turn(input) {
|
|
76
84
|
await ensureScope();
|
|
@@ -85,10 +93,11 @@ var AgentDurableObject = class {
|
|
|
85
93
|
async fetch(req) {
|
|
86
94
|
const url = new URL(req.url);
|
|
87
95
|
if (req.method === "POST" && url.pathname.endsWith("/turn")) {
|
|
88
|
-
const { userText, turnId } = await req.json();
|
|
96
|
+
const { userText, turnId, event } = await req.json();
|
|
89
97
|
return Response.json({ text: await this.turn({
|
|
90
98
|
userText,
|
|
91
|
-
turnId
|
|
99
|
+
turnId,
|
|
100
|
+
event
|
|
92
101
|
}) });
|
|
93
102
|
}
|
|
94
103
|
if (url.pathname.endsWith("/transcript")) return Response.json({ transcript: this.transcript() });
|
|
@@ -128,14 +137,30 @@ function durableChannelSurface(getNamespace, opts) {
|
|
|
128
137
|
if (!namespace) throw new Error("durableChannelSurface: no Durable Object namespace bound (env.AGENT)");
|
|
129
138
|
const { text } = await (await durableFetch(namespace, opts.agentName, o?.session ?? "default", new Request("https://do/turn", {
|
|
130
139
|
method: "POST",
|
|
131
|
-
body:
|
|
132
|
-
userText: message,
|
|
133
|
-
turnId: o?.turnId
|
|
134
|
-
})
|
|
140
|
+
body: serializeTurn(message, o)
|
|
135
141
|
}))).json();
|
|
136
142
|
return text;
|
|
137
143
|
}
|
|
138
144
|
});
|
|
139
145
|
}
|
|
146
|
+
function serializeTurn(userText, o) {
|
|
147
|
+
const payload = {
|
|
148
|
+
userText,
|
|
149
|
+
turnId: o?.turnId,
|
|
150
|
+
event: o?.event
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
return JSON.stringify(payload);
|
|
154
|
+
} catch {
|
|
155
|
+
const event = o?.event ? {
|
|
156
|
+
...o.event,
|
|
157
|
+
raw: void 0
|
|
158
|
+
} : void 0;
|
|
159
|
+
return JSON.stringify({
|
|
160
|
+
...payload,
|
|
161
|
+
event
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
140
165
|
//#endregion
|
|
141
166
|
export { AgentDurableObject, DoSessionStore, durableAgentSurface, durableChannelSurface, durableFetch };
|
package/dist/agent-native.d.ts
CHANGED
package/dist/agent-native.js
CHANGED
|
@@ -81,7 +81,7 @@ var NativeRuntime = class {
|
|
|
81
81
|
const def = this.agents[agent];
|
|
82
82
|
if (!def) throw new Error(`unknown agent: ${agent}`);
|
|
83
83
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
84
|
-
a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcBroadcaster(), model, def.tools, this);
|
|
84
|
+
a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcBroadcaster(), model, def.tools, this, def.channelInstructions);
|
|
85
85
|
this.actors.set(key, a);
|
|
86
86
|
}
|
|
87
87
|
return a;
|
|
@@ -154,7 +154,8 @@ function mountAgent(agent, runtime, opts = {}) {
|
|
|
154
154
|
agent,
|
|
155
155
|
run: (message, o) => runtime.session(agent.name, o?.session ?? "default").turn({
|
|
156
156
|
turnId: o?.turnId,
|
|
157
|
-
userText: message
|
|
157
|
+
userText: message,
|
|
158
|
+
event: o?.event
|
|
158
159
|
})
|
|
159
160
|
};
|
|
160
161
|
const channels = channelFetch(agent, ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "0.1.1",
|
|
3
|
+
"version": "0.1.2-dev.1",
|
|
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,8 +125,8 @@
|
|
|
125
125
|
"build": "tsdown"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@junejs/core": ">=0.1.
|
|
129
|
-
"@junejs/db": ">=0.0.
|
|
128
|
+
"@junejs/core": ">=0.1.1-dev.3",
|
|
129
|
+
"@junejs/db": ">=0.0.34-dev.0",
|
|
130
130
|
"@momiji-rs/sparkdown": "^0.0.4",
|
|
131
131
|
"lightningcss": "^1.27.0",
|
|
132
132
|
"oxc-parser": "^0.137.0",
|
package/src/agent-durable.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type Model,
|
|
23
23
|
type Msg,
|
|
24
24
|
type Runtime,
|
|
25
|
+
type InboundEvent,
|
|
25
26
|
type SessionStore,
|
|
26
27
|
type Tool,
|
|
27
28
|
} from "@junejs/core/agent-runtime";
|
|
@@ -152,6 +153,16 @@ export type DoAgentDef = {
|
|
|
152
153
|
model: Model;
|
|
153
154
|
tools: Tool[];
|
|
154
155
|
instructions?: string;
|
|
156
|
+
// Per-channel-source system overlays (see AgentDefinition.channelInstructions) — the
|
|
157
|
+
// shared agent branches by real inbound source, no userText marker.
|
|
158
|
+
channelInstructions?: Record<string, string>;
|
|
159
|
+
// Channels whose CAPABILITY tools should be available to this session's turns. Their
|
|
160
|
+
// tools are built HERE, in the DO isolate, from `env` — because a channel's tool `run`
|
|
161
|
+
// is a closure (over the bot token, etc.) that can't cross the worker→DO RPC. This is
|
|
162
|
+
// the edge equivalent of defineAgent merging channel.tools() on native: pass the same
|
|
163
|
+
// channel factories you mount on the worker, plus this DO's env.
|
|
164
|
+
channels?: (Channel | ChannelFactory)[];
|
|
165
|
+
env?: unknown;
|
|
155
166
|
resources?: Resources;
|
|
156
167
|
services?: unknown;
|
|
157
168
|
};
|
|
@@ -181,7 +192,18 @@ export class AgentDurableObject {
|
|
|
181
192
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
182
193
|
this.resources = def.resources ?? {};
|
|
183
194
|
this.services = def.services;
|
|
184
|
-
|
|
195
|
+
// Merge the mounted channels' capability tools (built here from this DO's env, since a
|
|
196
|
+
// tool's `run` closure can't cross the RPC). The cross-channel source gate on each tool
|
|
197
|
+
// keeps a Slack tool inert during a Crisp turn, so merging all of them is safe.
|
|
198
|
+
const channelTools = (def.channels ?? []).flatMap((c) => resolveChannel(c, def.env).tools?.() ?? []);
|
|
199
|
+
const tools: Tool[] = [...def.tools];
|
|
200
|
+
const seen = new Set(tools.map((t) => t.spec.name));
|
|
201
|
+
for (const t of channelTools) {
|
|
202
|
+
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.`);
|
|
203
|
+
seen.add(t.spec.name);
|
|
204
|
+
tools.push(t);
|
|
205
|
+
}
|
|
206
|
+
this.session = new AgentSession(def.name ?? "agent", "self", store, new InProcBroadcaster(), model, tools, crossDoUnsupported, def.channelInstructions);
|
|
185
207
|
}
|
|
186
208
|
// Run the whole turn inside a request scope seeded from this DO's env, so ambient
|
|
187
209
|
// `db`/`kv`/`blob` and `currentServices()` resolve inside a tool exactly as in a
|
|
@@ -190,7 +212,7 @@ export class AgentDurableObject {
|
|
|
190
212
|
// Juno's batch-loader registry) can't leak across turns on a long-lived DO.
|
|
191
213
|
// ensureScope() lazily wires node:async_hooks (workerd via nodejs_compat), as the
|
|
192
214
|
// pipeline does; without it runInScope is a pass-through and ambient reads throw.
|
|
193
|
-
async turn(input: { turnId?: string; userText: string }): Promise<string> {
|
|
215
|
+
async turn(input: { turnId?: string; userText: string; event?: InboundEvent }): Promise<string> {
|
|
194
216
|
await ensureScope();
|
|
195
217
|
return runInScope({ resources: this.resources, services: this.services }, () => this.session.turn(input));
|
|
196
218
|
}
|
|
@@ -202,8 +224,8 @@ export class AgentDurableObject {
|
|
|
202
224
|
async fetch(req: Request): Promise<Response> {
|
|
203
225
|
const url = new URL(req.url);
|
|
204
226
|
if (req.method === "POST" && url.pathname.endsWith("/turn")) {
|
|
205
|
-
const { userText, turnId } = (await req.json()) as { userText: string; turnId?: string };
|
|
206
|
-
return Response.json({ text: await this.turn({ userText, turnId }) });
|
|
227
|
+
const { userText, turnId, event } = (await req.json()) as { userText: string; turnId?: string; event?: InboundEvent };
|
|
228
|
+
return Response.json({ text: await this.turn({ userText, turnId, event }) });
|
|
207
229
|
}
|
|
208
230
|
if (url.pathname.endsWith("/transcript")) return Response.json({ transcript: this.transcript() });
|
|
209
231
|
return new Response("agent DO — POST /turn or GET /transcript", { status: 404 });
|
|
@@ -293,7 +315,7 @@ export function durableChannelSurface(
|
|
|
293
315
|
namespace,
|
|
294
316
|
opts.agentName,
|
|
295
317
|
o?.session ?? "default",
|
|
296
|
-
new Request("https://do/turn", { method: "POST", body:
|
|
318
|
+
new Request("https://do/turn", { method: "POST", body: serializeTurn(message, o) }),
|
|
297
319
|
);
|
|
298
320
|
const { text } = (await res.json()) as { text: string };
|
|
299
321
|
return text;
|
|
@@ -301,3 +323,19 @@ export function durableChannelSurface(
|
|
|
301
323
|
};
|
|
302
324
|
return channelDispatch(resolved, ctx);
|
|
303
325
|
}
|
|
326
|
+
|
|
327
|
+
// Serialize a turn for the /turn RPC body. InboundEvent.raw is `unknown` — the
|
|
328
|
+
// untouched platform payload — so a (third-party) channel could attach something
|
|
329
|
+
// JSON.stringify chokes on (a circular object, a BigInt). raw isn't needed to route or
|
|
330
|
+
// run the turn, so on a serialization failure we drop it rather than let an
|
|
331
|
+
// unserializable payload take down turn forwarding entirely. (raw is optional on
|
|
332
|
+
// InboundEvent precisely because it may not survive this boundary.)
|
|
333
|
+
function serializeTurn(userText: string, o?: { turnId?: string; event?: InboundEvent }): string {
|
|
334
|
+
const payload = { userText, turnId: o?.turnId, event: o?.event };
|
|
335
|
+
try {
|
|
336
|
+
return JSON.stringify(payload);
|
|
337
|
+
} catch {
|
|
338
|
+
const event = o?.event ? { ...o.event, raw: undefined } : undefined;
|
|
339
|
+
return JSON.stringify({ ...payload, event });
|
|
340
|
+
}
|
|
341
|
+
}
|
package/src/agent-native.ts
CHANGED
|
@@ -85,7 +85,7 @@ class InProcBroadcaster implements Broadcaster {
|
|
|
85
85
|
|
|
86
86
|
// `instructions` (the agent's system prompt) is injected into the model per turn
|
|
87
87
|
// by the runtime (withSystem) — single-sourced on the def, not baked into `model`.
|
|
88
|
-
export type AgentDef = { model: Model; tools: Tool[]; instructions?: string };
|
|
88
|
+
export type AgentDef = { model: Model; tools: Tool[]; instructions?: string; channelInstructions?: Record<string, string> };
|
|
89
89
|
|
|
90
90
|
// The native Runtime: a registry of agent definitions over one SQLite handle,
|
|
91
91
|
// handing out (and memoizing) an AgentSession actor per (agent, id).
|
|
@@ -107,7 +107,7 @@ export class NativeRuntime implements Runtime {
|
|
|
107
107
|
const def = this.agents[agent];
|
|
108
108
|
if (!def) throw new Error(`unknown agent: ${agent}`);
|
|
109
109
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
110
|
-
a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcBroadcaster(), model, def.tools, this);
|
|
110
|
+
a = new AgentSession(agent, id, new SqliteSessionStore(this.db, key), new InProcBroadcaster(), model, def.tools, this, def.channelInstructions);
|
|
111
111
|
this.actors.set(key, a);
|
|
112
112
|
}
|
|
113
113
|
return a;
|
|
@@ -207,7 +207,7 @@ export function mountAgent(
|
|
|
207
207
|
const ctx: ChannelContext = {
|
|
208
208
|
agent,
|
|
209
209
|
run: (message, o) =>
|
|
210
|
-
runtime.session(agent.name, o?.session ?? "default").turn({ turnId: o?.turnId, userText: message }),
|
|
210
|
+
runtime.session(agent.name, o?.session ?? "default").turn({ turnId: o?.turnId, userText: message, event: o?.event }),
|
|
211
211
|
};
|
|
212
212
|
const channels = channelFetch(agent, ctx);
|
|
213
213
|
const surface = async (req: Request): Promise<Response | null> => {
|