@lunora/agent 1.0.0-alpha.9 → 1.0.0-alpha.91
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/channels.d.mts +3 -11
- package/dist/channels.d.ts +3 -11
- package/dist/channels.mjs +1 -181
- package/dist/component.d.mts +2 -0
- package/dist/component.d.ts +2 -0
- package/dist/component.mjs +2 -418
- package/dist/inbound.d.mts +7 -5
- package/dist/inbound.d.ts +7 -5
- package/dist/inbound.mjs +1 -32
- package/dist/index.d.mts +49 -8
- package/dist/index.d.ts +49 -8
- package/dist/index.mjs +1 -18
- package/dist/naming.mjs +1 -8
- package/dist/packem_shared/AGENT_MODULE-M4D1EejI.mjs +1 -0
- package/dist/packem_shared/VoiceSessionDO-BdvnaLoO.mjs +1 -0
- package/dist/packem_shared/adaptMcpResult-CVlq-_TO.mjs +2 -0
- package/dist/packem_shared/agent-loop-B3KTMfqm.mjs +3 -0
- package/dist/packem_shared/agentAsTool-CgZb6ycK.mjs +1 -0
- package/dist/packem_shared/base64-5eyBfWO3.mjs +1 -0
- package/dist/packem_shared/braintrustTelemetry-Byne8uPK.mjs +1 -0
- package/dist/packem_shared/branch-marker-boZ00zmk.mjs +1 -0
- package/dist/packem_shared/buildModelMessages-Y4tGo1T9.mjs +5 -0
- package/dist/packem_shared/codeTool-BQb-K3my.mjs +1 -0
- package/dist/packem_shared/collectAgenticMemoryTools-BeMWT2qt.mjs +1 -0
- package/dist/packem_shared/combineTelemetry-DgE9W9G8.mjs +1 -0
- package/dist/packem_shared/common-CJSjtsfv.mjs +1 -0
- package/dist/packem_shared/compileAgentWorkflow-DRkaF9sV.mjs +1 -0
- package/dist/packem_shared/component-shared-G8ngerkU.mjs +1 -0
- package/dist/packem_shared/consoleTelemetry-BZY3Y-Ly.mjs +1 -0
- package/dist/packem_shared/createAgentContext-DPFZ88dr.mjs +1 -0
- package/dist/packem_shared/createAgentGenerate-BOILDUZj.mjs +3 -0
- package/dist/packem_shared/createDispatchRunner-BMgSkFiH-DjUQx8vg.mjs +1 -0
- package/dist/packem_shared/defineAgent-Q3MlQjYs.mjs +3 -0
- package/dist/packem_shared/defineSkill-DGGvDWNq.mjs +1 -0
- package/dist/packem_shared/fnv1a-BNN96GYb.mjs +1 -0
- package/dist/packem_shared/functionTool-D3CP4f8-.mjs +1 -0
- package/dist/packem_shared/in-flight-calls-GH1A_1P3.mjs +1 -0
- package/dist/packem_shared/normalizeEntityName-k3fjicAG.mjs +2 -0
- package/dist/packem_shared/otlpTelemetry-ByqrB-72.mjs +1 -0
- package/dist/packem_shared/positive-integer-ztHqqpBk.mjs +1 -0
- package/dist/packem_shared/runAgentLoop-DJvzWDgh.mjs +1 -0
- package/dist/packem_shared/runVoiceTurn-D1WCjVPw.mjs +1 -0
- package/dist/packem_shared/sandboxComponent-BtOkUYpY.mjs +5 -0
- package/dist/packem_shared/sentryTelemetry-BHeydiAB.mjs +1 -0
- package/dist/packem_shared/tool-output-Cmk3JWMG.mjs +1 -0
- package/dist/packem_shared/{types.d-boAM2Yi1.d.mts → types.d-B8WZl1rU.d.mts} +255 -22
- package/dist/packem_shared/{types.d-boAM2Yi1.d.ts → types.d-B8WZl1rU.d.ts} +255 -22
- package/dist/packem_shared/voice-turn-D1ZzTzcE.mjs +1 -0
- package/dist/reply.d.mts +40 -0
- package/dist/reply.d.ts +40 -0
- package/dist/reply.mjs +1 -0
- package/dist/sandbox.d.mts +14 -13
- package/dist/sandbox.d.ts +14 -13
- package/dist/sandbox.mjs +1 -113
- package/dist/skill-markdown.d.mts +36 -0
- package/dist/skill-markdown.d.ts +36 -0
- package/dist/skill-markdown.mjs +1 -0
- package/dist/telemetry/index.d.mts +152 -33
- package/dist/telemetry/index.d.ts +152 -33
- package/dist/telemetry/index.mjs +1 -5
- package/package.json +18 -9
- package/dist/packem_shared/AGENT_MODULE-Dnt_-AAT.mjs +0 -24
- package/dist/packem_shared/VoiceSessionDO-BdwlLaXC.mjs +0 -297
- package/dist/packem_shared/adaptMcpResult-wtNMvLoP.mjs +0 -65
- package/dist/packem_shared/agentAsTool-CUHlWsmt.mjs +0 -98
- package/dist/packem_shared/base64-BVwtgRJV.mjs +0 -18
- package/dist/packem_shared/braintrustTelemetry-TP7Kwuuj.mjs +0 -47
- package/dist/packem_shared/buildModelMessages-BWFigaoo.mjs +0 -69
- package/dist/packem_shared/codeTool-CjgJOC9t.mjs +0 -122
- package/dist/packem_shared/collectAgenticMemoryTools-QrzpV-WX.mjs +0 -97
- package/dist/packem_shared/combineTelemetry-DCyaaWAI.mjs +0 -43
- package/dist/packem_shared/common-DQXayow6.mjs +0 -89
- package/dist/packem_shared/compileAgentWorkflow-DAfyUuI5.mjs +0 -78
- package/dist/packem_shared/consoleTelemetry--3sWfu1R.mjs +0 -93
- package/dist/packem_shared/createAgentContext-4xJGXNR4.mjs +0 -50
- package/dist/packem_shared/createAgentGenerate-DO7Z96zX.mjs +0 -192
- package/dist/packem_shared/createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs +0 -69
- package/dist/packem_shared/defineAgent-DAwAZC9P.mjs +0 -148
- package/dist/packem_shared/defineSkill-Ctf_S-rz.mjs +0 -22
- package/dist/packem_shared/functionTool-D6lCa2jB.mjs +0 -20
- package/dist/packem_shared/graph-component-Bbaxxymp.mjs +0 -217
- package/dist/packem_shared/memory-D4FPcBsX.mjs +0 -12
- package/dist/packem_shared/normalizeEntityName-BouctxLC.mjs +0 -3
- package/dist/packem_shared/otlpTelemetry-CKgmWVLg.mjs +0 -170
- package/dist/packem_shared/runAgentLoop-M8PKbtWT.mjs +0 -493
- package/dist/packem_shared/runVoiceTurn-LnqLvCRR.mjs +0 -211
- package/dist/packem_shared/sandboxComponent-DR3pTwBL.mjs +0 -194
- package/dist/packem_shared/sentryTelemetry-A4F5ndh9.mjs +0 -36
|
@@ -60,9 +60,24 @@ interface BraintrustTelemetryOptions extends CommonOptions {
|
|
|
60
60
|
* A dependency-injected Braintrust bridge for the ai@7 telemetry surface.
|
|
61
61
|
*
|
|
62
62
|
* It wraps model calls (`type: "llm"`) and tool executions (`type: "tool"`) in
|
|
63
|
-
* `logger.traced` spans and logs structural metadata
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* `logger.traced` spans and logs structural metadata, including the call's token
|
|
64
|
+
* usage as Braintrust `metrics`. Prompts / tool arguments are logged only when
|
|
65
|
+
* `recordInputs` is set; generated text / tool results only when `recordOutputs`
|
|
66
|
+
* is set. `onError` opens a span and logs the error.
|
|
67
|
+
*
|
|
68
|
+
* The tool span is driven by the agent LOOP, not by `ai`: Lunora exposes tools
|
|
69
|
+
* schema-only so the SDK never executes one (see `telemetry/tool-execution.ts`).
|
|
70
|
+
*
|
|
71
|
+
* **A model-call span closes when the CALL ends, not when `execute()` resolves.**
|
|
72
|
+
* On a streamed turn `execute()` resolves the instant `doStream` hands back the
|
|
73
|
+
* stream — before a token, before any usage — so a span that simply awaited it
|
|
74
|
+
* measured time-to-first-byte and logged the stream handle instead of the
|
|
75
|
+
* generation. `execute()` still runs INSIDE the traced callback, which is what
|
|
76
|
+
* parents the provider's own work under the span; the callback then parks until
|
|
77
|
+
* the SDK's terminal event for that `callId` arrives, so `traced` finishes the
|
|
78
|
+
* span at the real end of the call, with the real usage. The caller gets
|
|
79
|
+
* `execute()`'s value the moment it resolves, exactly as before — the span's
|
|
80
|
+
* lifetime and the caller's are deliberately separate.
|
|
66
81
|
*
|
|
67
82
|
* The app owns Braintrust initialization; pass the logger in as `logger`.
|
|
68
83
|
* @experimental
|
|
@@ -173,36 +188,72 @@ interface OtlpTelemetryOptions extends CommonOptions {
|
|
|
173
188
|
* An OTLP-over-HTTP telemetry integration for `@lunora/agent`.
|
|
174
189
|
*
|
|
175
190
|
* The OTLP counterpart to the `sentryTelemetry` / `braintrustTelemetry` bridges:
|
|
176
|
-
* it
|
|
191
|
+
* it records each language-model call and each tool execution as an OTLP **span**
|
|
177
192
|
* (`gen_ai.*` semantic-convention attributes — model, provider, token usage,
|
|
178
193
|
* tool name) and ships it to a collector, so agent generations land in the same
|
|
179
194
|
* trace store as the rest of an app's telemetry (the Lunora Cloud, or any OTel
|
|
180
195
|
* collector). Plug it into `defineAgent({ telemetry: { isEnabled: true,
|
|
181
196
|
* integrations: [otlpTelemetry({ endpoint, token })] } })`.
|
|
182
197
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
198
|
+
* **A model-call span closes when the CALL ends, not when `execute()` resolves.**
|
|
199
|
+
* On a streamed turn `execute()` resolves the instant `doStream` hands back the
|
|
200
|
+
* stream — before a single token, before any usage, and before any mid-stream
|
|
201
|
+
* failure. Closing the span there reported every voice and workflow-streamed turn
|
|
202
|
+
* as a ~1 ms, zero-token, always-OK call. So the span opens in
|
|
203
|
+
* `executeLanguageModelCall` (which also owns the failure path, since a rejected
|
|
204
|
+
* provider call produces no end event) and closes on `onLanguageModelCallEnd`,
|
|
205
|
+
* which the SDK fires once the response is normalized — after the stream's
|
|
206
|
+
* `finish` part, where the duration and the token usage actually live.
|
|
207
|
+
* `onError` / `onAbort` close whatever is still open, so a stream that dies or is
|
|
208
|
+
* barged in on reports a failure instead of a phantom success.
|
|
209
|
+
*
|
|
210
|
+
* **Tool spans come from the agent loop.** Lunora exposes tools to the model
|
|
211
|
+
* schema-only, so `ai` never runs one and never fires its tool telemetry; the
|
|
212
|
+
* loop calls `executeTool` itself from the durable step where the tool really
|
|
213
|
+
* runs (see `telemetry/tool-execution.ts`).
|
|
214
|
+
*
|
|
215
|
+
* One span per REAL execution: the agent loop memoizes each `step.do(...)`, so a
|
|
216
|
+
* Workflow replay returns the cached result without re-invoking the wrapped work
|
|
217
|
+
* and no duplicate span is emitted. Privacy-safe by default —
|
|
218
|
+
* `recordInputs`/`recordOutputs` both default `false`, so no prompt or generated
|
|
219
|
+
* text leaves the worker without an explicit opt-in; only structural metadata +
|
|
220
|
+
* token counts are recorded.
|
|
189
221
|
*
|
|
190
222
|
* Each export is fire-and-forget (registered with `waitUntil` when supplied);
|
|
191
223
|
* every rejection is swallowed so a flaky collector never surfaces to the run.
|
|
192
224
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* `traceId` is set) but no `parentSpanId`, so model-call and tool spans are
|
|
198
|
-
* siblings under the run rather than a tree — OTLP has no ambient span context to
|
|
199
|
-
* parent to here.
|
|
225
|
+
* One deliberate difference from the SDK-backed bridges, which delegate to a host
|
|
226
|
+
* tracer: flat, not nested. Every span gets `traceId` (shared when `traceId` is
|
|
227
|
+
* set) but no `parentSpanId`, so model-call and tool spans are siblings under the
|
|
228
|
+
* run rather than a tree — OTLP has no ambient span context to parent to here.
|
|
200
229
|
* @param options `endpoint` (+ optional `token`/`headers`/`serviceName`),
|
|
201
230
|
* `traceId` to group a run's spans, `waitUntil`, and the `recordInputs`/
|
|
202
231
|
* `recordOutputs` privacy flags.
|
|
203
232
|
* @experimental
|
|
204
233
|
*/
|
|
205
234
|
declare const otlpTelemetry: (options: OtlpTelemetryOptions) => Telemetry;
|
|
235
|
+
/** The span context both `startSpan` and `startSpanManual` are called with. */
|
|
236
|
+
interface SentrySpanContext {
|
|
237
|
+
attributes?: Record<string, unknown>;
|
|
238
|
+
name: string;
|
|
239
|
+
op?: string;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The subset of a Sentry `Span` this bridge drives. Structural, like
|
|
243
|
+
* {@link SentryLike} itself — a real `@sentry/*` span satisfies it.
|
|
244
|
+
* @experimental
|
|
245
|
+
*/
|
|
246
|
+
interface SentrySpan {
|
|
247
|
+
/** Finish the span. Called once, from whichever terminal event closes the call. */
|
|
248
|
+
end: () => void;
|
|
249
|
+
/** Attach attributes discovered after the span started (token usage). */
|
|
250
|
+
setAttributes?: (attributes: Record<string, unknown>) => unknown;
|
|
251
|
+
/** `1` = OK, `2` = ERROR (Sentry's `SPAN_STATUS_OK` / `SPAN_STATUS_ERROR`). */
|
|
252
|
+
setStatus?: (status: {
|
|
253
|
+
code: 0 | 1 | 2;
|
|
254
|
+
message?: string;
|
|
255
|
+
}) => unknown;
|
|
256
|
+
}
|
|
206
257
|
/**
|
|
207
258
|
* The minimal, **structural** slice of `@sentry/cloudflare` (equivalently
|
|
208
259
|
* `@sentry/node`/`@sentry/browser`) this bridge needs. `@sentry/cloudflare` is
|
|
@@ -214,16 +265,16 @@ declare const otlpTelemetry: (options: OtlpTelemetryOptions) => Telemetry;
|
|
|
214
265
|
interface SentryLike {
|
|
215
266
|
/** Capture a thrown value / exception. */
|
|
216
267
|
captureException: (exception: unknown) => unknown;
|
|
217
|
-
/** Run `callback` inside a new span
|
|
218
|
-
startSpan: <T>(context:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
268
|
+
/** Run `callback` inside a new span, finished when the callback settles. */
|
|
269
|
+
startSpan: <T>(context: SentrySpanContext, callback: (span: SentrySpan) => T) => T;
|
|
270
|
+
/**
|
|
271
|
+
* Run `callback` inside a new span that is **not** finished automatically —
|
|
272
|
+
* the caller owns its lifetime through `span.end()`. Present on every Sentry
|
|
273
|
+
* SDK built on `@sentry/core` (verified against `@sentry/core@10.55.0`); it is
|
|
274
|
+
* what lets a streamed model call be measured to the end of the stream while
|
|
275
|
+
* still nesting the provider's own work under it.
|
|
276
|
+
*/
|
|
277
|
+
startSpanManual: <T>(context: SentrySpanContext, callback: (span: SentrySpan) => T) => T;
|
|
227
278
|
}
|
|
228
279
|
/**
|
|
229
280
|
* Options for {@link sentryTelemetry}.
|
|
@@ -241,14 +292,82 @@ interface SentryTelemetryOptions extends CommonOptions {
|
|
|
241
292
|
/**
|
|
242
293
|
* A dependency-injected Sentry bridge for the ai@7 telemetry surface.
|
|
243
294
|
*
|
|
244
|
-
* It wraps model calls and tool executions in Sentry spans
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* set, in which case prompts
|
|
295
|
+
* It wraps model calls and tool executions in Sentry spans so nested
|
|
296
|
+
* provider/tool work is correctly parented, and routes `onError` to
|
|
297
|
+
* `Sentry.captureException`. Span attributes carry only structural metadata
|
|
298
|
+
* (model, provider, tool name, token usage) unless `recordInputs` /
|
|
299
|
+
* `recordOutputs` are set, in which case prompts, tool arguments and generated
|
|
300
|
+
* text are attached too.
|
|
301
|
+
*
|
|
302
|
+
* The tool span is driven by the agent LOOP, not by `ai`: Lunora exposes tools
|
|
303
|
+
* schema-only so the SDK never executes one (see `telemetry/tool-execution.ts`).
|
|
304
|
+
*
|
|
305
|
+
* **A model-call span closes when the CALL ends, not when `execute()` resolves.**
|
|
306
|
+
* On a streamed turn `execute()` resolves the instant `doStream` hands back the
|
|
307
|
+
* stream — before a token, before any usage. The span still OPENS around
|
|
308
|
+
* `execute()`, because that is what makes it the active span nested provider work
|
|
309
|
+
* parents to, but it is opened with `startSpanManual` and so is not finished
|
|
310
|
+
* there: `onLanguageModelCallEnd` ends it, once the response is normalized and
|
|
311
|
+
* the real duration and token usage are both known. `onAbort` / `onError` end the
|
|
312
|
+
* call they name as a failure, so a stream that dies or is barged in on reports
|
|
313
|
+
* one instead of a phantom success.
|
|
249
314
|
*
|
|
250
315
|
* The app owns Sentry initialization; pass the namespace in as `Sentry`.
|
|
251
316
|
* @experimental
|
|
252
317
|
*/
|
|
253
318
|
declare const sentryTelemetry: (options: SentryTelemetryOptions) => Telemetry;
|
|
254
|
-
export {
|
|
319
|
+
export {
|
|
320
|
+
/**
|
|
321
|
+
* `@lunora/agent/telemetry` — observability integrations for `@lunora/agent`.
|
|
322
|
+
*
|
|
323
|
+
* Every export here produces an ai@7 `Telemetry` object suitable for the
|
|
324
|
+
* `integrations` array of `TelemetryOptions` (`defineAgent({ telemetry: {
|
|
325
|
+
* isEnabled: true, integrations: [...] } })`). `consoleTelemetry` is a
|
|
326
|
+
* zero-dependency structured console tracer; `combineTelemetry` fans the
|
|
327
|
+
* lifecycle out to several integrations and nests their execution wrappers;
|
|
328
|
+
* `sentryTelemetry` / `braintrustTelemetry` are dependency-injected bridges (the
|
|
329
|
+
* app passes its own Sentry namespace / Braintrust logger, so the heavy SDKs are
|
|
330
|
+
* never imported here); and `otlpTelemetry` ships `gen_ai.*` spans over
|
|
331
|
+
* OTLP-over-HTTP to any collector (the Lunora Cloud, or your own), so agent
|
|
332
|
+
* generations land in the same trace store as the rest of the app.
|
|
333
|
+
*
|
|
334
|
+
* All integrations are privacy-safe by default (`recordInputs` / `recordOutputs`
|
|
335
|
+
* both default `false`), so nothing sensitive is recorded without opt-in.
|
|
336
|
+
*/
|
|
337
|
+
type BraintrustLike,
|
|
338
|
+
/**
|
|
339
|
+
* `@lunora/agent/telemetry` — observability integrations for `@lunora/agent`.
|
|
340
|
+
*
|
|
341
|
+
* Every export here produces an ai@7 `Telemetry` object suitable for the
|
|
342
|
+
* `integrations` array of `TelemetryOptions` (`defineAgent({ telemetry: {
|
|
343
|
+
* isEnabled: true, integrations: [...] } })`). `consoleTelemetry` is a
|
|
344
|
+
* zero-dependency structured console tracer; `combineTelemetry` fans the
|
|
345
|
+
* lifecycle out to several integrations and nests their execution wrappers;
|
|
346
|
+
* `sentryTelemetry` / `braintrustTelemetry` are dependency-injected bridges (the
|
|
347
|
+
* app passes its own Sentry namespace / Braintrust logger, so the heavy SDKs are
|
|
348
|
+
* never imported here); and `otlpTelemetry` ships `gen_ai.*` spans over
|
|
349
|
+
* OTLP-over-HTTP to any collector (the Lunora Cloud, or your own), so agent
|
|
350
|
+
* generations land in the same trace store as the rest of the app.
|
|
351
|
+
*
|
|
352
|
+
* All integrations are privacy-safe by default (`recordInputs` / `recordOutputs`
|
|
353
|
+
* both default `false`), so nothing sensitive is recorded without opt-in.
|
|
354
|
+
*/
|
|
355
|
+
type BraintrustSpan,
|
|
356
|
+
/**
|
|
357
|
+
* `@lunora/agent/telemetry` — observability integrations for `@lunora/agent`.
|
|
358
|
+
*
|
|
359
|
+
* Every export here produces an ai@7 `Telemetry` object suitable for the
|
|
360
|
+
* `integrations` array of `TelemetryOptions` (`defineAgent({ telemetry: {
|
|
361
|
+
* isEnabled: true, integrations: [...] } })`). `consoleTelemetry` is a
|
|
362
|
+
* zero-dependency structured console tracer; `combineTelemetry` fans the
|
|
363
|
+
* lifecycle out to several integrations and nests their execution wrappers;
|
|
364
|
+
* `sentryTelemetry` / `braintrustTelemetry` are dependency-injected bridges (the
|
|
365
|
+
* app passes its own Sentry namespace / Braintrust logger, so the heavy SDKs are
|
|
366
|
+
* never imported here); and `otlpTelemetry` ships `gen_ai.*` spans over
|
|
367
|
+
* OTLP-over-HTTP to any collector (the Lunora Cloud, or your own), so agent
|
|
368
|
+
* generations land in the same trace store as the rest of the app.
|
|
369
|
+
*
|
|
370
|
+
* All integrations are privacy-safe by default (`recordInputs` / `recordOutputs`
|
|
371
|
+
* both default `false`), so nothing sensitive is recorded without opt-in.
|
|
372
|
+
*/
|
|
373
|
+
type BraintrustTelemetryOptions, type CommonOptions, type ConsoleLogLevel, type ConsoleLogger, type ConsoleTelemetryOptions, type OtlpTelemetryOptions, type SentryLike, type SentrySpan, type SentryTelemetryOptions, braintrustTelemetry, combineTelemetry, consoleTelemetry, otlpTelemetry, sentryTelemetry };
|
package/dist/telemetry/index.mjs
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { combineTelemetry } from '../packem_shared/combineTelemetry-DCyaaWAI.mjs';
|
|
3
|
-
export { consoleTelemetry } from '../packem_shared/consoleTelemetry--3sWfu1R.mjs';
|
|
4
|
-
export { otlpTelemetry } from '../packem_shared/otlpTelemetry-CKgmWVLg.mjs';
|
|
5
|
-
export { sentryTelemetry } from '../packem_shared/sentryTelemetry-A4F5ndh9.mjs';
|
|
1
|
+
import{braintrustTelemetry as o}from"../packem_shared/braintrustTelemetry-Byne8uPK.mjs";import{combineTelemetry as m}from"../packem_shared/combineTelemetry-DgE9W9G8.mjs";import{consoleTelemetry as p}from"../packem_shared/consoleTelemetry-BZY3Y-Ly.mjs";import{otlpTelemetry as f}from"../packem_shared/otlpTelemetry-ByqrB-72.mjs";import{sentryTelemetry as T}from"../packem_shared/sentryTelemetry-BHeydiAB.mjs";export{o as braintrustTelemetry,m as combineTelemetry,p as consoleTelemetry,f as otlpTelemetry,T as sentryTelemetry};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/agent",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.91",
|
|
4
4
|
"description": "Durable AI agents for Lunora: defineAgent compiles a replay-safe tool-loop onto Cloudflare Workflows, with DO SQLite threads and live message subscriptions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -62,6 +62,14 @@
|
|
|
62
62
|
"types": "./dist/naming.d.ts",
|
|
63
63
|
"import": "./dist/naming.mjs"
|
|
64
64
|
},
|
|
65
|
+
"./skill-markdown": {
|
|
66
|
+
"types": "./dist/skill-markdown.d.ts",
|
|
67
|
+
"import": "./dist/skill-markdown.mjs"
|
|
68
|
+
},
|
|
69
|
+
"./reply": {
|
|
70
|
+
"types": "./dist/reply.d.ts",
|
|
71
|
+
"import": "./dist/reply.mjs"
|
|
72
|
+
},
|
|
65
73
|
"./sandbox": {
|
|
66
74
|
"types": "./dist/sandbox.d.ts",
|
|
67
75
|
"import": "./dist/sandbox.mjs"
|
|
@@ -76,14 +84,15 @@
|
|
|
76
84
|
"access": "public"
|
|
77
85
|
},
|
|
78
86
|
"dependencies": {
|
|
79
|
-
"@lunora/ai": "1.0.0-alpha.
|
|
80
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
81
|
-
"@lunora/mail": "1.0.0-alpha.
|
|
82
|
-
"@lunora/server": "1.0.0-alpha.
|
|
83
|
-
"@lunora/values": "1.0.0-alpha.
|
|
84
|
-
"@lunora/workflow": "1.0.0-alpha.
|
|
85
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
86
|
-
"ai": "7.0.
|
|
87
|
+
"@lunora/ai": "1.0.0-alpha.74",
|
|
88
|
+
"@lunora/errors": "1.0.0-alpha.33",
|
|
89
|
+
"@lunora/mail": "1.0.0-alpha.65",
|
|
90
|
+
"@lunora/server": "1.0.0-alpha.105",
|
|
91
|
+
"@lunora/values": "1.0.0-alpha.41",
|
|
92
|
+
"@lunora/workflow": "1.0.0-alpha.47",
|
|
93
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
94
|
+
"ai": "7.0.59",
|
|
95
|
+
"yaml": "^2.9.0"
|
|
87
96
|
},
|
|
88
97
|
"engines": {
|
|
89
98
|
"node": "^22.15.0 || >=24.11.0"
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const AGENT_MODULE = "agents";
|
|
2
|
-
const SANDBOX_MODULE = "sandbox";
|
|
3
|
-
const SANDBOX_INVOKE_PATH = `${SANDBOX_MODULE}:invoke`;
|
|
4
|
-
const DEFAULT_AGENT_FUNCTION_PATHS = {
|
|
5
|
-
appendMessage: `${AGENT_MODULE}:agentAppendMessage`,
|
|
6
|
-
ensureThread: `${AGENT_MODULE}:agentEnsureThread`,
|
|
7
|
-
episodeRecall: `${AGENT_MODULE}:agentEpisodeRecall`,
|
|
8
|
-
episodeUpsert: `${AGENT_MODULE}:agentEpisodeUpsert`,
|
|
9
|
-
graphTraverse: `${AGENT_MODULE}:agentGraphTraverse`,
|
|
10
|
-
graphUpsert: `${AGENT_MODULE}:agentGraphUpsert`,
|
|
11
|
-
listMessages: `${AGENT_MODULE}:agentMessages`,
|
|
12
|
-
patchThread: `${AGENT_MODULE}:agentPatchThread`,
|
|
13
|
-
run: `${AGENT_MODULE}:agentRun`,
|
|
14
|
-
setState: `${AGENT_MODULE}:agentSetState`,
|
|
15
|
-
state: `${AGENT_MODULE}:agentState`
|
|
16
|
-
};
|
|
17
|
-
const toFunctionReference = (source) => {
|
|
18
|
-
if (typeof source === "string") {
|
|
19
|
-
return { __lunoraRef: source };
|
|
20
|
-
}
|
|
21
|
-
return source;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export { AGENT_MODULE, DEFAULT_AGENT_FUNCTION_PATHS, SANDBOX_INVOKE_PATH, SANDBOX_MODULE, toFunctionReference };
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
import { createAi } from '@lunora/ai';
|
|
2
|
-
import { c as createDispatchRunner } from './createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs';
|
|
3
|
-
import { t as toBase64 } from './base64-BVwtgRJV.mjs';
|
|
4
|
-
import { createStreamGenerate } from './createAgentGenerate-DO7Z96zX.mjs';
|
|
5
|
-
import { DEFAULT_AGENT_FUNCTION_PATHS, toFunctionReference } from './AGENT_MODULE-Dnt_-AAT.mjs';
|
|
6
|
-
import { parseIdentity, pcmToWav, readTranscriptionText, readSynthesisAudio, runVoiceTurn, toByteIterable } from './runVoiceTurn-LnqLvCRR.mjs';
|
|
7
|
-
|
|
8
|
-
const DEFAULT_STT_MODEL = "@cf/openai/whisper-large-v3-turbo";
|
|
9
|
-
const DEFAULT_TTS_MODEL = "@cf/deepgram/aura-1";
|
|
10
|
-
const MAX_UTTERANCE_BYTES = 8 * 1024 * 1024;
|
|
11
|
-
const MAX_SOCKET_BUFFER_BYTES = 256 * 1024;
|
|
12
|
-
const DRAIN_POLL_MS = 15;
|
|
13
|
-
const MAX_DRAIN_WAIT_MS = 5e3;
|
|
14
|
-
class VoiceSessionDO {
|
|
15
|
-
agent;
|
|
16
|
-
ai;
|
|
17
|
-
env;
|
|
18
|
-
exportName;
|
|
19
|
-
paths;
|
|
20
|
-
streamGenerate;
|
|
21
|
-
sttModel;
|
|
22
|
-
ttsModel;
|
|
23
|
-
audioBuffers = /* @__PURE__ */ new Map();
|
|
24
|
-
bufferedBytes = /* @__PURE__ */ new Map();
|
|
25
|
-
controllers = /* @__PURE__ */ new Map();
|
|
26
|
-
state;
|
|
27
|
-
constructor(state, env, agent, exportName) {
|
|
28
|
-
this.state = state;
|
|
29
|
-
this.env = env;
|
|
30
|
-
this.agent = agent;
|
|
31
|
-
this.exportName = exportName;
|
|
32
|
-
this.paths = DEFAULT_AGENT_FUNCTION_PATHS;
|
|
33
|
-
this.ai = createAi({ binding: env["AI"], env });
|
|
34
|
-
this.streamGenerate = createStreamGenerate(agent, env);
|
|
35
|
-
this.sttModel = agent.voice?.stt ?? DEFAULT_STT_MODEL;
|
|
36
|
-
this.ttsModel = agent.voice?.tts ?? DEFAULT_TTS_MODEL;
|
|
37
|
-
}
|
|
38
|
-
/** HTTP entry — only a WebSocket upgrade carrying a `threadKey` is accepted. */
|
|
39
|
-
fetch(request) {
|
|
40
|
-
if (request.headers.get("Upgrade") !== "websocket") {
|
|
41
|
-
return new Response("Expected a WebSocket upgrade", { status: 426 });
|
|
42
|
-
}
|
|
43
|
-
const url = new URL(request.url);
|
|
44
|
-
const threadKey = url.searchParams.get("threadKey");
|
|
45
|
-
if (!threadKey) {
|
|
46
|
-
return new Response("Missing threadKey", { status: 400 });
|
|
47
|
-
}
|
|
48
|
-
const WebSocketPairConstructor = globalThis.WebSocketPair;
|
|
49
|
-
const pair = new WebSocketPairConstructor();
|
|
50
|
-
const client = pair[0];
|
|
51
|
-
const server = pair[1];
|
|
52
|
-
this.state.acceptWebSocket(server);
|
|
53
|
-
const connectionId = crypto.randomUUID();
|
|
54
|
-
const identity = parseIdentity(request.headers.get("x-lunora-identity"));
|
|
55
|
-
const userId = request.headers.get("x-lunora-userid") ?? void 0;
|
|
56
|
-
server.serializeAttachment?.({
|
|
57
|
-
connectionId,
|
|
58
|
-
threadKey,
|
|
59
|
-
turn: 0,
|
|
60
|
-
...identity === void 0 ? {} : { identity },
|
|
61
|
-
...userId === void 0 ? {} : { userId }
|
|
62
|
-
});
|
|
63
|
-
this.send(server, { audioFormat: this.agent.voice?.audioFormat ?? "mp3", type: "ready" });
|
|
64
|
-
const greeting = this.agent.voice?.greeting;
|
|
65
|
-
if (greeting && greeting.length > 0) {
|
|
66
|
-
this.state.waitUntil?.(this.speakGreeting(server, connectionId, threadKey, userId, identity, greeting));
|
|
67
|
-
}
|
|
68
|
-
return new Response(null, { status: 101, webSocket: client });
|
|
69
|
-
}
|
|
70
|
-
/** Hibernation message handler — never throws (a thrown handler is fatal to the socket). */
|
|
71
|
-
async webSocketMessage(ws, message) {
|
|
72
|
-
const attachment = ws.deserializeAttachment?.();
|
|
73
|
-
if (!attachment) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
if (typeof message === "string") {
|
|
78
|
-
await this.handleControl(ws, attachment, message);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
this.bufferAudio(attachment.connectionId, new Uint8Array(message), ws);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
this.send(ws, { message: error instanceof Error ? error.message : String(error), type: "error" });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
/** Abort any in-flight turn + free the socket's buffers on close. Never throws. */
|
|
87
|
-
webSocketClose(ws) {
|
|
88
|
-
this.cleanupSocket(ws);
|
|
89
|
-
}
|
|
90
|
-
/** Abort any in-flight turn + free the socket's buffers on error. Never throws. */
|
|
91
|
-
webSocketError(ws) {
|
|
92
|
-
this.cleanupSocket(ws);
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* The runtime dispatch seam reaching the shared agent thread functions. When
|
|
96
|
-
* the socket carries a verified identity it is forwarded so the `agents:*`
|
|
97
|
-
* thread writes are attributed to the caller (RLS / row ownership) rather
|
|
98
|
-
* than the anonymous system dispatch.
|
|
99
|
-
*/
|
|
100
|
-
resolveRun(userId, claims) {
|
|
101
|
-
const identity = userId === void 0 && claims === void 0 ? void 0 : { ...claims === void 0 ? {} : { claims }, ...userId === void 0 ? {} : { userId } };
|
|
102
|
-
return createDispatchRunner({ env: this.env, label: "@lunora/agent voice", ...identity === void 0 ? {} : { identity } });
|
|
103
|
-
}
|
|
104
|
-
/** Production STT seam: WAV-wrap the utterance and run the batch transcription model. */
|
|
105
|
-
async transcribe(pcm) {
|
|
106
|
-
const wav = pcmToWav(pcm);
|
|
107
|
-
return readTranscriptionText(await this.ai.run(this.sttModel, { audio: toBase64(wav) }));
|
|
108
|
-
}
|
|
109
|
-
/** Production TTS seam: synthesize one sentence to a normalized audio source; `signal` aborts an in-flight barge-in. */
|
|
110
|
-
async synthesize(text, signal) {
|
|
111
|
-
const speaker = this.agent.voice?.speaker;
|
|
112
|
-
return readSynthesisAudio(
|
|
113
|
-
await this.ai.run(this.ttsModel, { text, ...speaker === void 0 ? {} : { speaker } }, signal === void 0 ? void 0 : { signal })
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
/** Route a JSON control frame (`commit` / `interrupt` / `text`). */
|
|
117
|
-
async handleControl(ws, attachment, raw) {
|
|
118
|
-
let frame;
|
|
119
|
-
try {
|
|
120
|
-
frame = JSON.parse(raw);
|
|
121
|
-
} catch {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (frame.type === "interrupt") {
|
|
125
|
-
this.controllers.get(attachment.connectionId)?.abort();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (this.controllers.has(attachment.connectionId)) {
|
|
129
|
-
this.send(ws, { message: "a turn is already in progress — send an interrupt before the next utterance", type: "error" });
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (frame.type === "commit") {
|
|
133
|
-
const pcm = this.drainAudio(attachment.connectionId);
|
|
134
|
-
await this.runTurn(ws, attachment, { pcm });
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
await this.runTurn(ws, attachment, { text: frame.text });
|
|
138
|
-
}
|
|
139
|
-
/** Append a binary audio frame to the socket's utterance buffer (bounded). */
|
|
140
|
-
bufferAudio(connectionId, chunk, ws) {
|
|
141
|
-
const total = (this.bufferedBytes.get(connectionId) ?? 0) + chunk.byteLength;
|
|
142
|
-
if (total > MAX_UTTERANCE_BYTES) {
|
|
143
|
-
this.audioBuffers.delete(connectionId);
|
|
144
|
-
this.bufferedBytes.delete(connectionId);
|
|
145
|
-
this.send(ws, { message: "utterance exceeded the maximum buffer — send a commit sooner", type: "error" });
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const chunks = this.audioBuffers.get(connectionId) ?? [];
|
|
149
|
-
chunks.push(chunk);
|
|
150
|
-
this.audioBuffers.set(connectionId, chunks);
|
|
151
|
-
this.bufferedBytes.set(connectionId, total);
|
|
152
|
-
}
|
|
153
|
-
/** Concatenate + clear the socket's buffered utterance. */
|
|
154
|
-
drainAudio(connectionId) {
|
|
155
|
-
const chunks = this.audioBuffers.get(connectionId) ?? [];
|
|
156
|
-
this.audioBuffers.delete(connectionId);
|
|
157
|
-
this.bufferedBytes.delete(connectionId);
|
|
158
|
-
const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
159
|
-
const pcm = new Uint8Array(total);
|
|
160
|
-
let offset = 0;
|
|
161
|
-
for (const chunk of chunks) {
|
|
162
|
-
pcm.set(chunk, offset);
|
|
163
|
-
offset += chunk.byteLength;
|
|
164
|
-
}
|
|
165
|
-
return pcm;
|
|
166
|
-
}
|
|
167
|
-
/** Execute a turn under a fresh abort controller, advancing the socket's turn index. */
|
|
168
|
-
async runTurn(ws, attachment, input) {
|
|
169
|
-
const controller = new AbortController();
|
|
170
|
-
this.controllers.set(attachment.connectionId, controller);
|
|
171
|
-
try {
|
|
172
|
-
await runVoiceTurn({
|
|
173
|
-
agent: this.agent,
|
|
174
|
-
connectionId: attachment.connectionId,
|
|
175
|
-
env: this.env,
|
|
176
|
-
exportName: this.exportName,
|
|
177
|
-
paths: this.paths,
|
|
178
|
-
run: this.resolveRun(attachment.userId, attachment.identity),
|
|
179
|
-
send: (frame) => {
|
|
180
|
-
this.send(ws, frame);
|
|
181
|
-
},
|
|
182
|
-
sendAudio: (bytes) => {
|
|
183
|
-
this.sendAudio(ws, bytes);
|
|
184
|
-
},
|
|
185
|
-
signal: controller.signal,
|
|
186
|
-
streamGenerate: this.streamGenerate,
|
|
187
|
-
synthesize: async (text, signal) => this.synthesizeWithSignal(text, signal),
|
|
188
|
-
threadKey: attachment.threadKey,
|
|
189
|
-
transcribe: async (pcm) => this.transcribe(pcm),
|
|
190
|
-
turn: attachment.turn,
|
|
191
|
-
waitForDrain: async () => this.waitForSocketDrain(ws),
|
|
192
|
-
...attachment.userId === void 0 ? {} : { owner: attachment.userId },
|
|
193
|
-
...input.pcm === void 0 ? {} : { pcm: input.pcm },
|
|
194
|
-
...input.text === void 0 ? {} : { text: input.text }
|
|
195
|
-
});
|
|
196
|
-
} finally {
|
|
197
|
-
if (this.controllers.get(attachment.connectionId) === controller) {
|
|
198
|
-
this.controllers.delete(attachment.connectionId);
|
|
199
|
-
}
|
|
200
|
-
ws.serializeAttachment?.({ ...attachment, turn: attachment.turn + 1 });
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
/** Synthesize a greeting on connect and persist it as the thread's opening assistant turn. */
|
|
204
|
-
async speakGreeting(ws, connectionId, threadKey, userId, identity, greeting) {
|
|
205
|
-
const run = this.resolveRun(userId, identity);
|
|
206
|
-
const controller = new AbortController();
|
|
207
|
-
const isAborted = () => controller.signal.aborted;
|
|
208
|
-
this.controllers.set(connectionId, controller);
|
|
209
|
-
try {
|
|
210
|
-
await run(toFunctionReference(this.paths.ensureThread), {
|
|
211
|
-
agent: this.exportName,
|
|
212
|
-
key: threadKey,
|
|
213
|
-
...this.agent.initialState === void 0 ? {} : { initialState: this.agent.initialState },
|
|
214
|
-
...userId === void 0 ? {} : { owner: userId }
|
|
215
|
-
});
|
|
216
|
-
for await (const chunk of toByteIterable(await this.synthesizeWithSignal(greeting, controller.signal))) {
|
|
217
|
-
if (isAborted()) {
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
await this.waitForSocketDrain(ws);
|
|
221
|
-
if (isAborted()) {
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
|
-
this.sendAudio(ws, chunk);
|
|
225
|
-
}
|
|
226
|
-
if (!isAborted()) {
|
|
227
|
-
await run(toFunctionReference(this.paths.appendMessage), {
|
|
228
|
-
content: greeting,
|
|
229
|
-
messageKey: "voice:greeting:assistant",
|
|
230
|
-
role: "assistant",
|
|
231
|
-
threadKey
|
|
232
|
-
});
|
|
233
|
-
this.send(ws, { text: greeting, type: "assistant_done" });
|
|
234
|
-
}
|
|
235
|
-
} catch (error) {
|
|
236
|
-
this.send(ws, { message: error instanceof Error ? error.message : String(error), type: "error" });
|
|
237
|
-
} finally {
|
|
238
|
-
if (this.controllers.get(connectionId) === controller) {
|
|
239
|
-
this.controllers.delete(connectionId);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/** Bridge the pipeline's `(text, signal)` synthesize seam onto the class TTS method, forwarding the barge-in signal. */
|
|
244
|
-
async synthesizeWithSignal(text, signal) {
|
|
245
|
-
if (signal.aborted) {
|
|
246
|
-
return new Uint8Array(0);
|
|
247
|
-
}
|
|
248
|
-
return this.synthesize(text, signal);
|
|
249
|
-
}
|
|
250
|
-
/** Abort an in-flight turn and free a socket's transient buffers. */
|
|
251
|
-
cleanupSocket(ws) {
|
|
252
|
-
const attachment = ws.deserializeAttachment?.();
|
|
253
|
-
if (!attachment) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
this.controllers.get(attachment.connectionId)?.abort();
|
|
257
|
-
this.controllers.delete(attachment.connectionId);
|
|
258
|
-
this.audioBuffers.delete(attachment.connectionId);
|
|
259
|
-
this.bufferedBytes.delete(attachment.connectionId);
|
|
260
|
-
}
|
|
261
|
-
/** Send a JSON control frame, swallowing a closed-socket error (never throw from a handler). */
|
|
262
|
-
// eslint-disable-next-line class-methods-use-this -- instance method (kept non-static for subclass override symmetry); acts on the passed socket
|
|
263
|
-
send(ws, frame) {
|
|
264
|
-
try {
|
|
265
|
-
ws.send(JSON.stringify(frame));
|
|
266
|
-
} catch {
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
/** Send a binary audio frame, swallowing a closed-socket error. */
|
|
270
|
-
// eslint-disable-next-line class-methods-use-this -- instance method (kept non-static for subclass override symmetry); acts on the passed socket
|
|
271
|
-
sendAudio(ws, bytes) {
|
|
272
|
-
try {
|
|
273
|
-
ws.send(bytes);
|
|
274
|
-
} catch {
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Outbound backpressure: if the socket exposes `bufferedAmount`, yield in
|
|
279
|
-
* short polls until the send buffer drains below the cap so a slow client
|
|
280
|
-
* can't balloon DO memory. Bounded by {@link MAX_DRAIN_WAIT_MS} so a stuck
|
|
281
|
-
* socket never blocks a turn forever, and never throws (a socket without
|
|
282
|
-
* `bufferedAmount` resolves immediately).
|
|
283
|
-
*/
|
|
284
|
-
// eslint-disable-next-line class-methods-use-this -- instance method (kept non-static for subclass override symmetry); acts on the passed socket
|
|
285
|
-
async waitForSocketDrain(ws) {
|
|
286
|
-
const socket = ws;
|
|
287
|
-
let waited = 0;
|
|
288
|
-
while (typeof socket.bufferedAmount === "number" && socket.bufferedAmount > MAX_SOCKET_BUFFER_BYTES && waited < MAX_DRAIN_WAIT_MS) {
|
|
289
|
-
await new Promise((resolve) => {
|
|
290
|
-
setTimeout(resolve, DRAIN_POLL_MS);
|
|
291
|
-
});
|
|
292
|
-
waited += DRAIN_POLL_MS;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export { VoiceSessionDO as default };
|