@lunora/agent 0.0.0 → 1.0.0-alpha.10

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 (53) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +38 -31
  3. package/dist/channels.d.mts +74 -0
  4. package/dist/channels.d.ts +74 -0
  5. package/dist/channels.mjs +181 -0
  6. package/dist/component.d.mts +104 -0
  7. package/dist/component.d.ts +104 -0
  8. package/dist/component.mjs +418 -0
  9. package/dist/inbound.d.mts +34 -0
  10. package/dist/inbound.d.ts +34 -0
  11. package/dist/inbound.mjs +32 -0
  12. package/dist/index.d.mts +832 -0
  13. package/dist/index.d.ts +832 -0
  14. package/dist/index.mjs +18 -0
  15. package/dist/naming.d.mts +30 -0
  16. package/dist/naming.d.ts +30 -0
  17. package/dist/naming.mjs +8 -0
  18. package/dist/packem_shared/AGENT_MODULE-Dnt_-AAT.mjs +24 -0
  19. package/dist/packem_shared/VoiceSessionDO-BdwlLaXC.mjs +297 -0
  20. package/dist/packem_shared/adaptMcpResult-wtNMvLoP.mjs +65 -0
  21. package/dist/packem_shared/agentAsTool-CUHlWsmt.mjs +98 -0
  22. package/dist/packem_shared/base64-BVwtgRJV.mjs +18 -0
  23. package/dist/packem_shared/braintrustTelemetry-TP7Kwuuj.mjs +47 -0
  24. package/dist/packem_shared/buildModelMessages-BWFigaoo.mjs +69 -0
  25. package/dist/packem_shared/codeTool-CjgJOC9t.mjs +122 -0
  26. package/dist/packem_shared/collectAgenticMemoryTools-QrzpV-WX.mjs +97 -0
  27. package/dist/packem_shared/combineTelemetry-DCyaaWAI.mjs +43 -0
  28. package/dist/packem_shared/common-DQXayow6.mjs +89 -0
  29. package/dist/packem_shared/compileAgentWorkflow-DYFFyx7i.mjs +78 -0
  30. package/dist/packem_shared/consoleTelemetry--3sWfu1R.mjs +93 -0
  31. package/dist/packem_shared/createAgentContext-4xJGXNR4.mjs +50 -0
  32. package/dist/packem_shared/createAgentGenerate-DO7Z96zX.mjs +192 -0
  33. package/dist/packem_shared/createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs +69 -0
  34. package/dist/packem_shared/defineAgent-DAwAZC9P.mjs +148 -0
  35. package/dist/packem_shared/defineSkill-Ctf_S-rz.mjs +22 -0
  36. package/dist/packem_shared/functionTool-D6lCa2jB.mjs +20 -0
  37. package/dist/packem_shared/graph-component-Bbaxxymp.mjs +217 -0
  38. package/dist/packem_shared/memory-D4FPcBsX.mjs +12 -0
  39. package/dist/packem_shared/normalizeEntityName-BouctxLC.mjs +3 -0
  40. package/dist/packem_shared/otlpTelemetry-DU5ZmoEV.mjs +177 -0
  41. package/dist/packem_shared/runAgentLoop-M8PKbtWT.mjs +493 -0
  42. package/dist/packem_shared/runVoiceTurn-LnqLvCRR.mjs +211 -0
  43. package/dist/packem_shared/sandboxComponent-DR3pTwBL.mjs +194 -0
  44. package/dist/packem_shared/sentryTelemetry-A4F5ndh9.mjs +36 -0
  45. package/dist/packem_shared/types.d-boAM2Yi1.d.mts +1114 -0
  46. package/dist/packem_shared/types.d-boAM2Yi1.d.ts +1114 -0
  47. package/dist/sandbox.d.mts +204 -0
  48. package/dist/sandbox.d.ts +204 -0
  49. package/dist/sandbox.mjs +113 -0
  50. package/dist/telemetry/index.d.mts +254 -0
  51. package/dist/telemetry/index.d.ts +254 -0
  52. package/dist/telemetry/index.mjs +5 -0
  53. package/package.json +88 -7
@@ -0,0 +1,211 @@
1
+ import { f as fromBase64 } from './base64-BVwtgRJV.mjs';
2
+ import { buildModelMessages } from './buildModelMessages-BWFigaoo.mjs';
3
+ import { toFunctionReference } from './AGENT_MODULE-Dnt_-AAT.mjs';
4
+
5
+ const PCM_SAMPLE_RATE = 16e3;
6
+ const PCM_CHANNELS = 1;
7
+ const PCM_BITS_PER_SAMPLE = 16;
8
+ const SENTENCE_BOUNDARY = /[^.!?]*[.!?]+\s+/u;
9
+ const toByteIterable = async function* (source) {
10
+ if (source instanceof Uint8Array) {
11
+ yield source;
12
+ return;
13
+ }
14
+ if (source instanceof ReadableStream) {
15
+ const reader = source.getReader();
16
+ try {
17
+ for (; ; ) {
18
+ const { done, value } = await reader.read();
19
+ if (done) {
20
+ break;
21
+ }
22
+ yield value;
23
+ }
24
+ } finally {
25
+ reader.releaseLock();
26
+ }
27
+ return;
28
+ }
29
+ yield* source;
30
+ };
31
+ const pcmToWav = (pcm, sampleRate = PCM_SAMPLE_RATE, channels = PCM_CHANNELS, bitsPerSample = PCM_BITS_PER_SAMPLE) => {
32
+ const blockAlign = channels * bitsPerSample / 8;
33
+ const byteRate = sampleRate * blockAlign;
34
+ const buffer = new ArrayBuffer(44 + pcm.byteLength);
35
+ const view = new DataView(buffer);
36
+ const writeAscii = (offset, text) => {
37
+ for (let index = 0; index < text.length; index += 1) {
38
+ view.setUint8(offset + index, text.codePointAt(index) ?? 0);
39
+ }
40
+ };
41
+ writeAscii(0, "RIFF");
42
+ view.setUint32(4, 36 + pcm.byteLength, true);
43
+ writeAscii(8, "WAVE");
44
+ writeAscii(12, "fmt ");
45
+ view.setUint32(16, 16, true);
46
+ view.setUint16(20, 1, true);
47
+ view.setUint16(22, channels, true);
48
+ view.setUint32(24, sampleRate, true);
49
+ view.setUint32(28, byteRate, true);
50
+ view.setUint16(32, blockAlign, true);
51
+ view.setUint16(34, bitsPerSample, true);
52
+ writeAscii(36, "data");
53
+ view.setUint32(40, pcm.byteLength, true);
54
+ const out = new Uint8Array(buffer);
55
+ out.set(pcm, 44);
56
+ return out;
57
+ };
58
+ const takeSentences = (buffer) => {
59
+ const sentences = [];
60
+ let rest = buffer;
61
+ let match = SENTENCE_BOUNDARY.exec(rest);
62
+ while (match && match[0].length > 0) {
63
+ const consumed = match[0];
64
+ const sentence = consumed.trim();
65
+ if (sentence.length > 0) {
66
+ sentences.push(sentence);
67
+ }
68
+ rest = rest.slice(consumed.length);
69
+ match = SENTENCE_BOUNDARY.exec(rest);
70
+ }
71
+ return { rest, sentences };
72
+ };
73
+ const runVoiceTurn = async (options) => {
74
+ const {
75
+ agent,
76
+ connectionId,
77
+ env,
78
+ exportName,
79
+ owner,
80
+ paths,
81
+ pcm,
82
+ run,
83
+ send,
84
+ sendAudio,
85
+ signal,
86
+ streamGenerate,
87
+ synthesize,
88
+ text,
89
+ threadKey,
90
+ transcribe,
91
+ turn,
92
+ waitForDrain
93
+ } = options;
94
+ const isAborted = () => signal.aborted;
95
+ const appendMessage = toFunctionReference(paths.appendMessage);
96
+ const ensureThread = toFunctionReference(paths.ensureThread);
97
+ const listMessages = toFunctionReference(paths.listMessages);
98
+ const patchThread = toFunctionReference(paths.patchThread);
99
+ const userText = (text ?? (pcm ? await transcribe(pcm) : "")).trim();
100
+ if (userText.length === 0) {
101
+ return { assistantText: "", interrupted: false, userText: "" };
102
+ }
103
+ send({ text: userText, type: "user_transcript" });
104
+ await run(ensureThread, {
105
+ agent: exportName,
106
+ key: threadKey,
107
+ ...agent.initialState === void 0 ? {} : { initialState: agent.initialState },
108
+ ...owner === void 0 ? {} : { owner }
109
+ });
110
+ await run(appendMessage, { content: userText, messageKey: `voice:${connectionId}:${String(turn)}:user`, role: "user", threadKey });
111
+ await run(patchThread, { key: threadKey, status: "running" });
112
+ const instructions = typeof agent.instructions === "function" ? agent.instructions({ env, input: userText, threadKey }) : agent.instructions;
113
+ const history = await run(listMessages, { key: threadKey });
114
+ const messages = buildModelMessages({ history, ...instructions === void 0 ? {} : { instructions } });
115
+ let pending = "";
116
+ let spoken = "";
117
+ let ttsChain = Promise.resolve();
118
+ const speak = async (sentence) => {
119
+ if (isAborted() || sentence.length === 0) {
120
+ return;
121
+ }
122
+ let flushed = false;
123
+ try {
124
+ for await (const chunk of toByteIterable(await synthesize(sentence, signal))) {
125
+ if (isAborted()) {
126
+ break;
127
+ }
128
+ await waitForDrain?.();
129
+ if (isAborted()) {
130
+ break;
131
+ }
132
+ sendAudio(chunk);
133
+ flushed = true;
134
+ }
135
+ } catch (error) {
136
+ send({ message: error instanceof Error ? error.message : String(error), type: "error" });
137
+ }
138
+ if (flushed) {
139
+ spoken = spoken.length > 0 ? `${spoken} ${sentence}` : sentence;
140
+ }
141
+ };
142
+ const enqueueSpeak = (sentence) => {
143
+ ttsChain = ttsChain.then(async () => speak(sentence));
144
+ };
145
+ try {
146
+ const result = await streamGenerate({ messages, signal }, (delta) => {
147
+ if (isAborted()) {
148
+ return;
149
+ }
150
+ send({ text: delta, type: "assistant_delta" });
151
+ pending += delta;
152
+ const { rest, sentences } = takeSentences(pending);
153
+ pending = rest;
154
+ for (const sentence of sentences) {
155
+ enqueueSpeak(sentence);
156
+ }
157
+ });
158
+ if (!isAborted() && pending.trim().length > 0) {
159
+ enqueueSpeak(pending.trim());
160
+ }
161
+ pending = "";
162
+ await ttsChain;
163
+ const interrupted = isAborted();
164
+ const assistantText = interrupted ? spoken : result.text;
165
+ await run(appendMessage, { content: assistantText, messageKey: `voice:${connectionId}:${String(turn)}:assistant`, role: "assistant", threadKey });
166
+ await run(patchThread, { key: threadKey, status: "idle" });
167
+ send(interrupted ? { type: "interrupted" } : { text: assistantText, type: "assistant_done" });
168
+ return { assistantText, interrupted, userText };
169
+ } catch (error) {
170
+ await run(patchThread, { key: threadKey, status: "idle" });
171
+ throw error;
172
+ }
173
+ };
174
+ const parseIdentity = (raw) => {
175
+ if (!raw) {
176
+ return void 0;
177
+ }
178
+ try {
179
+ const parsed = JSON.parse(raw);
180
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
181
+ } catch {
182
+ return void 0;
183
+ }
184
+ };
185
+ const readTranscriptionText = (result) => {
186
+ if (typeof result === "object" && result !== null && "text" in result) {
187
+ const { text } = result;
188
+ return typeof text === "string" ? text : "";
189
+ }
190
+ return "";
191
+ };
192
+ const readSynthesisAudio = (result) => {
193
+ if (result instanceof Uint8Array || result instanceof ReadableStream) {
194
+ return result;
195
+ }
196
+ if (result instanceof Response && result.body) {
197
+ return result.body;
198
+ }
199
+ if (typeof result === "object" && result !== null && "audio" in result) {
200
+ const { audio } = result;
201
+ if (typeof audio === "string") {
202
+ return fromBase64(audio);
203
+ }
204
+ if (audio instanceof ReadableStream || audio instanceof Uint8Array) {
205
+ return audio;
206
+ }
207
+ }
208
+ return new Uint8Array(0);
209
+ };
210
+
211
+ export { parseIdentity, pcmToWav, readSynthesisAudio, readTranscriptionText, runVoiceTurn, toByteIterable };
@@ -0,0 +1,194 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { initLunora } from '@lunora/server';
3
+ import { v } from '@lunora/values';
4
+ import { t as toBase64 } from './base64-BVwtgRJV.mjs';
5
+
6
+ const { internalAction } = initLunora.dataModel().create();
7
+ const sandboxScrapeDocument = () => globalThis.document?.documentElement?.outerHTML ?? "";
8
+ const MAX_FS_BYTES = 1e6;
9
+ const MAX_LS_PAGES = 20;
10
+ const fsEncoder = new TextEncoder();
11
+ const runBrowserOp = async (browser, request) => {
12
+ const url = request.url ?? "";
13
+ switch (request.op) {
14
+ case "content": {
15
+ return browser.content(url);
16
+ }
17
+ case "pdf": {
18
+ return { data: toBase64(await browser.pdf(url)), encoding: "base64", mediaType: "application/pdf" };
19
+ }
20
+ case "scrape": {
21
+ return browser.scrape(url, sandboxScrapeDocument);
22
+ }
23
+ case "screenshot": {
24
+ const bytes = await browser.screenshot(url, {
25
+ ...request.fullPage === void 0 ? {} : { fullPage: request.fullPage },
26
+ ...request.type === void 0 ? {} : { type: request.type }
27
+ });
28
+ return { data: toBase64(bytes), encoding: "base64", mediaType: request.type === "jpeg" ? "image/jpeg" : "image/png" };
29
+ }
30
+ default: {
31
+ throw new LunoraError("INTERNAL", `@lunora/agent: sandbox browser op "${request.op}" is not supported`);
32
+ }
33
+ }
34
+ };
35
+ const runContainerOp = async (accessor, request) => {
36
+ const handle = accessor.any();
37
+ if (request.op === "exec") {
38
+ const response = await handle.fetch("/exec", {
39
+ body: JSON.stringify({ args: request.args ?? [], command: request.command ?? "" }),
40
+ headers: { "content-type": "application/json" },
41
+ method: "POST"
42
+ });
43
+ return response.text();
44
+ }
45
+ if (request.op === "fetch") {
46
+ const response = await handle.fetch(request.path ?? "/", {
47
+ ...request.body === void 0 ? {} : { body: request.body },
48
+ method: request.method ?? "GET"
49
+ });
50
+ return response.text();
51
+ }
52
+ throw new LunoraError("INTERNAL", `@lunora/agent: sandbox container op "${request.op}" is not supported`);
53
+ };
54
+ const trimSlashes = (value) => {
55
+ let start = 0;
56
+ let end = value.length;
57
+ while (start < end && value[start] === "/") {
58
+ start += 1;
59
+ }
60
+ while (end > start && value[end - 1] === "/") {
61
+ end -= 1;
62
+ }
63
+ return value.slice(start, end);
64
+ };
65
+ const resolveFsKey = (root, path) => {
66
+ const base = trimSlashes(root);
67
+ const segments = [];
68
+ for (const segment of path.split("/")) {
69
+ if (segment === "" || segment === ".") {
70
+ continue;
71
+ }
72
+ if (segment === "..") {
73
+ if (segments.length === 0) {
74
+ throw new LunoraError("BAD_REQUEST", "@lunora/agent: fs path escapes the sandbox root");
75
+ }
76
+ segments.pop();
77
+ continue;
78
+ }
79
+ segments.push(segment);
80
+ }
81
+ const relative = segments.join("/");
82
+ return base && relative ? `${base}/${relative}` : base || relative;
83
+ };
84
+ const listFsEntries = async (bucket, key, base) => {
85
+ const prefix = key.length > 0 ? `${key}/` : "";
86
+ const strip = base.length > 0 ? base.length + 1 : 0;
87
+ const entries = [];
88
+ let cursor;
89
+ for (let page = 0; page < MAX_LS_PAGES; page += 1) {
90
+ const result = await bucket.list({ prefix, ...cursor === void 0 ? {} : { cursor } });
91
+ for (const object of result.objects) {
92
+ entries.push(object.key.slice(strip));
93
+ }
94
+ if (!result.truncated || result.cursor === void 0) {
95
+ return { entries, truncated: false };
96
+ }
97
+ cursor = result.cursor;
98
+ }
99
+ return { entries, truncated: true };
100
+ };
101
+ const runFsOp = async (bucket, root, request) => {
102
+ const base = trimSlashes(root);
103
+ const key = resolveFsKey(root, request.path ?? "");
104
+ switch (request.op) {
105
+ case "ls": {
106
+ const { entries, truncated } = await listFsEntries(bucket, key, base);
107
+ return truncated ? { entries, truncated: true } : { entries };
108
+ }
109
+ case "read": {
110
+ const head = await bucket.head(key);
111
+ if (head && head.size > MAX_FS_BYTES) {
112
+ throw new LunoraError(
113
+ "BAD_REQUEST",
114
+ `@lunora/agent: fs read: "${request.path ?? ""}" is ${String(head.size)} bytes (max ${String(MAX_FS_BYTES)})`
115
+ );
116
+ }
117
+ const object = await bucket.get(key);
118
+ if (!object) {
119
+ throw new LunoraError("NOT_FOUND", `@lunora/agent: fs read: no file at "${request.path ?? ""}"`);
120
+ }
121
+ return object.text();
122
+ }
123
+ case "rm": {
124
+ await bucket.delete(key);
125
+ return { path: request.path ?? "", removed: true };
126
+ }
127
+ case "stat": {
128
+ const head = await bucket.head(key);
129
+ return head ? { exists: true, size: head.size } : { exists: false };
130
+ }
131
+ case "write": {
132
+ const content = request.content ?? "";
133
+ const bytes = fsEncoder.encode(content).length;
134
+ if (bytes > MAX_FS_BYTES) {
135
+ throw new LunoraError("BAD_REQUEST", `@lunora/agent: fs write: ${String(bytes)} bytes exceeds the max (${String(MAX_FS_BYTES)})`);
136
+ }
137
+ await bucket.put(key, content);
138
+ return { bytes, path: request.path ?? "", wrote: true };
139
+ }
140
+ default: {
141
+ throw new LunoraError("INTERNAL", `@lunora/agent: sandbox fs op "${request.op}" is not supported`);
142
+ }
143
+ }
144
+ };
145
+ const sandboxComponent = () => {
146
+ const invoke = internalAction.input({
147
+ args: v.optional(v.array(v.string())),
148
+ body: v.optional(v.string()),
149
+ bucket: v.optional(v.string()),
150
+ command: v.optional(v.string()),
151
+ content: v.optional(v.string()),
152
+ fullPage: v.optional(v.boolean()),
153
+ kind: v.union(v.literal("browser"), v.literal("container"), v.literal("fs")),
154
+ method: v.optional(v.string()),
155
+ name: v.optional(v.string()),
156
+ op: v.string(),
157
+ path: v.optional(v.string()),
158
+ root: v.optional(v.string()),
159
+ selector: v.optional(v.string()),
160
+ type: v.optional(v.string()),
161
+ url: v.optional(v.string())
162
+ }).action(async ({ args, ctx: context }) => {
163
+ const surface = context;
164
+ const request = args;
165
+ if (request.kind === "browser") {
166
+ if (!surface.browser) {
167
+ throw new LunoraError("INTERNAL", "@lunora/agent: sandbox browser op needs `ctx.browser` — install @lunora/browser and run codegen");
168
+ }
169
+ return runBrowserOp(surface.browser, request);
170
+ }
171
+ if (request.kind === "fs") {
172
+ const bucket = surface.env?.[request.bucket ?? ""];
173
+ if (!bucket || typeof bucket.get !== "function") {
174
+ throw new LunoraError(
175
+ "INTERNAL",
176
+ `@lunora/agent: sandbox fs op found no R2 bucket "${request.bucket ?? ""}" on env — declare the r2_bucket binding in wrangler.jsonc and run codegen`
177
+ );
178
+ }
179
+ return runFsOp(bucket, request.root ?? "", request);
180
+ }
181
+ const name = request.name ?? "";
182
+ const accessor = surface.containers?.[name];
183
+ if (!accessor) {
184
+ throw new LunoraError(
185
+ "INTERNAL",
186
+ `@lunora/agent: sandbox container op found no ctx.containers["${name}"] — declare the container in lunora/containers.ts and run codegen`
187
+ );
188
+ }
189
+ return runContainerOp(accessor, request);
190
+ });
191
+ return { invoke };
192
+ };
193
+
194
+ export { resolveFsKey, runFsOp, sandboxComponent };
@@ -0,0 +1,36 @@
1
+ import { r as readField, t as toolInputOf } from './common-DQXayow6.mjs';
2
+
3
+ const stringOr = (value, fallback) => typeof value === "string" && value.length > 0 ? value : fallback;
4
+ const sentryTelemetry = (options) => {
5
+ const { functionId, recordInputs = false, Sentry: sentry } = options;
6
+ return {
7
+ executeLanguageModelCall: (options_) => {
8
+ const attributes = {
9
+ "gen_ai.operation.name": stringOr(functionId, "language_model_call"),
10
+ "gen_ai.request.model": readField(options_, "modelId"),
11
+ "gen_ai.system": readField(options_, "provider")
12
+ };
13
+ if (recordInputs) {
14
+ attributes["gen_ai.prompt"] = readField(options_, "messages");
15
+ }
16
+ return sentry.startSpan({ attributes, name: stringOr(functionId, "language_model_call"), op: "gen_ai.generate" }, () => options_.execute());
17
+ },
18
+ executeTool: (options_) => {
19
+ const toolName = readField(readField(options_, "toolCall"), "toolName");
20
+ const attributes = {
21
+ "gen_ai.operation.name": "execute_tool",
22
+ "gen_ai.tool.call.id": readField(options_, "toolCallId"),
23
+ "gen_ai.tool.name": toolName
24
+ };
25
+ if (recordInputs) {
26
+ attributes["gen_ai.tool.input"] = toolInputOf(options_);
27
+ }
28
+ return sentry.startSpan({ attributes, name: `execute_tool ${stringOr(toolName, "tool")}`, op: "gen_ai.execute_tool" }, () => options_.execute());
29
+ },
30
+ onError: (error) => {
31
+ sentry.captureException(error);
32
+ }
33
+ };
34
+ };
35
+
36
+ export { sentryTelemetry };