@junejs/server 1.0.0-dev.14 → 1.0.0-dev.17
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/adapter.d.ts +7 -1
- package/dist/adapter.js +62 -5
- package/dist/agent-compile.d.ts +35 -0
- package/dist/agent-compile.js +139 -0
- package/dist/agent-discover.d.ts +3 -2
- package/dist/agent-discover.js +18 -44
- package/dist/agent-durable.d.ts +5 -2
- package/dist/agent-durable.js +18 -8
- package/dist/build.js +73 -8
- package/dist/client-bundle.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/tsconfig-jsx.d.ts +2 -1
- package/dist/tsconfig-jsx.js +1 -1
- package/package.json +13 -3
- package/src/adapter.ts +84 -3
- package/src/agent-compile.ts +245 -0
- package/src/agent-discover.ts +39 -54
- package/src/agent-durable.ts +33 -12
- package/src/build.ts +124 -9
- package/src/index.ts +8 -0
- package/src/tsconfig-jsx.ts +2 -2
package/dist/adapter.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ type AdapterCapabilities = {
|
|
|
4
4
|
runtime: "edge" | "node" | "serverless" | "native" | "static";
|
|
5
5
|
persistentConnections: boolean;
|
|
6
6
|
assets: "platform" | "server" | "none";
|
|
7
|
+
durableObjects?: boolean;
|
|
7
8
|
};
|
|
8
9
|
type AdapterEntry = {
|
|
9
10
|
imports: string[];
|
|
@@ -14,6 +15,10 @@ type ResourcePlan = {
|
|
|
14
15
|
binding: string;
|
|
15
16
|
databaseName: string;
|
|
16
17
|
};
|
|
18
|
+
agent?: {
|
|
19
|
+
binding: string;
|
|
20
|
+
className: string;
|
|
21
|
+
};
|
|
17
22
|
};
|
|
18
23
|
type AdapterEmitContext = {
|
|
19
24
|
appRoot: string;
|
|
@@ -39,6 +44,7 @@ interface JuneAdapter {
|
|
|
39
44
|
}): AdapterEntry;
|
|
40
45
|
emit(ctx: AdapterEmitContext): Promise<void>;
|
|
41
46
|
}
|
|
47
|
+
declare function hasDurableBinding(text: string, file: string, binding: string, className: string): boolean;
|
|
42
48
|
declare function workers(opts?: {
|
|
43
49
|
name?: string;
|
|
44
50
|
domain?: string;
|
|
@@ -53,4 +59,4 @@ declare function deno(opts?: {
|
|
|
53
59
|
}): JuneAdapter;
|
|
54
60
|
declare function staticSite(): JuneAdapter;
|
|
55
61
|
//#endregion
|
|
56
|
-
export { AdapterCapabilities, AdapterEmitContext, AdapterEntry, JuneAdapter, ResourcePlan, deno, staticSite, vercel, workers };
|
|
62
|
+
export { AdapterCapabilities, AdapterEmitContext, AdapterEntry, JuneAdapter, ResourcePlan, deno, hasDurableBinding, staticSite, vercel, workers };
|
package/dist/adapter.js
CHANGED
|
@@ -1,14 +1,51 @@
|
|
|
1
|
+
import { stripJsonc } from "./tsconfig-jsx.js";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { copyFile, cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { copyFile, cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
4
5
|
//#region src/adapter.ts
|
|
6
|
+
function hasDurableBinding(text, file, binding, className) {
|
|
7
|
+
if (file.endsWith(".toml")) {
|
|
8
|
+
const lines = text.split("\n").map((line) => {
|
|
9
|
+
let inString = false;
|
|
10
|
+
for (let i = 0; i < line.length; i++) {
|
|
11
|
+
const ch = line[i];
|
|
12
|
+
if (ch === "\"" && line[i - 1] !== "\\") inString = !inString;
|
|
13
|
+
else if (ch === "#" && !inString) return line.slice(0, i);
|
|
14
|
+
}
|
|
15
|
+
return line;
|
|
16
|
+
});
|
|
17
|
+
let inBindings = false;
|
|
18
|
+
let name;
|
|
19
|
+
let cls;
|
|
20
|
+
const matches = () => name === binding && cls === className;
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
const t = line.trim();
|
|
23
|
+
if (t.startsWith("[")) {
|
|
24
|
+
if (inBindings && matches()) return true;
|
|
25
|
+
inBindings = /^\[\[\s*durable_objects\.bindings\s*\]\]$/.test(t);
|
|
26
|
+
name = cls = void 0;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!inBindings) continue;
|
|
30
|
+
const kv = t.match(/^(name|class_name)\s*=\s*"([^"]*)"/);
|
|
31
|
+
if (kv) kv[1] === "name" ? name = kv[2] : cls = kv[2];
|
|
32
|
+
}
|
|
33
|
+
return inBindings && matches();
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return (JSON.parse(stripJsonc(text)).durable_objects?.bindings ?? []).some((b) => b.name === binding && b.class_name === className);
|
|
37
|
+
} catch {
|
|
38
|
+
return text.includes(className);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
5
41
|
function workers(opts) {
|
|
6
42
|
return {
|
|
7
43
|
name: "workers",
|
|
8
44
|
capabilities: {
|
|
9
45
|
runtime: "edge",
|
|
10
46
|
persistentConnections: true,
|
|
11
|
-
assets: "platform"
|
|
47
|
+
assets: "platform",
|
|
48
|
+
durableObjects: true
|
|
12
49
|
},
|
|
13
50
|
conditions: [
|
|
14
51
|
"source",
|
|
@@ -25,7 +62,17 @@ function workers(opts) {
|
|
|
25
62
|
};
|
|
26
63
|
},
|
|
27
64
|
async emit({ appRoot, outDir, hasAssets, config, plan, defaultName }) {
|
|
28
|
-
|
|
65
|
+
const own = ["wrangler.toml", "wrangler.jsonc"].map((f) => join(appRoot, f)).find(existsSync);
|
|
66
|
+
if (own) {
|
|
67
|
+
if (plan.agent) {
|
|
68
|
+
const cls = plan.agent.className;
|
|
69
|
+
if (!hasDurableBinding(await readFile(own, "utf8"), own, plan.agent.binding, cls)) {
|
|
70
|
+
const snippet = own.endsWith(".toml") ? `[[durable_objects.bindings]]\nname = "${plan.agent.binding}"\nclass_name = "${cls}"\n\n[[migrations]]\ntag = "v1"\nnew_sqlite_classes = ["${cls}"]` : `"durable_objects": { "bindings": [{ "name": "${plan.agent.binding}", "class_name": "${cls}" }] },\n"migrations": [{ "tag": "v1", "new_sqlite_classes": ["${cls}"] }]`;
|
|
71
|
+
console.warn(`[june build] durable agent built, but ${basename(own)} doesn't bind it — add:\n${snippet}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
29
76
|
const deployCfg = config.deploy;
|
|
30
77
|
await writeFile(join(outDir, "wrangler.jsonc"), JSON.stringify({
|
|
31
78
|
name: opts?.name ?? deployCfg?.name ?? defaultName,
|
|
@@ -42,6 +89,16 @@ function workers(opts) {
|
|
|
42
89
|
database_name: plan.db.databaseName,
|
|
43
90
|
database_id: ""
|
|
44
91
|
}] } : {},
|
|
92
|
+
...plan.agent ? {
|
|
93
|
+
durable_objects: { bindings: [{
|
|
94
|
+
name: plan.agent.binding,
|
|
95
|
+
class_name: plan.agent.className
|
|
96
|
+
}] },
|
|
97
|
+
migrations: [{
|
|
98
|
+
tag: "v1",
|
|
99
|
+
new_sqlite_classes: [plan.agent.className]
|
|
100
|
+
}]
|
|
101
|
+
} : {},
|
|
45
102
|
...opts?.domain ?? deployCfg?.domain ? { routes: [{
|
|
46
103
|
pattern: opts?.domain ?? deployCfg?.domain,
|
|
47
104
|
custom_domain: true
|
|
@@ -208,4 +265,4 @@ function staticSite() {
|
|
|
208
265
|
};
|
|
209
266
|
}
|
|
210
267
|
//#endregion
|
|
211
|
-
export { deno, staticSite, vercel, workers };
|
|
268
|
+
export { deno, hasDurableBinding, staticSite, vercel, workers };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/agent-compile.d.ts
|
|
2
|
+
declare const AGENT_MODULE_FILE = "_agent.gen.ts";
|
|
3
|
+
type AgentDirScan = {
|
|
4
|
+
dir: string;
|
|
5
|
+
name: string;
|
|
6
|
+
hasConfig: boolean;
|
|
7
|
+
instructions: string | null;
|
|
8
|
+
tools: string[];
|
|
9
|
+
skills: {
|
|
10
|
+
name: string;
|
|
11
|
+
raw: string;
|
|
12
|
+
}[];
|
|
13
|
+
channels: string[];
|
|
14
|
+
channelOverlays: {
|
|
15
|
+
source: string;
|
|
16
|
+
raw: string;
|
|
17
|
+
}[];
|
|
18
|
+
connections: string[];
|
|
19
|
+
};
|
|
20
|
+
declare function scanAgentDir(dir: string): AgentDirScan | null;
|
|
21
|
+
type EmitOptions = {
|
|
22
|
+
tsExtensions?: boolean;
|
|
23
|
+
};
|
|
24
|
+
declare function emitAgentModule(scan: AgentDirScan, opts?: EmitOptions): string;
|
|
25
|
+
declare function findAgentDir(root: string, dirName?: string): string | null;
|
|
26
|
+
declare function generateAgentModule(dir: string, opts?: {
|
|
27
|
+
check?: boolean;
|
|
28
|
+
} & EmitOptions): {
|
|
29
|
+
file: string;
|
|
30
|
+
code: string;
|
|
31
|
+
stale: boolean;
|
|
32
|
+
written: boolean;
|
|
33
|
+
} | null;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { AGENT_MODULE_FILE, AgentDirScan, EmitOptions, emitAgentModule, findAgentDir, generateAgentModule, scanAgentDir };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { stripJsonc } from "./tsconfig-jsx.js";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
//#region src/agent-compile.ts
|
|
5
|
+
const AGENT_MODULE_FILE = "_agent.gen.ts";
|
|
6
|
+
function list(dir, ext) {
|
|
7
|
+
if (!existsSync(dir)) return [];
|
|
8
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(ext) && !e.name.startsWith("_")).map((e) => e.name).sort();
|
|
9
|
+
}
|
|
10
|
+
function scanAgentDir(dir) {
|
|
11
|
+
if (!existsSync(dir)) return null;
|
|
12
|
+
const instructionsFile = join(dir, "instructions.md");
|
|
13
|
+
const scan = {
|
|
14
|
+
dir,
|
|
15
|
+
name: basename(dir),
|
|
16
|
+
hasConfig: existsSync(join(dir, "agent.ts")),
|
|
17
|
+
instructions: existsSync(instructionsFile) ? readFileSync(instructionsFile, "utf8") : null,
|
|
18
|
+
tools: list(join(dir, "tools"), ".ts").map((f) => `./tools/${f}`),
|
|
19
|
+
skills: list(join(dir, "skills"), ".md").map((f) => ({
|
|
20
|
+
name: basename(f, ".md"),
|
|
21
|
+
raw: readFileSync(join(dir, "skills", f), "utf8")
|
|
22
|
+
})),
|
|
23
|
+
channels: list(join(dir, "channels"), ".ts").map((f) => `./channels/${f}`),
|
|
24
|
+
channelOverlays: list(join(dir, "channels"), ".md").map((f) => ({
|
|
25
|
+
source: basename(f, ".md"),
|
|
26
|
+
raw: readFileSync(join(dir, "channels", f), "utf8")
|
|
27
|
+
})),
|
|
28
|
+
connections: list(join(dir, "connections"), ".ts").map((f) => `./connections/${f}`)
|
|
29
|
+
};
|
|
30
|
+
return !scan.hasConfig && scan.instructions === null && !scan.tools.length && !scan.skills.length && !scan.channels.length && !scan.channelOverlays.length && !scan.connections.length ? null : scan;
|
|
31
|
+
}
|
|
32
|
+
function identFor(prefix, spec, taken) {
|
|
33
|
+
const stem = basename(spec).replace(/\.ts$/, "").replace(/[^A-Za-z0-9]/g, "_");
|
|
34
|
+
let id = `${prefix}_${stem}`;
|
|
35
|
+
for (let i = 2; taken.has(id); i++) id = `${prefix}_${stem}_${i}`;
|
|
36
|
+
taken.add(id);
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
function emitAgentModule(scan, opts) {
|
|
40
|
+
const spec = (s) => JSON.stringify(opts?.tsExtensions ? s : s.replace(/\.ts$/, ""));
|
|
41
|
+
const taken = /* @__PURE__ */ new Set();
|
|
42
|
+
const imports = [];
|
|
43
|
+
const toolIds = scan.tools.map((s) => {
|
|
44
|
+
const id = identFor("tool", s, taken);
|
|
45
|
+
imports.push(`import ${id} from ${spec(s)};`);
|
|
46
|
+
return id;
|
|
47
|
+
});
|
|
48
|
+
const channelIds = scan.channels.map((s) => {
|
|
49
|
+
const id = identFor("channel", s, taken);
|
|
50
|
+
imports.push(`import ${id} from ${spec(s)};`);
|
|
51
|
+
return {
|
|
52
|
+
key: basename(s, ".ts"),
|
|
53
|
+
id
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
const connectionIds = scan.connections.map((s) => {
|
|
57
|
+
const id = identFor("connection", s, taken);
|
|
58
|
+
imports.push(`import ${id} from ${spec(s)};`);
|
|
59
|
+
return id;
|
|
60
|
+
});
|
|
61
|
+
if (scan.hasConfig) imports.push(`import config from ${spec("./agent.ts")};`);
|
|
62
|
+
const lines = [
|
|
63
|
+
"// AUTO-GENERATED by June — do not edit. Compiled from this agent/ directory so",
|
|
64
|
+
"// the definition mounts without filesystem discovery (workerd has no fs).",
|
|
65
|
+
"// Regenerate with `june gen` after adding, removing, or editing agent files.",
|
|
66
|
+
...imports,
|
|
67
|
+
scan.skills.length ? `import { parseSkill, type AgentModule } from "@junejs/core/agent-config";` : `import type { AgentModule } from "@junejs/core/agent-config";`,
|
|
68
|
+
""
|
|
69
|
+
];
|
|
70
|
+
if (!scan.hasConfig) lines.push(`const config = { name: ${JSON.stringify(scan.name)} };`, "");
|
|
71
|
+
lines.push("export const agentModule: AgentModule = {", " config,", ` instructions: ${JSON.stringify(scan.instructions ?? "")},`, ` tools: [${toolIds.join(", ")}],`, scan.skills.length ? ` skills: [\n${scan.skills.map((s) => ` parseSkill(${JSON.stringify(s.name)}, ${JSON.stringify(s.raw)}),`).join("\n")}\n ],` : " skills: [],", channelIds.length ? ` channels: { ${channelIds.map((c) => `${JSON.stringify(c.key)}: ${c.id}`).join(", ")} },` : " channels: {},", scan.channelOverlays.length ? ` channelInstructions: {\n${scan.channelOverlays.map((o) => ` ${JSON.stringify(o.source)}: ${JSON.stringify(o.raw)},`).join("\n")}\n },` : " channelInstructions: {},", ` connections: [${connectionIds.join(", ")}],`, "};", "export default agentModule;", "");
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
function findAgentDir(root, dirName = "agent") {
|
|
75
|
+
for (const candidate of [join(root, "app", dirName), join(root, dirName)]) if (existsSync(candidate)) return candidate;
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function readTsconfig(path) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(stripJsonc(readFileSync(path, "utf8")));
|
|
81
|
+
} catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function resolveExtends(fromDir, spec) {
|
|
86
|
+
const withJson = (p) => existsSync(p) ? p : !p.endsWith(".json") && existsSync(`${p}.json`) ? `${p}.json` : void 0;
|
|
87
|
+
if (spec.startsWith(".")) return withJson(resolve(fromDir, spec));
|
|
88
|
+
for (let d = fromDir;;) {
|
|
89
|
+
const candidate = withJson(join(d, "node_modules", spec));
|
|
90
|
+
if (candidate) return candidate;
|
|
91
|
+
const parent = dirname(d);
|
|
92
|
+
if (parent === d) return void 0;
|
|
93
|
+
d = parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function resolveAllowTsExtensions(path, depth) {
|
|
97
|
+
if (depth > 8) return void 0;
|
|
98
|
+
const tc = readTsconfig(path);
|
|
99
|
+
if (!tc) return void 0;
|
|
100
|
+
const own = tc.compilerOptions?.allowImportingTsExtensions;
|
|
101
|
+
if (own !== void 0) return own;
|
|
102
|
+
const bases = typeof tc.extends === "string" ? [tc.extends] : [...tc.extends ?? []].reverse();
|
|
103
|
+
for (const spec of bases) {
|
|
104
|
+
const base = resolveExtends(dirname(path), spec);
|
|
105
|
+
const inherited = base ? resolveAllowTsExtensions(base, depth + 1) : void 0;
|
|
106
|
+
if (inherited !== void 0) return inherited;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function sniffTsExtensions(dir) {
|
|
110
|
+
for (let d = dir;;) {
|
|
111
|
+
const candidate = join(d, "tsconfig.json");
|
|
112
|
+
if (existsSync(candidate)) return resolveAllowTsExtensions(candidate, 0) ?? false;
|
|
113
|
+
const parent = dirname(d);
|
|
114
|
+
if (parent === d) return false;
|
|
115
|
+
d = parent;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function generateAgentModule(dir, opts) {
|
|
119
|
+
const scan = scanAgentDir(dir);
|
|
120
|
+
if (!scan) return null;
|
|
121
|
+
const code = emitAgentModule(scan, { tsExtensions: opts?.tsExtensions ?? sniffTsExtensions(dir) });
|
|
122
|
+
const file = join(dir, AGENT_MODULE_FILE);
|
|
123
|
+
const stale = (existsSync(file) ? readFileSync(file, "utf8") : null) !== code;
|
|
124
|
+
if (opts?.check) return {
|
|
125
|
+
file,
|
|
126
|
+
code,
|
|
127
|
+
stale,
|
|
128
|
+
written: false
|
|
129
|
+
};
|
|
130
|
+
if (stale) writeFileSync(file, code);
|
|
131
|
+
return {
|
|
132
|
+
file,
|
|
133
|
+
code,
|
|
134
|
+
stale,
|
|
135
|
+
written: stale
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { AGENT_MODULE_FILE, emitAgentModule, findAgentDir, generateAgentModule, scanAgentDir };
|
package/dist/agent-discover.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AgentDefinition } from "@junejs/core/agent-config";
|
|
1
|
+
import { AgentDefinition, AgentModule } from "@junejs/core/agent-config";
|
|
2
2
|
//#region src/agent-discover.d.ts
|
|
3
|
+
declare function discoverAgentModule(dir: string): Promise<AgentModule>;
|
|
3
4
|
declare function discoverAgent(dir: string): Promise<AgentDefinition>;
|
|
4
5
|
//#endregion
|
|
5
|
-
export { discoverAgent };
|
|
6
|
+
export { discoverAgent, discoverAgentModule };
|
package/dist/agent-discover.js
CHANGED
|
@@ -2,42 +2,17 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import { basename, extname, join } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
import { connectAll } from "@junejs/core/connections";
|
|
5
|
+
import { assembleAgent, parseSkill } from "@junejs/core/agent-config";
|
|
7
6
|
//#region src/agent-discover.ts
|
|
8
7
|
async function scan(dir, ext) {
|
|
9
8
|
if (!existsSync(dir)) return [];
|
|
10
|
-
return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isFile() && extname(e.name) === ext).map((e) => join(dir, e.name)).sort();
|
|
9
|
+
return (await readdir(dir, { withFileTypes: true })).filter((e) => e.isFile() && extname(e.name) === ext && !e.name.startsWith("_")).map((e) => join(dir, e.name)).sort();
|
|
11
10
|
}
|
|
12
|
-
function
|
|
13
|
-
if (text.startsWith("---")) {
|
|
14
|
-
const end = text.indexOf("\n---", 3);
|
|
15
|
-
if (end !== -1) {
|
|
16
|
-
const front = text.slice(3, end);
|
|
17
|
-
const body = text.slice(end + 4).replace(/^\s*\n/, "");
|
|
18
|
-
const meta = {};
|
|
19
|
-
for (const line of front.split("\n")) {
|
|
20
|
-
const m = line.match(/^\s*(\w+)\s*:\s*(.+?)\s*$/);
|
|
21
|
-
if (m) meta[m[1]] = m[2];
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
name: meta.name ?? name,
|
|
25
|
-
description: meta.description ?? "",
|
|
26
|
-
body
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
name,
|
|
32
|
-
description: (text.split("\n").find((l) => l.trim()) ?? "").replace(/^#\s*/, ""),
|
|
33
|
-
body: text
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
async function discoverAgent(dir) {
|
|
11
|
+
async function discoverAgentModule(dir) {
|
|
37
12
|
const configFile = join(dir, "agent.ts");
|
|
38
13
|
const config = existsSync(configFile) ? (await import(pathToFileURL(configFile).href)).default : { name: basename(dir) };
|
|
39
14
|
const instructionsFile = join(dir, "instructions.md");
|
|
40
|
-
const instructions = existsSync(instructionsFile) ? await readFile(instructionsFile, "utf8") :
|
|
15
|
+
const instructions = existsSync(instructionsFile) ? await readFile(instructionsFile, "utf8") : "";
|
|
41
16
|
const tools = [];
|
|
42
17
|
for (const f of await scan(join(dir, "tools"), ".ts")) {
|
|
43
18
|
const mod = await import(pathToFileURL(f).href);
|
|
@@ -45,31 +20,30 @@ async function discoverAgent(dir) {
|
|
|
45
20
|
}
|
|
46
21
|
const skills = [];
|
|
47
22
|
for (const f of await scan(join(dir, "skills"), ".md")) skills.push(parseSkill(basename(f, ".md"), await readFile(f, "utf8")));
|
|
48
|
-
const channels =
|
|
23
|
+
const channels = {};
|
|
49
24
|
for (const f of await scan(join(dir, "channels"), ".ts")) {
|
|
50
25
|
const mod = await import(pathToFileURL(f).href);
|
|
51
|
-
if (mod.default) channels
|
|
26
|
+
if (mod.default) channels[basename(f, ".ts")] = mod.default;
|
|
52
27
|
}
|
|
53
|
-
const
|
|
28
|
+
const channelInstructions = {};
|
|
29
|
+
for (const f of await scan(join(dir, "channels"), ".md")) channelInstructions[basename(f, ".md")] = await readFile(f, "utf8");
|
|
30
|
+
const connections = [];
|
|
54
31
|
for (const f of await scan(join(dir, "connections"), ".ts")) {
|
|
55
32
|
const mod = await import(pathToFileURL(f).href);
|
|
56
|
-
if (mod.default)
|
|
33
|
+
if (mod.default) connections.push(mod.default);
|
|
57
34
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
report: []
|
|
61
|
-
};
|
|
62
|
-
tools.push(...connectionActions);
|
|
63
|
-
return defineAgent({
|
|
64
|
-
name: config.name,
|
|
65
|
-
model: config.model,
|
|
66
|
-
description: config.description,
|
|
35
|
+
return {
|
|
36
|
+
config,
|
|
67
37
|
instructions,
|
|
68
38
|
tools,
|
|
69
39
|
skills,
|
|
70
40
|
channels,
|
|
41
|
+
channelInstructions,
|
|
71
42
|
connections
|
|
72
|
-
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function discoverAgent(dir) {
|
|
46
|
+
return assembleAgent(await discoverAgentModule(dir), process.env);
|
|
73
47
|
}
|
|
74
48
|
//#endregion
|
|
75
|
-
export { discoverAgent };
|
|
49
|
+
export { discoverAgent, discoverAgentModule };
|
package/dist/agent-durable.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ type DoAgentDef = {
|
|
|
50
50
|
channelInstructions?: Record<string, string>;
|
|
51
51
|
channels?: (Channel | ChannelFactory)[];
|
|
52
52
|
env?: unknown;
|
|
53
|
-
resources?: Resources;
|
|
53
|
+
resources?: Resources | ((env: unknown) => Promise<Resources> | Resources);
|
|
54
54
|
services?: unknown;
|
|
55
55
|
onTurnError?: (failure: {
|
|
56
56
|
turnId: string;
|
|
@@ -69,8 +69,11 @@ declare class AgentDurableObject {
|
|
|
69
69
|
private readonly tools;
|
|
70
70
|
private readonly channels;
|
|
71
71
|
private readonly channelInstructions?;
|
|
72
|
-
private readonly
|
|
72
|
+
private readonly resourcesInput;
|
|
73
|
+
private resourcesOpened;
|
|
74
|
+
private readonly doEnv;
|
|
73
75
|
private readonly services;
|
|
76
|
+
private resolveResources;
|
|
74
77
|
constructor(state: DurableObjectState, def: DoAgentDef);
|
|
75
78
|
private resolveSession;
|
|
76
79
|
turn(input: {
|
package/dist/agent-durable.js
CHANGED
|
@@ -99,13 +99,19 @@ var AgentDurableObject = class {
|
|
|
99
99
|
tools;
|
|
100
100
|
channels;
|
|
101
101
|
channelInstructions;
|
|
102
|
-
|
|
102
|
+
resourcesInput;
|
|
103
|
+
resourcesOpened;
|
|
104
|
+
doEnv;
|
|
103
105
|
services;
|
|
106
|
+
resolveResources() {
|
|
107
|
+
return this.resourcesOpened ??= Promise.resolve(typeof this.resourcesInput === "function" ? this.resourcesInput(this.doEnv) : this.resourcesInput ?? {});
|
|
108
|
+
}
|
|
104
109
|
constructor(state, def) {
|
|
105
110
|
assertCoreRuntimeVersion(`AgentDurableObject(${def.name ?? "agent"})`);
|
|
106
111
|
const store = new DoSessionStore(state.storage);
|
|
107
112
|
const model = def.instructions ? withSystem(def.model, def.instructions) : def.model;
|
|
108
|
-
this.
|
|
113
|
+
this.resourcesInput = def.resources;
|
|
114
|
+
this.doEnv = def.env;
|
|
109
115
|
this.services = def.services;
|
|
110
116
|
const name = def.name ?? "agent";
|
|
111
117
|
const sink = new InProcEventSink();
|
|
@@ -162,17 +168,19 @@ var AgentDurableObject = class {
|
|
|
162
168
|
}
|
|
163
169
|
async turn(input) {
|
|
164
170
|
await ensureScope();
|
|
171
|
+
const resources = await this.resolveResources();
|
|
165
172
|
const session = this.resolveSession(input.session);
|
|
166
173
|
return runInScope({
|
|
167
|
-
resources
|
|
174
|
+
resources,
|
|
168
175
|
services: this.services
|
|
169
176
|
}, () => session.turn(input));
|
|
170
177
|
}
|
|
171
178
|
async start(input) {
|
|
172
179
|
await ensureScope();
|
|
180
|
+
const resources = await this.resolveResources();
|
|
173
181
|
const session = this.resolveSession(input.session);
|
|
174
182
|
return runInScope({
|
|
175
|
-
resources
|
|
183
|
+
resources,
|
|
176
184
|
services: this.services
|
|
177
185
|
}, () => session.start(input));
|
|
178
186
|
}
|
|
@@ -198,12 +206,13 @@ var AgentDurableObject = class {
|
|
|
198
206
|
deliverChannel = this.channels.find((c) => c.name === event.source);
|
|
199
207
|
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 });
|
|
200
208
|
}
|
|
209
|
+
const resources = await this.resolveResources();
|
|
201
210
|
let session;
|
|
202
211
|
let started;
|
|
203
212
|
try {
|
|
204
213
|
session = this.resolveSession(key);
|
|
205
214
|
started = runInScope({
|
|
206
|
-
resources
|
|
215
|
+
resources,
|
|
207
216
|
services: this.services
|
|
208
217
|
}, () => session.start({
|
|
209
218
|
userText,
|
|
@@ -227,7 +236,7 @@ var AgentDurableObject = class {
|
|
|
227
236
|
recipientTeamId: event.teamId
|
|
228
237
|
};
|
|
229
238
|
Promise.resolve().then(() => runInScope({
|
|
230
|
-
resources
|
|
239
|
+
resources,
|
|
231
240
|
services: this.services
|
|
232
241
|
}, () => deliverChannel.deliver(target, events, { session: this.sessionKey }))).catch(logRenderFailure).finally(() => {
|
|
233
242
|
it.return?.();
|
|
@@ -250,11 +259,12 @@ var AgentDurableObject = class {
|
|
|
250
259
|
resumeChannel = this.channels.find((c) => c.name === source);
|
|
251
260
|
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 });
|
|
252
261
|
}
|
|
262
|
+
const resources = await this.resolveResources();
|
|
253
263
|
let session;
|
|
254
264
|
try {
|
|
255
265
|
session = this.resolveSession(key);
|
|
256
266
|
runInScope({
|
|
257
|
-
resources
|
|
267
|
+
resources,
|
|
258
268
|
services: this.services
|
|
259
269
|
}, () => session.resume(turnId, inputId, input, { by }));
|
|
260
270
|
} catch (err) {
|
|
@@ -267,7 +277,7 @@ var AgentDurableObject = class {
|
|
|
267
277
|
const it = observeTurnEvents(session, turnId)[Symbol.asyncIterator]();
|
|
268
278
|
const events = { [Symbol.asyncIterator]: () => it };
|
|
269
279
|
Promise.resolve().then(() => runInScope({
|
|
270
|
-
resources
|
|
280
|
+
resources,
|
|
271
281
|
services: this.services
|
|
272
282
|
}, () => resumeChannel.deliverResume(target, events, { session: this.sessionKey }))).catch(logRenderFailure).finally(() => {
|
|
273
283
|
it.return?.();
|
package/dist/build.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { jsxTransform } from "./tsconfig-jsx.js";
|
|
1
2
|
import { deno, staticSite, vercel, workers } from "./adapter.js";
|
|
3
|
+
import { generateAgentModule } from "./agent-compile.js";
|
|
2
4
|
import { findMiddlewareFile } from "./router.js";
|
|
3
5
|
import { resolveBoundary } from "./segment.js";
|
|
4
6
|
import { buildModuleCss, registerCssModules, rolldownCssModulesPlugin } from "./css-modules.js";
|
|
5
|
-
import { jsxTransform } from "./tsconfig-jsx.js";
|
|
6
7
|
import { CLIENT_SCRIPT_URL, bundleClientToFile, findClientEntry } from "./client-bundle.js";
|
|
7
8
|
import { generateIslandRegistry } from "./island-registry.js";
|
|
8
9
|
import { STYLES_URL, cssTargets, findGlobalCss, globalCssUsesTailwind, minifyCss, processCss } from "./css.js";
|
|
@@ -262,6 +263,12 @@ async function buildManifest(appRoot) {
|
|
|
262
263
|
const mod = await import(pathToFileURL(extraFile).href);
|
|
263
264
|
if (typeof mod.default === "function") extra = mod.default;
|
|
264
265
|
}
|
|
266
|
+
let agentName;
|
|
267
|
+
const agentDir = join(appDir, frozen.agent.runtime.dir);
|
|
268
|
+
if (frozen.agent.runtime.enabled && existsSync(agentDir)) {
|
|
269
|
+
const cfgFile = join(agentDir, "agent.ts");
|
|
270
|
+
agentName = (existsSync(cfgFile) ? (await import(pathToFileURL(cfgFile).href)).default : void 0)?.name ?? basename(agentDir);
|
|
271
|
+
}
|
|
265
272
|
return {
|
|
266
273
|
routes,
|
|
267
274
|
dynamicRoutes,
|
|
@@ -271,6 +278,7 @@ async function buildManifest(appRoot) {
|
|
|
271
278
|
loadings,
|
|
272
279
|
document: frozen.document,
|
|
273
280
|
agent: frozen.agent,
|
|
281
|
+
agentName,
|
|
274
282
|
i18n: frozen.i18n,
|
|
275
283
|
earlyHints: frozen.earlyHints,
|
|
276
284
|
extra
|
|
@@ -317,6 +325,24 @@ async function juneBuild(appRoot, options = {}) {
|
|
|
317
325
|
} : void 0 },
|
|
318
326
|
config: fullConfig
|
|
319
327
|
});
|
|
328
|
+
const agentDir = join(appDir, frozen.agent.runtime.dir);
|
|
329
|
+
let agentModule = null;
|
|
330
|
+
if (frozen.agent.runtime.enabled) {
|
|
331
|
+
const generated = generateAgentModule(agentDir);
|
|
332
|
+
if (generated && adapter.capabilities.durableObjects) agentModule = generated;
|
|
333
|
+
else if (generated) console.warn(`[june build] app/${frozen.agent.runtime.dir}/ found, but the ${adapter.name}() target has no Durable Objects — the durable agent is not mounted.`);
|
|
334
|
+
}
|
|
335
|
+
if (agentModule) {
|
|
336
|
+
const sdkResolvable = (fromDir) => {
|
|
337
|
+
for (let d = resolve(fromDir);;) {
|
|
338
|
+
if (existsSync(join(d, "node_modules", "@anthropic-ai", "sdk", "package.json"))) return true;
|
|
339
|
+
const parent = dirname(d);
|
|
340
|
+
if (parent === d) return false;
|
|
341
|
+
d = parent;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
if (!sdkResolvable(appRoot)) throw new Error(`app/${frozen.agent.runtime.dir}/ mounts a durable agent whose model is Claude — add the SDK to the app so it bundles for workerd: bun add @anthropic-ai/sdk`);
|
|
345
|
+
}
|
|
320
346
|
const cssOut = await processCss(appDir, { minify: true });
|
|
321
347
|
let cssAsset = null;
|
|
322
348
|
if (cssOut !== null) {
|
|
@@ -407,7 +433,8 @@ async function juneBuild(appRoot, options = {}) {
|
|
|
407
433
|
imports.push(`import { turso } from "@junejs/server/db";`);
|
|
408
434
|
imports.push(`import { memoizeResources } from "@junejs/server/resources";`);
|
|
409
435
|
} else if (hasResources) imports.push(`import { bindWorkerResources } from "@junejs/server/resources";`);
|
|
410
|
-
const
|
|
436
|
+
const resourcesExpr = tursoDb ? `memoizeResources({ db: turso() })` : hasResources ? `bindWorkerResources(${JSON.stringify(resourceFlags)})` : null;
|
|
437
|
+
const resourcesField = resourcesExpr ? `\n resources: ${resourcesExpr},` : "";
|
|
411
438
|
const dataLayerModule = fullConfig.dataLayer?.module;
|
|
412
439
|
if (dataLayerModule) imports.push(`import { installDataLayer } from ${JSON.stringify(dataLayerModule)};`);
|
|
413
440
|
const dataLayerBoot = dataLayerModule ? "\ninstallDataLayer();\n" : "";
|
|
@@ -417,6 +444,36 @@ async function juneBuild(appRoot, options = {}) {
|
|
|
417
444
|
imports.push(`import { services as __appServices } from ${JSON.stringify(spec)};`);
|
|
418
445
|
}
|
|
419
446
|
const servicesField = servicesModule ? `\n services: __appServices,` : "";
|
|
447
|
+
if (agentModule) {
|
|
448
|
+
imports.push(`import __agentModule from ${JSON.stringify(importPath(genDir, agentModule.file))};`);
|
|
449
|
+
imports.push(`import { assembleDurable } from "@junejs/core/agent-config";`);
|
|
450
|
+
imports.push(`import { anthropic } from "@junejs/core/agent-models";`);
|
|
451
|
+
imports.push(`import { AgentDurableObject } from "@junejs/server/agent-durable";`);
|
|
452
|
+
imports.push(`import { DurableObject } from "cloudflare:workers";`);
|
|
453
|
+
imports.push(`import Anthropic from "@anthropic-ai/sdk";`);
|
|
454
|
+
}
|
|
455
|
+
const agentNameField = agentModule ? `\n agentName: __agentModule.config.name,` : "";
|
|
456
|
+
const doClass = agentModule ? `
|
|
457
|
+
// The durable agent: one Durable Object per session, the compiled agent/
|
|
458
|
+
// directory as its definition — adapted tools (+ read_skill when skills exist),
|
|
459
|
+
// the assembled system prompt, channel factories resolved with the DO's own
|
|
460
|
+
// env, and the app's declared resources opened from THIS isolate's env (lazily,
|
|
461
|
+
// at the first turn — the same ambient db a tool sees in native dev). Model:
|
|
462
|
+
// Anthropic over a statically bundled client (workerd cannot resolve a bare
|
|
463
|
+
// import at runtime), keyed by the ANTHROPIC_API_KEY secret — a missing secret
|
|
464
|
+
// surfaces as the SDK's own clear construction error on the first agent request.
|
|
465
|
+
const __agentDef = assembleDurable(__agentModule);
|
|
466
|
+
export class JuneAgentDO extends DurableObject {
|
|
467
|
+
#agent = new AgentDurableObject(this.ctx, {
|
|
468
|
+
...__agentDef,
|
|
469
|
+
model: anthropic({ model: __agentModule.config.model, client: new Anthropic({ apiKey: (this.env as { ANTHROPIC_API_KEY?: string }).ANTHROPIC_API_KEY }) }),
|
|
470
|
+
env: this.env,${resourcesExpr ? `\n resources: ${resourcesExpr},` : ""}${servicesModule ? "\n services: __appServices(this.env)," : ""}
|
|
471
|
+
});
|
|
472
|
+
fetch(req: Request): Promise<Response> {
|
|
473
|
+
return this.#agent.fetch(req);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
` : "";
|
|
420
477
|
const entry = `// AUTO-GENERATED by \`june build\` — do not edit. Regenerate: june build .
|
|
421
478
|
${adapterEntry.imports.join("\n")}
|
|
422
479
|
${imports.join("\n")}
|
|
@@ -435,12 +492,12 @@ ${chains.join("\n")}
|
|
|
435
492
|
${loadings.join("\n")}
|
|
436
493
|
},
|
|
437
494
|
document: ${JSON.stringify(frozen.document, null, 2).replace(/\n/g, "\n ")},
|
|
438
|
-
agent: ${JSON.stringify(frozen.agent)},${frozen.i18n ? `\n i18n: ${JSON.stringify(frozen.i18n)},` : ""}
|
|
495
|
+
agent: ${JSON.stringify(frozen.agent)},${agentNameField}${frozen.i18n ? `\n i18n: ${JSON.stringify(frozen.i18n)},` : ""}
|
|
439
496
|
earlyHints: ${JSON.stringify(frozen.earlyHints)},${builtExtraFile ? "\n extra," : ""}${resourcesField}${servicesField}
|
|
440
497
|
});
|
|
441
498
|
|
|
442
499
|
${adapterEntry.wrap("pipeline")}
|
|
443
|
-
`;
|
|
500
|
+
${doClass}`;
|
|
444
501
|
const entryFile = join(genDir, "worker-entry.tsx");
|
|
445
502
|
await writeFile(entryFile, entry);
|
|
446
503
|
const { rolldown } = await import("rolldown");
|
|
@@ -455,6 +512,7 @@ ${adapterEntry.wrap("pipeline")}
|
|
|
455
512
|
plugins: [rolldownCssModulesPlugin(cssModuleMaps)],
|
|
456
513
|
external: (id) => {
|
|
457
514
|
if (isBunSpecifier(id)) return true;
|
|
515
|
+
if (id.startsWith("cloudflare:")) return true;
|
|
458
516
|
if (/\.(wasm|ttf|otf|woff2?|png|jpe?g|avif|webp)$/.test(id)) return true;
|
|
459
517
|
return [...adapter.buildExternal ?? [], ...options.external ?? frozen.buildExternal].some((e) => id === e || id.startsWith(`${e}/`));
|
|
460
518
|
},
|
|
@@ -561,10 +619,16 @@ ${adapterEntry.wrap("pipeline")}
|
|
|
561
619
|
}
|
|
562
620
|
const pkgPath = join(appRoot, "package.json");
|
|
563
621
|
const defaultName = workerName((existsSync(pkgPath) ? JSON.parse(await Bun.file(pkgPath).text()).name : void 0) ?? basename(appRoot));
|
|
564
|
-
const plan =
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
622
|
+
const plan = {
|
|
623
|
+
...resourcesCfg?.db && resourcesCfg.db.kind !== "turso" ? { db: {
|
|
624
|
+
binding: "DB",
|
|
625
|
+
databaseName: `${defaultName}-db`
|
|
626
|
+
} } : {},
|
|
627
|
+
...agentModule ? { agent: {
|
|
628
|
+
binding: "AGENT",
|
|
629
|
+
className: "JuneAgentDO"
|
|
630
|
+
} } : {}
|
|
631
|
+
};
|
|
568
632
|
await adapter.emit({
|
|
569
633
|
appRoot,
|
|
570
634
|
outDir,
|
|
@@ -576,6 +640,7 @@ ${adapterEntry.wrap("pipeline")}
|
|
|
576
640
|
publicFiles
|
|
577
641
|
});
|
|
578
642
|
if (plan.db) console.log(` ↳ d1 binding "${plan.db.binding}" emitted — set database_id in wrangler.jsonc (wrangler d1 create ${plan.db.databaseName})`);
|
|
643
|
+
if (plan.agent) console.log(` ↳ durable agent mounted (${plan.agent.className} → env.${plan.agent.binding}; chat at ${frozen.agent.runtime.chat.path})`);
|
|
579
644
|
return {
|
|
580
645
|
outFile,
|
|
581
646
|
routes: routes.filter((r) => !r.dynamic).map((r) => r.path),
|