@junejs/server 1.0.0-dev.14 → 1.0.0-dev.19
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 +39 -0
- package/dist/agent-compile.js +145 -0
- package/dist/agent-discover.d.ts +3 -2
- package/dist/agent-discover.js +30 -44
- package/dist/agent-durable.d.ts +13 -4
- package/dist/agent-durable.js +45 -9
- package/dist/build.js +74 -9
- 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/dist/worker.d.ts +9 -4
- package/dist/worker.js +28 -9
- package/package.json +13 -3
- package/src/adapter.ts +84 -3
- package/src/agent-compile.ts +271 -0
- package/src/agent-discover.ts +71 -60
- package/src/agent-durable.ts +92 -16
- package/src/build.ts +127 -10
- package/src/index.ts +8 -0
- package/src/tsconfig-jsx.ts +2 -2
- package/src/worker.ts +66 -18
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,10 +444,40 @@ 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 agentPreamble = agentModule ? `\nconst __agentDef = assembleDurable(__agentModule);\n` : "";
|
|
456
|
+
const agentNameField = agentModule ? `\n agentName: __agentModule.config.name,\n agentChannels: __agentDef.channels,` : "";
|
|
457
|
+
const doClass = agentModule ? `
|
|
458
|
+
// The durable agent: one Durable Object per session, the compiled agent/
|
|
459
|
+
// directory as its definition — adapted tools (+ read_skill when skills exist),
|
|
460
|
+
// the assembled system prompt, channel factories resolved with the DO's own
|
|
461
|
+
// env, and the app's declared resources opened from THIS isolate's env (lazily,
|
|
462
|
+
// at the first turn — the same ambient db a tool sees in native dev). Model:
|
|
463
|
+
// Anthropic over a statically bundled client (workerd cannot resolve a bare
|
|
464
|
+
// import at runtime), keyed by the ANTHROPIC_API_KEY secret — a missing secret
|
|
465
|
+
// surfaces as the SDK's own clear construction error on the first agent request.
|
|
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")}
|
|
423
|
-
${dataLayerBoot}
|
|
480
|
+
${dataLayerBoot}${agentPreamble}
|
|
424
481
|
const pipeline = createWorker({
|
|
425
482
|
routes: {
|
|
426
483
|
${statics.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),
|
package/dist/client-bundle.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { rolldownCssModulesPlugin } from "./css-modules.js";
|
|
2
1
|
import { jsxTransform } from "./tsconfig-jsx.js";
|
|
2
|
+
import { rolldownCssModulesPlugin } from "./css-modules.js";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, join } from "node:path";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AdapterCapabilities, AdapterEmitContext, AdapterEntry, JuneAdapter, ResourcePlan, deno, staticSite, vercel, workers } from "./adapter.js";
|
|
2
|
+
import { AGENT_MODULE_FILE, AgentDirScan, emitAgentModule, findAgentDir, generateAgentModule, scanAgentDir } from "./agent-compile.js";
|
|
2
3
|
import { CreateAppOptions, JuneApp, createApp } from "./app.js";
|
|
3
4
|
import { R2Bucket, localBlob, r2 } from "./blob.js";
|
|
4
5
|
import { a as Pipeline, d as createPipeline, n as LayoutComponent, o as PipelineConfig, s as Resolved, u as RouteResolver } from "./pipeline-cCR6MGNK.js";
|
|
@@ -24,4 +25,4 @@ import { Negotiated, negotiate } from "./negotiate.js";
|
|
|
24
25
|
import { blob, currentServices, db, kv } from "@junejs/db";
|
|
25
26
|
import { ServicesConfig, defineServices } from "@junejs/core/config";
|
|
26
27
|
import { I18nConfig, LOCALE_COOKIE, LocaleConfig, LocaleMatch, dirForLang, localeDir, localeHref, matchPinnedLocale, negotiateLocale, parseAcceptLanguage, resolveRequestLocale } from "@junejs/core/i18n";
|
|
27
|
-
export { type AdapterCapabilities, type AdapterEmitContext, type AdapterEntry, type BuildResult, type ContentEntry, type CreateAppOptions, type D1Database, type D1Exec, type DeployResult, type DevServer, type DevServerOptions, type I18nConfig, type JuneAdapter, type JuneApp, type JuneDb, type JuneHost, LOCALE_COOKIE, type LayoutComponent, type LocaleConfig, type LocaleMatch, type MatchOptions, type MigrateResult, type Migration, type Negotiated, type Pipeline, type PipelineConfig, type R2Bucket, type Resolved, type ResourceFlags, type ResourcePlan, type RouteMatch, type RouteResolver, type RouteTreeMatch, type RunResult, type SegmentMatch, type ServeHandle, type ServicesConfig, type SpawnedModule, type WorkerEnv, type WorkerManifest, bindWorkerResources, blob, blockedMessage, buildManifest, classify, collection, createApp, createPipeline, createWorker, currentServices, d1, db, defineServices, deno, dirForLang, entry, freezeConfig, generateContent, host, installAsyncContext, juneBuild, juneDeploy, kv, listRoutes, loadJuneConfig, localBlob, localeDir, localeHref, matchPinnedLocale, matchRoute, matchRouteTree, memoizeResources, memoryKv, migrate, migrateApp, migrateD1, mysql, negotiate, negotiateLocale, parseAcceptLanguage, postgres, r2, readMigrations, redisKv, resolveD1Database, resolveNotFound, resolveRequestLocale, scanRoutes, sqlite, startDevServer, staticSite, turso, typesApp, vercel, workers, wranglerD1 };
|
|
28
|
+
export { AGENT_MODULE_FILE, type AdapterCapabilities, type AdapterEmitContext, type AdapterEntry, type AgentDirScan, type BuildResult, type ContentEntry, type CreateAppOptions, type D1Database, type D1Exec, type DeployResult, type DevServer, type DevServerOptions, type I18nConfig, type JuneAdapter, type JuneApp, type JuneDb, type JuneHost, LOCALE_COOKIE, type LayoutComponent, type LocaleConfig, type LocaleMatch, type MatchOptions, type MigrateResult, type Migration, type Negotiated, type Pipeline, type PipelineConfig, type R2Bucket, type Resolved, type ResourceFlags, type ResourcePlan, type RouteMatch, type RouteResolver, type RouteTreeMatch, type RunResult, type SegmentMatch, type ServeHandle, type ServicesConfig, type SpawnedModule, type WorkerEnv, type WorkerManifest, bindWorkerResources, blob, blockedMessage, buildManifest, classify, collection, createApp, createPipeline, createWorker, currentServices, d1, db, defineServices, deno, dirForLang, emitAgentModule, entry, findAgentDir, freezeConfig, generateAgentModule, generateContent, host, installAsyncContext, juneBuild, juneDeploy, kv, listRoutes, loadJuneConfig, localBlob, localeDir, localeHref, matchPinnedLocale, matchRoute, matchRouteTree, memoizeResources, memoryKv, migrate, migrateApp, migrateD1, mysql, negotiate, negotiateLocale, parseAcceptLanguage, postgres, r2, readMigrations, redisKv, resolveD1Database, resolveNotFound, resolveRequestLocale, scanAgentDir, scanRoutes, sqlite, startDevServer, staticSite, turso, typesApp, vercel, workers, wranglerD1 };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deno, staticSite, vercel, workers } from "./adapter.js";
|
|
2
|
+
import { AGENT_MODULE_FILE, emitAgentModule, findAgentDir, generateAgentModule, scanAgentDir } from "./agent-compile.js";
|
|
2
3
|
import { listRoutes, matchRoute, matchRouteTree, resolveNotFound } from "./router.js";
|
|
3
4
|
import { negotiate } from "./negotiate.js";
|
|
4
5
|
import { createPipeline } from "./pipeline.js";
|
|
@@ -24,4 +25,4 @@ import { memoryKv, redisKv } from "./kv.js";
|
|
|
24
25
|
import { blob, currentServices, db, kv } from "@junejs/db";
|
|
25
26
|
import { defineServices } from "@junejs/core/config";
|
|
26
27
|
import { LOCALE_COOKIE, dirForLang, localeDir, localeHref, matchPinnedLocale, negotiateLocale, parseAcceptLanguage, resolveRequestLocale } from "@junejs/core/i18n";
|
|
27
|
-
export { LOCALE_COOKIE, bindWorkerResources, blob, blockedMessage, buildManifest, classify, collection, createApp, createPipeline, createWorker, currentServices, d1, db, defineServices, deno, dirForLang, entry, freezeConfig, generateContent, host, installAsyncContext, juneBuild, juneDeploy, kv, listRoutes, loadJuneConfig, localBlob, localeDir, localeHref, matchPinnedLocale, matchRoute, matchRouteTree, memoizeResources, memoryKv, migrate, migrateApp, migrateD1, mysql, negotiate, negotiateLocale, parseAcceptLanguage, postgres, r2, readMigrations, redisKv, resolveD1Database, resolveNotFound, resolveRequestLocale, scanRoutes, sqlite, startDevServer, staticSite, turso, typesApp, vercel, workers, wranglerD1 };
|
|
28
|
+
export { AGENT_MODULE_FILE, LOCALE_COOKIE, bindWorkerResources, blob, blockedMessage, buildManifest, classify, collection, createApp, createPipeline, createWorker, currentServices, d1, db, defineServices, deno, dirForLang, emitAgentModule, entry, findAgentDir, freezeConfig, generateAgentModule, generateContent, host, installAsyncContext, juneBuild, juneDeploy, kv, listRoutes, loadJuneConfig, localBlob, localeDir, localeHref, matchPinnedLocale, matchRoute, matchRouteTree, memoizeResources, memoryKv, migrate, migrateApp, migrateD1, mysql, negotiate, negotiateLocale, parseAcceptLanguage, postgres, r2, readMigrations, redisKv, resolveD1Database, resolveNotFound, resolveRequestLocale, scanAgentDir, scanRoutes, sqlite, startDevServer, staticSite, turso, typesApp, vercel, workers, wranglerD1 };
|
package/dist/tsconfig-jsx.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//#region src/tsconfig-jsx.d.ts
|
|
2
|
+
declare const stripJsonc: (s: string) => string;
|
|
2
3
|
/** Read the app's tsconfig.json and return compilerOptions.jsxImportSource.
|
|
3
4
|
* Follows one level of `extends` so apps that inherit from a base tsconfig
|
|
4
5
|
* (e.g. "@kurajs/docs/tsconfig.kura.json") are handled correctly.
|
|
@@ -12,4 +13,4 @@ declare function jsxTransform(appRoot: string): Promise<{
|
|
|
12
13
|
importSource?: string;
|
|
13
14
|
}>;
|
|
14
15
|
//#endregion
|
|
15
|
-
export { appJsxImportSource, jsxTransform };
|
|
16
|
+
export { appJsxImportSource, jsxTransform, stripJsonc };
|
package/dist/tsconfig-jsx.js
CHANGED
package/dist/worker.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { l as ResourceHandler, n as LayoutComponent, r as LoadingComponent, t as ExtraHandler } from "./pipeline-cCR6MGNK.js";
|
|
2
|
+
import { Channel, ChannelFactory } from "@junejs/core/agent-config";
|
|
2
3
|
import { BrandedRoute } from "@junejs/core/route";
|
|
3
4
|
import { AgentConfig } from "@junejs/core/config";
|
|
4
5
|
import React from "react";
|
|
@@ -6,6 +7,9 @@ import { DocumentConfig } from "@junejs/core/document";
|
|
|
6
7
|
import { I18nConfig } from "@junejs/core/i18n";
|
|
7
8
|
import { Resources } from "@junejs/core/resources";
|
|
8
9
|
//#region src/worker.d.ts
|
|
10
|
+
type WorkerExecutionContext = {
|
|
11
|
+
waitUntil?(p: Promise<unknown>): void;
|
|
12
|
+
};
|
|
9
13
|
type WorkerManifest = {
|
|
10
14
|
routes: Record<string, BrandedRoute>;
|
|
11
15
|
dynamicRoutes?: Array<{
|
|
@@ -25,6 +29,7 @@ type WorkerManifest = {
|
|
|
25
29
|
document: DocumentConfig;
|
|
26
30
|
agent: AgentConfig;
|
|
27
31
|
agentName?: string;
|
|
32
|
+
agentChannels?: (Channel | ChannelFactory)[];
|
|
28
33
|
i18n?: I18nConfig;
|
|
29
34
|
earlyHints?: string[];
|
|
30
35
|
htmlCacheControl?: string;
|
|
@@ -36,7 +41,7 @@ type WorkerManifest = {
|
|
|
36
41
|
services?: (env?: unknown) => unknown;
|
|
37
42
|
};
|
|
38
43
|
declare function createWorker(manifest: WorkerManifest): {
|
|
39
|
-
fetch(request: Request, env?: unknown): Promise<Response>;
|
|
44
|
+
fetch(request: Request, env?: unknown, ctx?: WorkerExecutionContext): Promise<Response>;
|
|
40
45
|
};
|
|
41
46
|
type AssetEnv = {
|
|
42
47
|
ASSETS?: {
|
|
@@ -44,13 +49,13 @@ type AssetEnv = {
|
|
|
44
49
|
};
|
|
45
50
|
};
|
|
46
51
|
type FetchPipeline = {
|
|
47
|
-
fetch(request: Request, env?: unknown): Promise<Response>;
|
|
52
|
+
fetch(request: Request, env?: unknown, ctx?: WorkerExecutionContext): Promise<Response>;
|
|
48
53
|
};
|
|
49
54
|
declare function withAssets(pipeline: FetchPipeline, opts?: {
|
|
50
55
|
link?: string | null;
|
|
51
56
|
}): {
|
|
52
|
-
fetch(request: Request, env?: AssetEnv): Promise<Response>;
|
|
57
|
+
fetch(request: Request, env?: AssetEnv, ctx?: WorkerExecutionContext): Promise<Response>;
|
|
53
58
|
};
|
|
54
59
|
declare function withDenoAssets(pipeline: FetchPipeline): (request: Request) => Promise<Response>;
|
|
55
60
|
//#endregion
|
|
56
|
-
export { WorkerManifest, createWorker, withAssets, withDenoAssets };
|
|
61
|
+
export { WorkerExecutionContext, WorkerManifest, createWorker, withAssets, withDenoAssets };
|
package/dist/worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { durableAgentSurface } from "./agent-durable.js";
|
|
1
|
+
import { durableAgentSurface, durableChannelSurface } from "./agent-durable.js";
|
|
2
2
|
import { createPipeline } from "./pipeline.js";
|
|
3
3
|
import { RESERVED_PREFIX, contentTypeFor, safeRelativePath } from "./static-files.js";
|
|
4
4
|
//#region src/worker.ts
|
|
@@ -61,15 +61,30 @@ function createWorker(manifest) {
|
|
|
61
61
|
const loadingFor = (key) => manifest.loadings?.[key];
|
|
62
62
|
const boundaryFor = (key) => manifest.layoutBoundaries?.[key] ?? null;
|
|
63
63
|
let currentEnv;
|
|
64
|
+
const requestLocals = /* @__PURE__ */ new WeakMap();
|
|
64
65
|
const provider = manifest.resources;
|
|
65
66
|
const servicesProvider = manifest.services;
|
|
66
67
|
let servicesCache;
|
|
67
68
|
const agentSurface = manifest.agent.runtime.enabled && manifest.agentName ? (() => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
const agentName = manifest.agentName;
|
|
70
|
+
const wantChannels = manifest.agent.runtime.channels !== false && (manifest.agentChannels?.length ?? 0) > 0;
|
|
71
|
+
return async (req) => {
|
|
72
|
+
const locals = requestLocals.get(req);
|
|
73
|
+
const ns = () => (locals?.env)?.AGENT;
|
|
74
|
+
const chatRes = await durableAgentSurface(ns, {
|
|
75
|
+
agentName,
|
|
76
|
+
chatPath: manifest.agent.runtime.chat.path
|
|
77
|
+
})(req);
|
|
78
|
+
if (chatRes) return chatRes;
|
|
79
|
+
if (!wantChannels) return null;
|
|
80
|
+
return durableChannelSurface(ns, {
|
|
81
|
+
agentName,
|
|
82
|
+
channels: manifest.agentChannels,
|
|
83
|
+
env: locals?.env,
|
|
84
|
+
services: servicesProvider,
|
|
85
|
+
waitUntil: locals?.ctx?.waitUntil?.bind(locals.ctx)
|
|
86
|
+
})(req);
|
|
87
|
+
};
|
|
73
88
|
})() : void 0;
|
|
74
89
|
const pipeline = createPipeline({
|
|
75
90
|
docConfig: manifest.document,
|
|
@@ -130,14 +145,18 @@ function createWorker(manifest) {
|
|
|
130
145
|
return null;
|
|
131
146
|
}
|
|
132
147
|
});
|
|
133
|
-
return { fetch(request, env) {
|
|
148
|
+
return { fetch(request, env, ctx) {
|
|
134
149
|
currentEnv = env;
|
|
150
|
+
requestLocals.set(request, {
|
|
151
|
+
env,
|
|
152
|
+
ctx
|
|
153
|
+
});
|
|
135
154
|
return pipeline.fetch(request);
|
|
136
155
|
} };
|
|
137
156
|
}
|
|
138
157
|
const estimateTokens = (s) => String(Math.ceil(s.length / 4));
|
|
139
158
|
function withAssets(pipeline, opts = {}) {
|
|
140
|
-
return { async fetch(request, env) {
|
|
159
|
+
return { async fetch(request, env, ctx) {
|
|
141
160
|
const assets = env?.ASSETS;
|
|
142
161
|
const url = new URL(request.url);
|
|
143
162
|
const isPagePath = !/\.[a-z0-9]+$/i.test(url.pathname);
|
|
@@ -177,7 +196,7 @@ function withAssets(pipeline, opts = {}) {
|
|
|
177
196
|
return a;
|
|
178
197
|
}
|
|
179
198
|
}
|
|
180
|
-
return pipeline.fetch(request, env);
|
|
199
|
+
return pipeline.fetch(request, env, ctx);
|
|
181
200
|
} };
|
|
182
201
|
}
|
|
183
202
|
function withDenoAssets(pipeline) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junejs/server",
|
|
3
|
-
"version": "1.0.0-dev.
|
|
3
|
+
"version": "1.0.0-dev.19",
|
|
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",
|
|
@@ -36,6 +36,12 @@
|
|
|
36
36
|
"types": "./dist/agent-discover.d.ts",
|
|
37
37
|
"default": "./dist/agent-discover.js"
|
|
38
38
|
},
|
|
39
|
+
"./agent-compile": {
|
|
40
|
+
"source": "./src/agent-compile.ts",
|
|
41
|
+
"bun": "./src/agent-compile.ts",
|
|
42
|
+
"types": "./dist/agent-compile.d.ts",
|
|
43
|
+
"default": "./dist/agent-compile.js"
|
|
44
|
+
},
|
|
39
45
|
"./db": {
|
|
40
46
|
"source": "./src/db.ts",
|
|
41
47
|
"bun": "./src/db.ts",
|
|
@@ -132,13 +138,14 @@
|
|
|
132
138
|
"rolldown": "^1.1.0"
|
|
133
139
|
},
|
|
134
140
|
"peerDependencies": {
|
|
135
|
-
"@junejs/core": ">=0.2.0-dev.
|
|
141
|
+
"@junejs/core": ">=0.2.0-dev.36",
|
|
136
142
|
"react": "^19.2.0",
|
|
137
143
|
"react-dom": "^19.2.0",
|
|
138
144
|
"react-server-dom-webpack": "19.2.7",
|
|
139
145
|
"pg": "^8.0.0",
|
|
140
146
|
"mysql2": "^3.0.0",
|
|
141
|
-
"@libsql/client": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0"
|
|
147
|
+
"@libsql/client": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0",
|
|
148
|
+
"@anthropic-ai/sdk": ">=0.60.0"
|
|
142
149
|
},
|
|
143
150
|
"peerDependenciesMeta": {
|
|
144
151
|
"react-server-dom-webpack": {
|
|
@@ -152,6 +159,9 @@
|
|
|
152
159
|
},
|
|
153
160
|
"@libsql/client": {
|
|
154
161
|
"optional": true
|
|
162
|
+
},
|
|
163
|
+
"@anthropic-ai/sdk": {
|
|
164
|
+
"optional": true
|
|
155
165
|
}
|
|
156
166
|
},
|
|
157
167
|
"repository": {
|
package/src/adapter.ts
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
// `workers()` is the zero-config default (this file, no import). Other targets
|
|
8
8
|
// are explicit `deploy: { adapter: vercel() }` from their own package.
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
|
-
import { cp, copyFile, mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import { cp, copyFile, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
11
11
|
import { basename, dirname, join } from "node:path";
|
|
12
12
|
|
|
13
13
|
import type { JuneConfig } from "@junejs/core/config";
|
|
14
|
+
import { stripJsonc } from "./tsconfig-jsx";
|
|
14
15
|
|
|
15
16
|
export type AdapterCapabilities = {
|
|
16
17
|
runtime: "edge" | "node" | "serverless" | "native" | "static";
|
|
@@ -20,6 +21,9 @@ export type AdapterCapabilities = {
|
|
|
20
21
|
persistentConnections: boolean;
|
|
21
22
|
// Who serves prerendered static files: the platform CDN, the server, or none.
|
|
22
23
|
assets: "platform" | "server" | "none";
|
|
24
|
+
// Durable Objects (the per-session agent actor). Only workers() today; the
|
|
25
|
+
// build skips the durable-agent mount (with a notice) when absent.
|
|
26
|
+
durableObjects?: boolean;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
// What the adapter contributes to the generated worker entry: extra imports and
|
|
@@ -37,6 +41,11 @@ export type ResourcePlan = {
|
|
|
37
41
|
// A declared `db` → a D1 binding named `binding`. The runtime provider
|
|
38
42
|
// (bindWorkerResources) reads env[binding]; keep in sync (default "DB").
|
|
39
43
|
db?: { binding: string; databaseName: string };
|
|
44
|
+
// A durable agent (an app/agent directory on a DO-capable target) → the DO
|
|
45
|
+
// namespace binding createWorker reads (env.AGENT) and the class the
|
|
46
|
+
// generated entry exports. workers() emits durable_objects + the SQLite-class
|
|
47
|
+
// migration from it.
|
|
48
|
+
agent?: { binding: string; className: string };
|
|
40
49
|
};
|
|
41
50
|
|
|
42
51
|
export type AdapterEmitContext = {
|
|
@@ -77,13 +86,60 @@ export interface JuneAdapter {
|
|
|
77
86
|
emit(ctx: AdapterEmitContext): Promise<void>;
|
|
78
87
|
}
|
|
79
88
|
|
|
89
|
+
// Whether an app-owned wrangler config actually BINDS the generated DO class
|
|
90
|
+
// under the binding name createWorker reads (env.AGENT) — not merely mentions
|
|
91
|
+
// the class (a migrations entry does that too, and a binding under another
|
|
92
|
+
// name never reaches the chat route). JSONC is parsed for the real
|
|
93
|
+
// durable_objects.bindings entry; TOML is scanned per
|
|
94
|
+
// [[durable_objects.bindings]] table with comments stripped, so a
|
|
95
|
+
// commented-out binding never counts. The TOML scan covers wrangler's
|
|
96
|
+
// canonical table form; an exotic inline-table config may draw a spurious
|
|
97
|
+
// (advisory) warning, never a suppressed one. Pure — unit-tested directly.
|
|
98
|
+
export function hasDurableBinding(text: string, file: string, binding: string, className: string): boolean {
|
|
99
|
+
if (file.endsWith(".toml")) {
|
|
100
|
+
// Strip comments: '#' to end of line, unless inside a double-quoted string.
|
|
101
|
+
const lines = text.split("\n").map((line) => {
|
|
102
|
+
let inString = false;
|
|
103
|
+
for (let i = 0; i < line.length; i++) {
|
|
104
|
+
const ch = line[i];
|
|
105
|
+
if (ch === '"' && line[i - 1] !== "\\") inString = !inString;
|
|
106
|
+
else if (ch === "#" && !inString) return line.slice(0, i);
|
|
107
|
+
}
|
|
108
|
+
return line;
|
|
109
|
+
});
|
|
110
|
+
let inBindings = false;
|
|
111
|
+
let name: string | undefined;
|
|
112
|
+
let cls: string | undefined;
|
|
113
|
+
const matches = () => name === binding && cls === className;
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
const t = line.trim();
|
|
116
|
+
if (t.startsWith("[")) {
|
|
117
|
+
if (inBindings && matches()) return true; // the table that just ended bound it
|
|
118
|
+
inBindings = /^\[\[\s*durable_objects\.bindings\s*\]\]$/.test(t);
|
|
119
|
+
name = cls = undefined;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!inBindings) continue;
|
|
123
|
+
const kv = t.match(/^(name|class_name)\s*=\s*"([^"]*)"/);
|
|
124
|
+
if (kv) kv[1] === "name" ? (name = kv[2]) : (cls = kv[2]);
|
|
125
|
+
}
|
|
126
|
+
return inBindings && matches();
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(stripJsonc(text)) as { durable_objects?: { bindings?: { name?: string; class_name?: string }[] } };
|
|
130
|
+
return (parsed.durable_objects?.bindings ?? []).some((b) => b.name === binding && b.class_name === className);
|
|
131
|
+
} catch {
|
|
132
|
+
return text.includes(className); // unparsable — loose fallback, never crash the build
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
80
136
|
// The built-in default. Reproduces exactly what build.ts emitted inline:
|
|
81
137
|
// withAssets(pipeline) as the entry, and a wrangler.jsonc with the assets
|
|
82
138
|
// binding (run_worker_first) + optional custom domain.
|
|
83
139
|
export function workers(opts?: { name?: string; domain?: string }): JuneAdapter {
|
|
84
140
|
return {
|
|
85
141
|
name: "workers",
|
|
86
|
-
capabilities: { runtime: "edge", persistentConnections: true, assets: "platform" },
|
|
142
|
+
capabilities: { runtime: "edge", persistentConnections: true, assets: "platform", durableObjects: true },
|
|
87
143
|
conditions: ["source", "workerd", "edge", "import", "default"],
|
|
88
144
|
// workers-og is a workerd-native WASM package: rolldown cannot bundle its
|
|
89
145
|
// .wasm assets (wrangler's own bundler applies the CompiledWasm rule). Mark
|
|
@@ -100,7 +156,23 @@ export function workers(opts?: { name?: string; domain?: string }): JuneAdapter
|
|
|
100
156
|
|
|
101
157
|
async emit({ appRoot, outDir, hasAssets, config, plan, defaultName }) {
|
|
102
158
|
// An app that manages its own wrangler config wins — don't overwrite it.
|
|
103
|
-
|
|
159
|
+
// But a durable agent NEEDS its DO binding: warn when the app-owned config
|
|
160
|
+
// doesn't BIND the generated class under the name createWorker reads
|
|
161
|
+
// (hasDurableBinding above — a migrations-only mention, a binding under
|
|
162
|
+
// another name, or a commented-out table must all still warn), instead of
|
|
163
|
+
// silently shipping a worker whose chat surface 404s.
|
|
164
|
+
const own = ["wrangler.toml", "wrangler.jsonc"].map((f) => join(appRoot, f)).find(existsSync);
|
|
165
|
+
if (own) {
|
|
166
|
+
if (plan.agent) {
|
|
167
|
+
const cls = plan.agent.className;
|
|
168
|
+
const bound = hasDurableBinding(await readFile(own, "utf8"), own, plan.agent.binding, cls);
|
|
169
|
+
if (!bound) {
|
|
170
|
+
const snippet = own.endsWith(".toml")
|
|
171
|
+
? `[[durable_objects.bindings]]\nname = "${plan.agent.binding}"\nclass_name = "${cls}"\n\n[[migrations]]\ntag = "v1"\nnew_sqlite_classes = ["${cls}"]`
|
|
172
|
+
: `"durable_objects": { "bindings": [{ "name": "${plan.agent.binding}", "class_name": "${cls}" }] },\n"migrations": [{ "tag": "v1", "new_sqlite_classes": ["${cls}"] }]`;
|
|
173
|
+
console.warn(`[june build] durable agent built, but ${basename(own)} doesn't bind it — add:\n${snippet}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
104
176
|
return;
|
|
105
177
|
}
|
|
106
178
|
const deployCfg = config.deploy;
|
|
@@ -133,6 +205,15 @@ export function workers(opts?: { name?: string; domain?: string }): JuneAdapter
|
|
|
133
205
|
],
|
|
134
206
|
}
|
|
135
207
|
: {}),
|
|
208
|
+
// The durable agent: one SQLite-backed DO class = the per-session
|
|
209
|
+
// AgentSession actor (its ctx.storage.sql is the durable loop's
|
|
210
|
+
// synchronous store). The binding name is what createWorker reads.
|
|
211
|
+
...(plan.agent
|
|
212
|
+
? {
|
|
213
|
+
durable_objects: { bindings: [{ name: plan.agent.binding, class_name: plan.agent.className }] },
|
|
214
|
+
migrations: [{ tag: "v1", new_sqlite_classes: [plan.agent.className] }],
|
|
215
|
+
}
|
|
216
|
+
: {}),
|
|
136
217
|
// config deploy.domain → a Workers custom domain; without it the
|
|
137
218
|
// regenerated file would silently drop a hand-attached domain.
|
|
138
219
|
...(opts?.domain ?? deployCfg?.domain
|