@lunora/codegen 1.0.0-alpha.42 → 1.0.0-alpha.44

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.
Files changed (25) hide show
  1. package/dist/index.d.mts +155 -11
  2. package/dist/index.d.ts +155 -11
  3. package/dist/index.mjs +19 -18
  4. package/dist/packem_shared/AGENTS_FILENAME-BksnAJgx.mjs +116 -0
  5. package/dist/packem_shared/{FLAGS_FILENAME-fEZtzWXi.mjs → FLAGS_FILENAME-Dg4mKUuz.mjs} +1 -1
  6. package/dist/packem_shared/GENERATED_HEADER-7W9f2IG3.mjs +4 -0
  7. package/dist/packem_shared/{OPENRPC_VERSION-CzhfvCIQ.mjs → OPENRPC_VERSION-Cyttekkt.mjs} +1 -1
  8. package/dist/packem_shared/{SCHEMA_SNAPSHOT_FILENAME-Cl1EBMdR.mjs → SCHEMA_SNAPSHOT_FILENAME-CMjGZSa1.mjs} +80 -21
  9. package/dist/packem_shared/{buildOpenApiDocument-bg-NnLka.mjs → buildOpenApiDocument-saZ_-kl4.mjs} +1 -1
  10. package/dist/packem_shared/{discoverAuthApiCalls-WMx8L2FG.mjs → discoverAuthApiCalls-Dx3K42rk.mjs} +1 -1
  11. package/dist/packem_shared/{discoverCrons-D-jrpm97.mjs → discoverCrons-_aosTRLr.mjs} +36 -21
  12. package/dist/packem_shared/{discoverFunctions-CZ91aenA.mjs → discoverFunctions-BJ-qR7Rg.mjs} +1 -1
  13. package/dist/packem_shared/{discoverHttpRoutes-DNZLDCmm.mjs → discoverHttpRoutes-daCzuTe8.mjs} +9 -1
  14. package/dist/packem_shared/{discoverInserts-DFRJhmCv.mjs → discoverInserts-DI4q5NxE.mjs} +1 -1
  15. package/dist/packem_shared/{discoverMaskProcedures-B5iFnZ-X.mjs → discoverMaskProcedures-BcTOEKNU.mjs} +1 -1
  16. package/dist/packem_shared/{discoverMigrations-CdKtMlBc.mjs → discoverMigrations-VNUFvCwr.mjs} +1 -1
  17. package/dist/packem_shared/{discoverNondeterministicCalls-C_PrQPkA.mjs → discoverNondeterministicCalls-S0N2xLCq.mjs} +1 -1
  18. package/dist/packem_shared/{discoverQueries-B8W9UPrt.mjs → discoverQueries-CJnnnLpd.mjs} +1 -1
  19. package/dist/packem_shared/{discoverR2sqlCalls-Bm7Rfnf4.mjs → discoverR2sqlCalls-pnpicWfz.mjs} +1 -1
  20. package/dist/packem_shared/{discoverRlsMetadata-B4GL4ZKm.mjs → discoverRlsMetadata-DppniPUH.mjs} +1 -1
  21. package/dist/packem_shared/{discoverStorageRulesMetadata-nosjYcvN.mjs → discoverStorageRulesMetadata-CnHl2rXD.mjs} +1 -1
  22. package/dist/packem_shared/{emit-31Yg1Ee5.mjs → emit-C_29WVlW.mjs} +328 -31
  23. package/dist/packem_shared/{emitApp-DCP7Pr6M.mjs → emitApp-C9oljiZd.mjs} +25 -2
  24. package/package.json +9 -8
  25. package/dist/packem_shared/GENERATED_HEADER-D6RK1WF1.mjs +0 -3
@@ -1,3 +1,4 @@
1
+ import { agentComponent } from '@lunora/agent/component';
1
2
  import { LunoraError } from '@lunora/errors';
2
3
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
4
 
@@ -696,19 +697,19 @@ ${members}
696
697
  };
697
698
  return [...namespaces.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map((entry) => renderNamespace(entry)).join("\n");
698
699
  };
699
- const renderWorkflowsRef = (workflows) => {
700
- if (workflows.length === 0) {
700
+ const renderSchedulerReferences = (workflows, agents) => {
701
+ if (workflows.length === 0 && agents.length === 0) {
701
702
  return { block: "", importLine: "" };
702
703
  }
703
- const sorted = [...workflows].toSorted((a, b) => a.exportName.localeCompare(b.exportName));
704
- const refMembers = sorted.map((workflow) => ` ${workflow.exportName}: WorkflowReference<WorkflowParamsOf<typeof lunoraWorkflowDefinitions.${workflow.exportName}>>;`).join("\n");
705
- const objectMembers = sorted.map(
706
- (workflow) => ` ${workflow.exportName}: { isLunoraWorkflow: true, binding: ${JSON.stringify(workflow.bindingName)}, name: ${JSON.stringify(workflow.exportName)} },`
707
- ).join("\n");
708
- const block = `
704
+ const importLines = [];
705
+ let block = "";
706
+ if (workflows.length > 0) {
707
+ block += `
709
708
  /** Params type carried by a \`defineWorkflow\` definition (its phantom \`__params\`). */
710
709
  type WorkflowParamsOf<Definition> = Definition extends { __params?: infer Params } ? (unknown extends Params ? Record<string, unknown> : Params) : Record<string, unknown>;
711
-
710
+ `;
711
+ }
712
+ block += `
712
713
  /** A typed reference to a durable workflow, addressable for \`cronJobs()\` targets. Mirrors \`@lunora/scheduler\`'s \`WorkflowReference\` structurally. */
713
714
  export interface WorkflowReference<Params = Record<string, unknown>> {
714
715
  readonly isLunoraWorkflow: true;
@@ -716,7 +717,14 @@ export interface WorkflowReference<Params = Record<string, unknown>> {
716
717
  readonly binding: string;
717
718
  readonly name: string;
718
719
  }
719
-
720
+ `;
721
+ if (workflows.length > 0) {
722
+ const sorted = [...workflows].toSorted((a, b) => a.exportName.localeCompare(b.exportName));
723
+ const refMembers = sorted.map((workflow) => ` ${workflow.exportName}: WorkflowReference<WorkflowParamsOf<typeof lunoraWorkflowDefinitions.${workflow.exportName}>>;`).join("\n");
724
+ const objectMembers = sorted.map(
725
+ (workflow) => ` ${workflow.exportName}: { isLunoraWorkflow: true, binding: ${JSON.stringify(workflow.bindingName)}, name: ${JSON.stringify(workflow.exportName)} },`
726
+ ).join("\n");
727
+ block += `
720
728
  /** This project's durable workflows, addressable as typed references (e.g. \`crons.daily("digest", …, workflows.digestPipeline, params)\`). */
721
729
  export interface WorkflowsRef {
722
730
  ${refMembers}
@@ -726,17 +734,189 @@ export const workflows: WorkflowsRef = {
726
734
  ${objectMembers}
727
735
  };
728
736
  `;
729
- return { block, importLine: `import type * as lunoraWorkflowDefinitions from "../workflows.js";
730
- ` };
737
+ importLines.push(`import type * as lunoraWorkflowDefinitions from "../workflows.js";
738
+ `);
739
+ }
740
+ if (agents.length > 0) {
741
+ const sorted = [...agents].toSorted((a, b) => a.exportName.localeCompare(b.exportName));
742
+ const refMembers = sorted.map((agent) => ` ${agent.exportName}: WorkflowReference<AgentRunInput>;`).join("\n");
743
+ const objectMembers = sorted.map(
744
+ (agent) => ` ${agent.exportName}: { isLunoraWorkflow: true, binding: ${JSON.stringify(agent.bindingName)}, name: ${JSON.stringify(agent.name)} },`
745
+ ).join("\n");
746
+ block += `
747
+ /** This project's durable agents, addressable as typed references for \`cronJobs()\` — each fire starts a fresh agent run (e.g. \`crons.daily("sweep", …, agents.support, { input, threadKey })\`). */
748
+ export interface AgentsRef {
749
+ ${refMembers}
750
+ }
751
+
752
+ export const agents: AgentsRef = {
753
+ ${objectMembers}
754
+ };
755
+ `;
756
+ importLines.push(`import type { AgentRunInput } from "@lunora/agent";
757
+ `);
758
+ }
759
+ return { block, importLine: importLines.join("") };
760
+ };
761
+ const agentRuntimeFunctionNames = () => Object.keys(agentComponent().functions).toSorted((a, b) => a.localeCompare(b));
762
+ const takenAgentFunctionNames = (functions) => new Set(functions.filter((definition) => sanitizeNamespace(definition.filePath) === "agents").map((definition) => definition.exportName));
763
+ const renderAgentFunctionRegistry = (agents, functions) => {
764
+ const empty = { importLine: "", lines: "", prelude: "" };
765
+ if (agents.length === 0) {
766
+ return empty;
767
+ }
768
+ const taken = takenAgentFunctionNames(functions);
769
+ const names = agentRuntimeFunctionNames().filter((name) => !taken.has(name));
770
+ if (names.length === 0) {
771
+ return empty;
772
+ }
773
+ return {
774
+ importLine: 'import { agentComponent } from "@lunora/agent/component";\n',
775
+ lines: names.map((name) => ` "agents:${name}": lunoraAgentRuntimeFunctions.${name} as unknown as RegisteredLunoraFunction,`).join("\n"),
776
+ prelude: "\n/**\n * The `@lunora/agent` runtime component — auto-registered because `lunora/agents.ts`\n * declares agents. The durable loop dispatches the internal mutations; clients\n * subscribe to the public queries (`agents:agentMessages` is the live thread view).\n */\nconst lunoraAgentRuntimeFunctions = agentComponent().functions;\n"
777
+ };
778
+ };
779
+ const renderSandboxFunctionRegistry = (usesSandbox, functions) => {
780
+ if (!usesSandbox) {
781
+ return { importLine: "", lines: "", prelude: "" };
782
+ }
783
+ const collision = functions.find((definition) => sanitizeNamespace(definition.filePath) === "sandbox" && definition.exportName === "invoke");
784
+ if (collision) {
785
+ throw new LunoraError(
786
+ "INTERNAL",
787
+ `@lunora/codegen: "sandbox:invoke" is reserved for the batteries-included sandbox tool dispatcher (browserTool/containerTool) — rename the "invoke" export in ${collision.filePath}`
788
+ );
789
+ }
790
+ return {
791
+ importLine: 'import { sandboxComponent } from "@lunora/agent/component";\n',
792
+ lines: ' "sandbox:invoke": lunoraSandbox.invoke as unknown as RegisteredLunoraFunction,',
793
+ prelude: "\n/**\n * The `@lunora/agent` sandbox dispatcher — auto-registered because `lunora/`\n * imports a sandbox tool (`browserTool`/`containerTool`). The batteries-included\n * tools dispatch to this internal action, which runs on an action ctx carrying\n * `ctx.browser` + `ctx.containers` (the durable tool step itself has neither).\n */\nconst lunoraSandbox = sandboxComponent();\n"
794
+ };
731
795
  };
732
- const emitApi = (functions, workflows = [], useUmbrella = false) => {
796
+ const syntheticAgentApiFunctions = (agents, functions) => {
797
+ if (agents.length === 0) {
798
+ return [];
799
+ }
800
+ const taken = takenAgentFunctionNames(functions);
801
+ const definitions = [
802
+ {
803
+ args: { key: { kind: "string" }, limit: { inner: { kind: "number" }, kind: "optional" } },
804
+ exportName: "agentMessages",
805
+ filePath: "agents",
806
+ kind: "query",
807
+ returnType: "Record<string, unknown>[]"
808
+ },
809
+ {
810
+ args: {
811
+ decision: {
812
+ kind: "union",
813
+ members: [
814
+ { kind: "literal", literalValue: '"approve"' },
815
+ { kind: "literal", literalValue: '"reject"' }
816
+ ]
817
+ },
818
+ instanceId: { kind: "string" },
819
+ note: { inner: { kind: "string" }, kind: "optional" },
820
+ threadKey: { kind: "string" },
821
+ toolCallId: { kind: "string" }
822
+ },
823
+ exportName: "agentResolveApproval",
824
+ filePath: "agents",
825
+ kind: "mutation",
826
+ returnType: "{ resolved: boolean }"
827
+ },
828
+ {
829
+ args: {
830
+ agent: { kind: "string" },
831
+ input: { kind: "string" },
832
+ threadKey: { kind: "string" },
833
+ title: { inner: { kind: "string" }, kind: "optional" }
834
+ },
835
+ exportName: "agentRun",
836
+ filePath: "agents",
837
+ kind: "mutation",
838
+ returnType: "{ id: string; threadKey: string }"
839
+ },
840
+ {
841
+ args: { key: { kind: "string" } },
842
+ exportName: "agentState",
843
+ filePath: "agents",
844
+ kind: "query",
845
+ returnType: "Record<string, unknown> | undefined"
846
+ },
847
+ {
848
+ args: { key: { kind: "string" } },
849
+ exportName: "agentThread",
850
+ filePath: "agents",
851
+ kind: "query",
852
+ returnType: "Record<string, unknown> | undefined"
853
+ }
854
+ ];
855
+ for (const agent of agents) {
856
+ if (agent.voice) {
857
+ definitions.push({
858
+ args: { threadKey: { kind: "string" } },
859
+ exportName: `${agent.exportName}Voice`,
860
+ filePath: "agents",
861
+ kind: "stream",
862
+ returnType: "Record<string, unknown>"
863
+ });
864
+ }
865
+ }
866
+ return definitions.filter((definition) => !taken.has(definition.exportName));
867
+ };
868
+ const renderHttpStreamsRef = (httpRoutes) => {
869
+ const streams = httpRoutes.filter((route) => route.stream);
870
+ if (streams.length === 0) {
871
+ return { block: "", body: "" };
872
+ }
873
+ const namespaces = /* @__PURE__ */ new Map();
874
+ for (const route of streams) {
875
+ const list = namespaces.get(route.filePath) ?? [];
876
+ list.push(route);
877
+ namespaces.set(route.filePath, list);
878
+ }
879
+ const sortedNamespaces = [...namespaces.entries()].toSorted(([a], [b]) => a.localeCompare(b));
880
+ const typeBody = sortedNamespaces.map(([file, list]) => {
881
+ const members = list.map((route) => {
882
+ const chunkType = relocateGeneratedImports(route.chunkType ?? "unknown");
883
+ return ` ${renderPropertyKey(route.exportName)}: HttpStreamRef<${chunkType}, ${renderArgsType(route.searchParams)}, ${renderArgsType(route.params)}>;`;
884
+ }).join("\n");
885
+ return ` ${renderPropertyKey(sanitizeNamespace(file))}: {
886
+ ${members}
887
+ };`;
888
+ }).join("\n");
889
+ const valueBody = sortedNamespaces.map(([file, list]) => {
890
+ const members = list.map(
891
+ (route) => ` ${renderPropertyKey(route.exportName)}: { method: ${JSON.stringify(route.method)}, path: ${JSON.stringify(route.path)} },`
892
+ ).join("\n");
893
+ return ` ${renderPropertyKey(sanitizeNamespace(file))}: {
894
+ ${members}
895
+ },`;
896
+ }).join("\n");
897
+ const block = `
898
+ /** This project's HTTP-SSE stream routes (\`httpRoute.<verb>(path).stream()\`), addressable as typed references for \`client.httpStream\` / \`useHttpStream\`. */
899
+ export interface HttpStreamsRef {
900
+ ${typeBody}
901
+ }
902
+
903
+ export const httpStreams: HttpStreamsRef = {
904
+ ${valueBody}
905
+ };
906
+ `;
907
+ return { block, body: typeBody };
908
+ };
909
+ const emitApi = (options) => {
910
+ const { agents = [], functions, httpRoutes = [], useUmbrella = false, workflows = [] } = options;
733
911
  const base = baseSpecifiers(useUmbrella);
734
- const publicFunctions = functions.filter((definition) => definition.visibility !== "internal");
912
+ const publicFunctions = [...functions.filter((definition) => definition.visibility !== "internal"), ...syntheticAgentApiFunctions(agents, functions)];
735
913
  const internalFunctions = functions.filter((definition) => definition.visibility === "internal");
736
914
  const publicBody = renderApiBody(publicFunctions);
737
915
  const internalBody = renderApiBody(internalFunctions);
916
+ const httpStreamsRef = renderHttpStreamsRef(httpRoutes);
738
917
  const combinedBody = `${publicBody}
739
- ${internalBody}`;
918
+ ${internalBody}
919
+ ${httpStreamsRef.body}`;
740
920
  const dataModelImports = referencedDataModelImports(combinedBody);
741
921
  const dataModelImportLine = dataModelImports.length > 0 ? `
742
922
  import type { ${dataModelImports.join(", ")} } from "./dataModel.js";
@@ -747,10 +927,11 @@ ${publicBody}
747
927
  const internalBlock = internalBody ? `
748
928
  ${internalBody}
749
929
  ` : "";
750
- const workflowsRef = renderWorkflowsRef(workflows);
930
+ const schedulerReferences = renderSchedulerReferences(workflows, agents);
931
+ const clientImports = httpStreamsRef.block === "" ? "FunctionReference" : "FunctionReference, HttpStreamRef";
751
932
  return `${GENERATED_HEADER}import { anyApi } from "${base.serverTypes}";
752
- import type { FunctionReference } from "${base.client}";
753
- ${workflowsRef.importLine}${dataModelImportLine}
933
+ import type { ${clientImports} } from "${base.client}";
934
+ ${schedulerReferences.importLine}${dataModelImportLine}
754
935
  export interface ApiTypes {${apiBlock}}
755
936
 
756
937
  export const api = anyApi as unknown as ApiTypes;
@@ -759,7 +940,7 @@ export const api = anyApi as unknown as ApiTypes;
759
940
  export interface InternalApiTypes {${internalBlock}}
760
941
 
761
942
  export const internal = anyApi as unknown as InternalApiTypes;
762
- ${workflowsRef.block}`;
943
+ ${schedulerReferences.block}${httpStreamsRef.block}`;
763
944
  };
764
945
  const emitSeed = (enabled) => {
765
946
  if (!enabled) {
@@ -928,6 +1109,7 @@ const buildStorageBucketNames = (schema, ruleBuckets = []) => {
928
1109
  return ["default", ...[...named].toSorted((a, b) => a.localeCompare(b))];
929
1110
  };
930
1111
  const emitServer = ({
1112
+ agents = [],
931
1113
  containers = [],
932
1114
  env,
933
1115
  hasAccessFacade = false,
@@ -986,6 +1168,11 @@ const emitServer = ({
986
1168
  assertIdentifier(queue.bindingName, `queue binding "${queue.bindingName}"`);
987
1169
  return ` /** Queue producer binding for the \`${queue.exportName}\` queue. */
988
1170
  readonly ${queue.bindingName}?: unknown;`;
1171
+ }),
1172
+ ...agents.map((agent) => {
1173
+ assertIdentifier(agent.bindingName, `agent binding "${agent.bindingName}"`);
1174
+ return ` /** Workflow binding for the \`${agent.exportName}\` agent. */
1175
+ readonly ${agent.bindingName}?: unknown;`;
989
1176
  })
990
1177
  ].join("\n");
991
1178
  const envBlock = `
@@ -1056,6 +1243,20 @@ ${queues.map((queue) => ` readonly ${queue.exportName}: QueueProducer<QueueBo
1056
1243
  }` : "";
1057
1244
  const queuesContextField = hasQueues ? `
1058
1245
  readonly queues: LunoraQueues;` : "";
1246
+ const hasAgents = agents.length > 0;
1247
+ const agentsTypeImport = hasAgents ? `import type { AgentHandle } from "@lunora/agent";
1248
+ ` : "";
1249
+ const agentsTypeBlock = hasAgents ? `
1250
+
1251
+ /** This project's declared agents, addressable from \`ctx.agents\` by their \`lunora/agents.ts\` export name. */
1252
+ export interface LunoraAgents {
1253
+ ${agents.map((agent) => {
1254
+ assertIdentifier(agent.exportName, `agent export "${agent.exportName}"`);
1255
+ return ` readonly ${agent.exportName}: AgentHandle;`;
1256
+ }).join("\n")}
1257
+ }` : "";
1258
+ const agentsContextField = hasAgents ? `
1259
+ readonly agents: LunoraAgents;` : "";
1059
1260
  const identityTypeImport = identity ? `import type { InferIdentity } from "${base.server}";
1060
1261
  import type * as lunoraIdentityContract from "../identity.js";
1061
1262
  ` : "";
@@ -1101,11 +1302,11 @@ import type {
1101
1302
  } from "${base.server}";
1102
1303
 
1103
1304
  import type { DataModel, DatabaseReaderFacade, DatabaseWriterFacade, Doc, Id as IdOfTable, OrmReader, OrmWriter, Relations, TableName } from "./dataModel.js";
1104
- ${aiTypeImport}${paymentsTypeImport}${x402TypeImport}${containersTypeImport}${workflowsTypeImport}${queuesTypeImport}${identityTypeImport}${envTypeImport}
1305
+ ${aiTypeImport}${paymentsTypeImport}${x402TypeImport}${containersTypeImport}${workflowsTypeImport}${queuesTypeImport}${agentsTypeImport}${identityTypeImport}${envTypeImport}
1105
1306
  export type { DataModel, Doc, Id, TableName } from "./dataModel.js";
1106
1307
 
1107
1308
  /** Storage buckets this schema declares (\`v.storage("name")\`), narrowing \`ctx.storage.bucket(name)\`. */
1108
- export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}${queuesTypeBlock}${identityTypeBlock}${envTypeBlock}
1309
+ export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}${queuesTypeBlock}${agentsTypeBlock}${identityTypeBlock}${envTypeBlock}
1109
1310
 
1110
1311
  /**
1111
1312
  * Project-typed contexts. The base contexts from \`@lunora/server\` are
@@ -1143,13 +1344,13 @@ export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"${authOmit}
1143
1344
  export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${workflowsOmit}${authOmit}${envOmit}> {
1144
1345
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
1145
1346
  readonly orm: OrmWriter;
1146
- readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}${envContextField}${workflowsContextField}${queuesContextField}${authContextField}
1347
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}${envContextField}${workflowsContextField}${queuesContextField}${agentsContextField}${authContextField}
1147
1348
  }
1148
1349
 
1149
1350
  export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${workflowsOmit}${authOmit}${envOmit}> {
1150
1351
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
1151
1352
  readonly orm: OrmWriter;
1152
- readonly storage: StorageBase<StorageBucketName>;${accessContextField}${aiActionField}${paymentsActionField}${x402ActionField}${containersActionField}${kvContextField}${flagsContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${envContextField}${workflowsContextField}${queuesContextField}${authContextField}
1353
+ readonly storage: StorageBase<StorageBucketName>;${accessContextField}${aiActionField}${paymentsActionField}${x402ActionField}${containersActionField}${kvContextField}${flagsContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${envContextField}${workflowsContextField}${queuesContextField}${agentsContextField}${authContextField}
1153
1354
  }
1154
1355
 
1155
1356
  /**
@@ -1219,10 +1420,22 @@ const renderLifecycleManifest = (functions) => {
1219
1420
  }
1220
1421
  return manifest;
1221
1422
  };
1222
- const emitFunctions = (functions, migrations = [], useUmbrella = false, mutators = [], shapes = []) => {
1423
+ const emitFunctions = (options) => {
1424
+ const { agents = [], functions, migrations = [], mutators = [], shapes = [], useUmbrella = false, usesSandbox = false } = options;
1223
1425
  const hasFunctions = functions.length > 0;
1224
1426
  const base = baseSpecifiers(useUmbrella);
1225
1427
  const { dispatchBody, importBlock, installBlock, migrationBody, mutatorPaths, shapeBody } = renderFunctionRegistry(functions, migrations, mutators, shapes);
1428
+ const agentRegistry = renderAgentFunctionRegistry(agents, functions);
1429
+ const sandboxRegistry = renderSandboxFunctionRegistry(usesSandbox, functions);
1430
+ const autoLines = [agentRegistry.lines, sandboxRegistry.lines].filter((block) => block.length > 0).join("\n");
1431
+ let dispatchBodyWithAgents = dispatchBody;
1432
+ if (autoLines.length > 0) {
1433
+ dispatchBodyWithAgents = dispatchBody.length > 0 ? `${dispatchBody.trimEnd()}
1434
+ ${autoLines}
1435
+ ` : `
1436
+ ${autoLines}
1437
+ `;
1438
+ }
1226
1439
  const lifecycleHooks = renderLifecycleManifest(functions);
1227
1440
  const shapeTypeImport = shapes.length > 0 ? `import type { RegisteredShape } from "${base.server}";
1228
1441
  ` : "";
@@ -1267,7 +1480,7 @@ ${caller.implementation}
1267
1480
  const callRegisteredHelper = hasFunctions ? `${CALL_REGISTERED_HELPER}
1268
1481
 
1269
1482
  ` : "";
1270
- return `${GENERATED_HEADER}${importBlock}${compiledArgsImport}${shapeTypeImport}import { LunoraError } from "${base.server}";
1483
+ return `${GENERATED_HEADER}${importBlock}${agentRegistry.importLine}${sandboxRegistry.importLine}${compiledArgsImport}${shapeTypeImport}import { LunoraError } from "${base.server}";
1271
1484
  import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
1272
1485
  ${dataModelImport}
1273
1486
  /**
@@ -1288,12 +1501,12 @@ export interface RegisteredLunoraFunction {
1288
1501
  /** \`"internal"\` functions are rejected on the external RPC path; absence === public. */
1289
1502
  visibility?: "internal" | "public";
1290
1503
  }
1291
-
1504
+ ${agentRegistry.prelude}${sandboxRegistry.prelude}
1292
1505
  /**
1293
1506
  * Static dispatch table. The key matches the \`__lunoraRef\` the client
1294
1507
  * emits (\`api[namespace][fn].__lunoraRef === "namespace:fn"\`).
1295
1508
  */
1296
- export const LUNORA_FUNCTIONS: Record<string, RegisteredLunoraFunction> = {${dispatchBody}};
1509
+ export const LUNORA_FUNCTIONS: Record<string, RegisteredLunoraFunction> = {${dispatchBodyWithAgents}};
1297
1510
  ${compiledArgsInstall}${shapeRegistry}${mutatorPathsRegistry}
1298
1511
  /**
1299
1512
  * Connection-lifecycle manifest: the function paths the generated ShardDO
@@ -1967,6 +2180,62 @@ type WorkflowParamsOf<Definition> = Definition extends { __params?: infer Params
1967
2180
  /** Output type carried by a \`defineWorkflow\` definition (its phantom \`__output\`), so each entrypoint keeps the authored return type. */
1968
2181
  type WorkflowOutputOf<Definition> = Definition extends { __output?: infer Output } ? Output : unknown;
1969
2182
 
2183
+ ${classes}`;
2184
+ };
2185
+ const emitAgents = (agents) => {
2186
+ if (agents.length === 0) {
2187
+ return "";
2188
+ }
2189
+ const voiceAgents = agents.filter((agent) => agent.voice);
2190
+ const hasVoice = voiceAgents.length > 0;
2191
+ const classes = agents.map((agent) => {
2192
+ assertIdentifier(agent.exportName, `agent export "${agent.exportName}"`);
2193
+ assertIdentifier(agent.className, `agent class "${agent.className}"`);
2194
+ const workflowClass = `/** WorkflowEntrypoint for the \`${agent.exportName}\` agent (binding \`${agent.bindingName}\`). */
2195
+ export class ${agent.className} extends LunoraWorkflow<AgentRunInput, AgentRunResult> {
2196
+ public constructor(ctx: ConstructorParameters<typeof LunoraWorkflow>[0], env: Record<string, unknown>) {
2197
+ super(ctx, env, compileAgentWorkflow(${agent.exportName}, "${agent.exportName}"), "${agent.exportName}");
2198
+ }
2199
+ }
2200
+ `;
2201
+ if (!agent.voice) {
2202
+ return workflowClass;
2203
+ }
2204
+ const voiceClassName = agent.voiceClassName ?? "";
2205
+ const voiceBindingName = agent.voiceBindingName ?? "";
2206
+ assertIdentifier(voiceClassName, `agent voice class "${voiceClassName}"`);
2207
+ const voiceClass = `
2208
+ /** Voice-session Durable Object for the \`${agent.exportName}\` agent (binding \`${voiceBindingName}\`). */
2209
+ export class ${voiceClassName} extends VoiceSessionDO {
2210
+ public constructor(ctx: ConstructorParameters<typeof VoiceSessionDO>[0], env: Record<string, unknown>) {
2211
+ super(ctx, env, ${agent.exportName}, "${agent.exportName}");
2212
+ }
2213
+ }
2214
+ `;
2215
+ return `${workflowClass}${voiceClass}`;
2216
+ }).join("\n");
2217
+ const imports = agents.map((agent) => agent.exportName).join(", ");
2218
+ const runtimeImport = hasVoice ? `import { compileAgentWorkflow, VoiceSessionDO } from "@lunora/agent";` : `import { compileAgentWorkflow } from "@lunora/agent";`;
2219
+ const voiceHeaderNote = hasVoice ? `
2220
+ *
2221
+ * Voice-enabled agents ALSO get a \`VoiceSessionDO\` subclass here — a real
2222
+ * Durable Object (unlike the Workflow), so wrangler needs both its
2223
+ * \`durable_objects\` binding and its \`class_name\` export (the same re-export
2224
+ * line covers it).` : "";
2225
+ return `${GENERATED_HEADER}/**
2226
+ * WorkflowEntrypoint classes for the agents declared in \`lunora/agents.ts\`.
2227
+ * Each \`defineAgent\` compiles a replay-safe tool-loop onto a Cloudflare
2228
+ * Workflow. Re-export them from your worker entry — wrangler requires each
2229
+ * \`workflows[].class_name\` to be exported by the worker:
2230
+ *
2231
+ * \`export * from "./lunora/_generated/agents.js";\`${voiceHeaderNote}
2232
+ */
2233
+ import LunoraWorkflow from "@lunora/workflow/do";
2234
+ ${runtimeImport}
2235
+ import type { AgentRunInput, AgentRunResult } from "@lunora/agent";
2236
+
2237
+ import { ${imports} } from "../agents.js";
2238
+
1970
2239
  ${classes}`;
1971
2240
  };
1972
2241
  const emitQueues = (queues) => {
@@ -2045,6 +2314,31 @@ ${specEntries}
2045
2314
  `
2046
2315
  };
2047
2316
  };
2317
+ const emitAgentFragments = (agents) => {
2318
+ if (agents.length === 0) {
2319
+ return { build: "", contextField: "", importLines: [], specs: "" };
2320
+ }
2321
+ for (const agent of agents) {
2322
+ assertIdentifier(agent.exportName, `agent export "${agent.exportName}"`);
2323
+ assertIdentifier(agent.bindingName, `agent binding "${agent.bindingName}"`);
2324
+ }
2325
+ const specEntries = agents.map((agent) => ` { binding: "${agent.bindingName}", exportName: "${agent.exportName}"${agent.publicRun === true ? ", publicRun: true" : ""} },`).join("\n");
2326
+ return {
2327
+ build: `
2328
+ const agents = createAgentContext(env, LUNORA_AGENTS);
2329
+ `,
2330
+ contextField: `
2331
+ agents,`,
2332
+ importLines: [`import { createAgentContext } from "@lunora/agent";`, `import type { AgentBindingSpec } from "@lunora/agent";`],
2333
+ // eslint-disable-next-line no-secrets/no-secrets -- the emitted readonly-array type annotation is dense generated TS, not a credential
2334
+ specs: `
2335
+ /** Wiring specs for \`ctx.agents\` (codegen-derived from \`lunora/agents.ts\`). */
2336
+ const LUNORA_AGENTS: ReadonlyArray<AgentBindingSpec> = [
2337
+ ${specEntries}
2338
+ ];
2339
+ `
2340
+ };
2341
+ };
2048
2342
  const emitWorkflowsMetadataFragments = (workflows) => {
2049
2343
  if (workflows.length === 0) {
2050
2344
  return { constant: "", override: "" };
@@ -2208,6 +2502,7 @@ const buildDoTypeImports = (hasVectors, hasWorkflows, hasQueues, hasFlags) => [
2208
2502
  ];
2209
2503
  const emitShard = ({
2210
2504
  advisories = [],
2505
+ agents = [],
2211
2506
  containers = [],
2212
2507
  env,
2213
2508
  flagKeys = [],
@@ -2262,6 +2557,7 @@ const emitShard = ({
2262
2557
  importLines: workflowImportLines,
2263
2558
  specs: workflowSpecs
2264
2559
  } = emitWorkflowFragments(workflows);
2560
+ const { build: agentsBuild, contextField: agentsContextField, importLines: agentImportLines, specs: agentSpecs } = emitAgentFragments(agents);
2265
2561
  const {
2266
2562
  build: paymentsBuild,
2267
2563
  configField: paymentsConfigField,
@@ -2408,6 +2704,7 @@ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCT
2408
2704
  ...containerImportLines,
2409
2705
  ...workflowImportLines,
2410
2706
  ...queueImportLines,
2707
+ ...agentImportLines,
2411
2708
  ...paymentsImports,
2412
2709
  ...x402Imports,
2413
2710
  ``,
@@ -2749,7 +3046,7 @@ const LUNORA_STORAGE_RULES: StorageRulesResult = ${JSON.stringify(storageRulesDa
2749
3046
 
2750
3047
  /** Which optional package-backed features this app wires up (discovered from imports / \`ctx.*\` reads / schema signals) served via \`__lunora_admin__:studioFeatures\` so the studio hides nav pages whose package isn't enabled. */
2751
3048
  const LUNORA_STUDIO_FEATURES: StudioFeaturesResult = ${JSON.stringify(studioFeaturesData, void 0, 4)};
2752
- ${flagsOverrides.constant}${shapeReadRegistryConst}${workflowsMetadataConst}${queuesMetadataConst}${containerSpecs}${workflowSpecs}${queueSpecs}
3049
+ ${flagsOverrides.constant}${shapeReadRegistryConst}${workflowsMetadataConst}${queuesMetadataConst}${containerSpecs}${workflowSpecs}${queueSpecs}${agentSpecs}
2753
3050
  export interface ShardDOConfig {
2754
3051
  /** Opt into change-data-capture: records a post-image to \`__cdc_log\` on every write (backs streaming export + replay-PITR). */
2755
3052
  cdc?: boolean;
@@ -3165,7 +3462,7 @@ ${flagsOverrides.evaluateOverride}${flagsOverrides.subscriptionOverride}${workfl
3165
3462
  // dispatch path) fall back to the per-request fields as before.
3166
3463
  const userId = options.identity ? options.identity.userId : this.getCurrentUserId();
3167
3464
  const identity = options.identity ? options.identity.identity : this.getCurrentIdentity();
3168
- ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}${queuesBuild}
3465
+ ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}${queuesBuild}${agentsBuild}
3169
3466
  const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
3170
3467
  // Build the storage adapter once and share it between \`ctx.storage\`
3171
3468
  // and \`ctx.db.system._storage\` so both read the same R2 binding. The
@@ -3207,7 +3504,7 @@ ${facadeBlock}${paymentsBuild}
3207
3504
  log,
3208
3505
  now,${ormContextField}
3209
3506
  scheduler,
3210
- storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}${queuesContextField}
3507
+ storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}${queuesContextField}${agentsContextField}
3211
3508
  };
3212
3509
  ${isActionLine}${actionOnlyBlock}
3213
3510
  ctx.runAction = (reference: FunctionReference, fnArgs: Record<string, unknown>) => dispatchRun("action", reference.__lunoraRef, fnArgs, ctx);
@@ -3471,4 +3768,4 @@ const emitWranglerCronTriggers = (crons) => {
3471
3768
  return triggers;
3472
3769
  };
3473
3770
 
3474
- export { APP_METHOD_CAPABILITIES as A, CAPABILITIES as C, GENERATED_HEADER as G, emitCollections as a, emitContainers as b, emitCrons as c, emitDataModel as d, emitApi as e, emitDrizzleSchema as f, emitFunctions as g, emitServer as h, emitShard as i, emitVectors as j, emitWorkflows as k, emitWranglerCronTriggers as l, buildStorageColumns as m, emitQueues as n, emitSeed as o };
3771
+ export { APP_METHOD_CAPABILITIES as A, CAPABILITIES as C, GENERATED_HEADER as G, emitApi as a, emitCollections as b, emitContainers as c, emitCrons as d, emitAgents as e, emitDataModel as f, emitDrizzleSchema as g, emitFunctions as h, emitServer as i, emitShard as j, emitVectors as k, emitWorkflows as l, emitWranglerCronTriggers as m, buildStorageColumns as n, emitQueues as o, emitSeed as p };
@@ -1,4 +1,4 @@
1
- import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-31Yg1Ee5.mjs';
1
+ import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-C_29WVlW.mjs';
2
2
 
3
3
  const hasFlagKey = (key) => `has${key.charAt(0).toUpperCase()}${key.slice(1)}`;
4
4
  const LONG_TAIL = APP_METHOD_CAPABILITIES.map(
@@ -11,6 +11,9 @@ const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
11
11
  `import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
12
12
  ] : [];
13
13
  const buildKvImports = (hasKv) => hasKv ? [`import { createKvIntrospectorFromEnv } from "@lunora/bindings/kv";`] : [];
14
+ const hasEmailAgents = (options) => (options.emailAgents?.length ?? 0) > 0;
15
+ const buildInboundImports = (options) => hasEmailAgents(options) ? [`import { dispatchAgentEmail } from "@lunora/agent/inbound";`] : [];
16
+ const buildAgentDefinitionsImport = (options) => hasEmailAgents(options) ? [`import * as lunoraAgentDefinitions from "../agents.js";`] : [];
14
17
  const buildImportLines = (options) => {
15
18
  const {
16
19
  hasAccess,
@@ -59,10 +62,12 @@ const buildImportLines = (options) => {
59
62
  ...hasScheduler ? [`import type { DurableObjectNamespaceLike } from "@lunora/scheduler";`, `import { createScheduler } from "@lunora/scheduler";`] : [],
60
63
  ...hasStorage ? [`import type { R2BucketLike, Storage } from "@lunora/storage";`, `import { createBucketStorage, createStorage } from "@lunora/storage";`] : [],
61
64
  ...hasWorkflow ? [`import { createWorkflowsRestClient } from "@lunora/workflow";`] : [],
65
+ ...buildInboundImports(options),
62
66
  `import type { ${[...runtimeTypeImports].toSorted((a, b) => a.localeCompare(b)).join(", ")} } from "${runtimeModule}";`,
63
67
  `import { ${runtimeValueImports} } from "${runtimeModule}";`,
64
68
  ``,
65
69
  ...buildIdentityImports(options.identity),
70
+ ...buildAgentDefinitionsImport(options),
66
71
  ...hasGlobal || hasHyperdriveGlobal ? [`import schema from "../schema.js";`] : [],
67
72
  `import { LUNORA_CRONS } from "./crons.js";`,
68
73
  `import { LUNORA_FUNCTIONS } from "./functions.js";`,
@@ -383,6 +388,18 @@ const buildWorkerOptionLines = (options) => [
383
388
  }` : ` if (this.accessSelector) {
384
389
  options.resolveIdentity = createAccessResolver(this.accessSelector(env));
385
390
  }`
391
+ ] : [],
392
+ // Voice-enabled agents: map each export name to its `VOICE_*` Durable Object
393
+ // namespace so the runtime serves `/_lunora/voice/<exportName>`. Read off
394
+ // `env` structurally (the binding is provisioned by the config layer's
395
+ // reconcile step, so it may not be on the generated `Env` type). Emitted only
396
+ // when at least one agent opted into voice — voice-free output is unchanged.
397
+ ...options.voiceAgents && options.voiceAgents.length > 0 ? [
398
+ ` options.voiceAgents = {
399
+ ${options.voiceAgents.map(
400
+ (agent) => ` ${JSON.stringify(agent.exportName)}: (env as Record<string, unknown>)[${JSON.stringify(agent.bindingName)}] as ShardNamespaceLike,`
401
+ ).join("\n")}
402
+ };`
386
403
  ] : []
387
404
  ];
388
405
  const buildBaseWorkerOptions = (options) => [
@@ -554,6 +571,12 @@ const emitApp = (options) => {
554
571
  const buildWorkerLine = options.hasFramework ? ` const buildWorker = (env: Env): LunoraWorker =>
555
572
  host ? withFrameworkWorker(host, (hostEnv) => this.buildWorkerOptions(hostEnv as Env, ${getAuthArgument})) : createWorker(this.buildWorkerOptions(env, ${getAuthArgument}));` : ` const buildWorker = (env: Env): LunoraWorker => createWorker(this.buildWorkerOptions(env, ${getAuthArgument}));`;
556
573
  const assembleParameter = options.hasFramework ? `host?: FrameworkHostHandler` : ``;
574
+ const emailAgents = options.emailAgents ?? [];
575
+ const emailAgentsBlock = emailAgents.length > 0 ? ` composed.email = dispatchAgentEmail([
576
+ ${emailAgents.map((agent) => ` { agent: lunoraAgentDefinitions.${agent.exportName}, binding: ${JSON.stringify(agent.bindingName)} },`).join("\n")}
577
+ ]);
578
+
579
+ ` : "";
557
580
  const buildTerminals = ` /** Materialise the standalone Cloudflare worker + \`ShardDO\` class. */
558
581
  public build(): ComposedApp {
559
582
  return this.assemble();
@@ -630,7 +653,7 @@ ${buildWorkerLine}
630
653
  },` : ""}
631
654
  };
632
655
 
633
- if (this.emailHandler) {
656
+ ${emailAgentsBlock} if (this.emailHandler) {
634
657
  const handler = this.emailHandler;
635
658
 
636
659
  composed.email = (message, rawEnv, context) => handler(rawEnv as Env)(message, rawEnv, context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.42",
3
+ "version": "1.0.0-alpha.44",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,13 +46,14 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.26",
50
- "@lunora/container": "1.0.0-alpha.11",
51
- "@lunora/errors": "1.0.0-alpha.4",
52
- "@lunora/queue": "1.0.0-alpha.7",
53
- "@lunora/scheduler": "1.0.0-alpha.8",
54
- "@lunora/values": "1.0.0-alpha.7",
55
- "@lunora/workflow": "1.0.0-alpha.9",
49
+ "@lunora/advisor": "1.0.0-alpha.28",
50
+ "@lunora/agent": "1.0.0-alpha.2",
51
+ "@lunora/container": "1.0.0-alpha.12",
52
+ "@lunora/errors": "1.0.0-alpha.5",
53
+ "@lunora/queue": "1.0.0-alpha.8",
54
+ "@lunora/scheduler": "1.0.0-alpha.10",
55
+ "@lunora/values": "1.0.0-alpha.8",
56
+ "@lunora/workflow": "1.0.0-alpha.10",
56
57
  "ts-morph": "^28.0.0"
57
58
  },
58
59
  "engines": {
@@ -1,3 +0,0 @@
1
- import '@lunora/errors';
2
- export { G as GENERATED_HEADER, m as buildStorageColumns, e as emitApi, a as emitCollections, b as emitContainers, c as emitCrons, d as emitDataModel, f as emitDrizzleSchema, g as emitFunctions, n as emitQueues, o as emitSeed, h as emitServer, i as emitShard, j as emitVectors, k as emitWorkflows, l as emitWranglerCronTriggers } from './emit-31Yg1Ee5.mjs';
3
- import './paths-BRd6JHuF.mjs';