@lunora/agent 1.0.0-alpha.6 → 1.0.0-alpha.8
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/index.mjs +1 -1
- package/dist/packem_shared/compileAgentWorkflow-DXNKigj0.mjs +75 -0
- package/dist/packem_shared/otlpTelemetry-9ypXork9.mjs +163 -0
- package/dist/telemetry/index.d.mts +73 -1
- package/dist/telemetry/index.d.ts +73 -1
- package/dist/telemetry/index.mjs +1 -0
- package/package.json +2 -2
- package/dist/packem_shared/compileAgentWorkflow-DX90058f.mjs +0 -55
package/dist/index.mjs
CHANGED
|
@@ -15,4 +15,4 @@ export { browserTool, containerTool, fsTool } from './sandbox.mjs';
|
|
|
15
15
|
export { defineSkill, isSkillDefinition } from './packem_shared/defineSkill-Ctf_S-rz.mjs';
|
|
16
16
|
export { default as VoiceSessionDO } from './packem_shared/VoiceSessionDO-DLoXsHGF.mjs';
|
|
17
17
|
export { runVoiceTurn } from './packem_shared/runVoiceTurn-LnqLvCRR.mjs';
|
|
18
|
-
export { default as compileAgentWorkflow } from './packem_shared/compileAgentWorkflow-
|
|
18
|
+
export { default as compileAgentWorkflow } from './packem_shared/compileAgentWorkflow-DXNKigj0.mjs';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineWorkflow } from '@lunora/workflow';
|
|
2
|
+
import { runAgentLoop } from './runAgentLoop-B3Q2o-Uz.mjs';
|
|
3
|
+
import { createStreamGenerate, createAgentGenerate, createEpisodeExtract, createGraphExtract, createCompact } from './createAgentGenerate-BQv9YJ01.mjs';
|
|
4
|
+
import { agentDefaultName } from '../naming.mjs';
|
|
5
|
+
import { DEFAULT_AGENT_FUNCTION_PATHS } from './AGENT_MODULE-Dnt_-AAT.mjs';
|
|
6
|
+
import { c as createDispatchRunner } from './createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs';
|
|
7
|
+
import { otlpTelemetry } from './otlpTelemetry-9ypXork9.mjs';
|
|
8
|
+
|
|
9
|
+
const resolveAgentRun = (contextRun, owner, env) => {
|
|
10
|
+
if (owner === void 0) {
|
|
11
|
+
return contextRun;
|
|
12
|
+
}
|
|
13
|
+
return createDispatchRunner({ env, identity: { userId: owner }, label: "@lunora/agent" });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const withAutoOtlpTelemetry = (agent, env) => {
|
|
17
|
+
const endpoint = env.LUNORA_OTLP_ENDPOINT;
|
|
18
|
+
if (typeof endpoint !== "string" || endpoint === "" || agent.telemetry?.isEnabled === false) {
|
|
19
|
+
return agent;
|
|
20
|
+
}
|
|
21
|
+
const token = typeof env.LUNORA_OTLP_TOKEN === "string" ? env.LUNORA_OTLP_TOKEN : void 0;
|
|
22
|
+
const existingList = [agent.telemetry?.integrations].flat().filter((integration) => integration !== void 0);
|
|
23
|
+
return {
|
|
24
|
+
...agent,
|
|
25
|
+
telemetry: {
|
|
26
|
+
...agent.telemetry,
|
|
27
|
+
integrations: [...existingList, otlpTelemetry({ endpoint, token })],
|
|
28
|
+
isEnabled: true
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const compileAgentWorkflow = (agent, exportName, options) => defineWorkflow({
|
|
33
|
+
handler: async (context) => {
|
|
34
|
+
const runtimeAgent = withAutoOtlpTelemetry(agent, context.env);
|
|
35
|
+
return runAgentLoop({
|
|
36
|
+
agent: runtimeAgent,
|
|
37
|
+
// Automatic history compaction. Dormant unless the agent declares
|
|
38
|
+
// a `compaction` config; the loop gates on it, so any other agent
|
|
39
|
+
// takes the byte-identical no-compaction path.
|
|
40
|
+
compact: createCompact(),
|
|
41
|
+
env: context.env,
|
|
42
|
+
exportName,
|
|
43
|
+
// Run-end graph extraction. Dormant unless the agent declares a
|
|
44
|
+
// `kind: "graph"` memory source AND the run carries an owner — the
|
|
45
|
+
// loop gates on both, so a semantic-only agent takes the
|
|
46
|
+
// byte-identical no-extraction path.
|
|
47
|
+
extractGraph: createGraphExtract(),
|
|
48
|
+
// Run-end episode recording. Dormant unless the agent declares a
|
|
49
|
+
// `kind: "episodic"` memory source AND the run carries an owner —
|
|
50
|
+
// the loop gates on both, so any other agent is byte-identical.
|
|
51
|
+
extractEpisode: createEpisodeExtract(),
|
|
52
|
+
generate: createAgentGenerate(runtimeAgent, context.env),
|
|
53
|
+
instanceId: context.event.instanceId,
|
|
54
|
+
params: context.params,
|
|
55
|
+
paths: options?.paths ?? DEFAULT_AGENT_FUNCTION_PATHS,
|
|
56
|
+
// The loop reads its own owner-gated thread back through
|
|
57
|
+
// `agents:*` queries. The default `context.run` forwards no
|
|
58
|
+
// identity, so on an OWNED thread those reads would come back
|
|
59
|
+
// empty and the model would answer blind. `resolveAgentRun`
|
|
60
|
+
// dispatches an owner-scoped run under that verified identity so
|
|
61
|
+
// the owner gate admits the loop's reads (ownerless runs keep the
|
|
62
|
+
// identity-free `context.run`). See `resolve-run.ts`.
|
|
63
|
+
run: resolveAgentRun(context.run, context.params.owner, context.env),
|
|
64
|
+
step: context.step,
|
|
65
|
+
// The streaming seam is wired and ready, but stays dormant until a
|
|
66
|
+
// live token sink is threaded onto the run (a follow-up wires
|
|
67
|
+
// `onTokenDelta` to the stream transport). With no sink the loop
|
|
68
|
+
// takes the byte-identical non-streaming `generate` path.
|
|
69
|
+
streamGenerate: createStreamGenerate(runtimeAgent, context.env)
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
name: agent.name ?? agentDefaultName(exportName)
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export { compileAgentWorkflow as default };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { s as summarizeUsage, r as readField, c as contentText, t as toolInputOf, a as toolNameOf } from './common-DAeFCot5.mjs';
|
|
2
|
+
|
|
3
|
+
const otlpUnixNano = (ms) => `${String(Math.round(ms))}000000`;
|
|
4
|
+
const otlpRandomHex = (bytes) => {
|
|
5
|
+
const buffer = new Uint8Array(bytes);
|
|
6
|
+
crypto.getRandomValues(buffer);
|
|
7
|
+
let hex = "";
|
|
8
|
+
for (const byte of buffer) {
|
|
9
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
10
|
+
}
|
|
11
|
+
return hex;
|
|
12
|
+
};
|
|
13
|
+
const encodeAttribute = (key, value) => {
|
|
14
|
+
if (typeof value === "boolean") {
|
|
15
|
+
return { key, value: { boolValue: value } };
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "number") {
|
|
18
|
+
if (!Number.isFinite(value)) {
|
|
19
|
+
return { key, value: { stringValue: String(value) } };
|
|
20
|
+
}
|
|
21
|
+
return Number.isSafeInteger(value) ? { key, value: { intValue: String(value) } } : { key, value: { doubleValue: value } };
|
|
22
|
+
}
|
|
23
|
+
return { key, value: { stringValue: value } };
|
|
24
|
+
};
|
|
25
|
+
const mergeHeaders = (defaults, overrides, token) => {
|
|
26
|
+
const merged = {};
|
|
27
|
+
const seen = /* @__PURE__ */ new Map();
|
|
28
|
+
const put = (name, value) => {
|
|
29
|
+
const lower = name.toLowerCase();
|
|
30
|
+
const existing = seen.get(lower);
|
|
31
|
+
if (existing === void 0) {
|
|
32
|
+
seen.set(lower, name);
|
|
33
|
+
merged[name] = value;
|
|
34
|
+
} else {
|
|
35
|
+
merged[existing] = value;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
for (const [name, value] of Object.entries(defaults)) {
|
|
39
|
+
put(name, value);
|
|
40
|
+
}
|
|
41
|
+
for (const [name, value] of Object.entries(overrides ?? {})) {
|
|
42
|
+
put(name, value);
|
|
43
|
+
}
|
|
44
|
+
if (token !== void 0 && token.length > 0) {
|
|
45
|
+
put("authorization", `Bearer ${token}`);
|
|
46
|
+
}
|
|
47
|
+
return merged;
|
|
48
|
+
};
|
|
49
|
+
const wrapResourceSpans = (span, scopeName, serviceName) => {
|
|
50
|
+
return {
|
|
51
|
+
resourceSpans: [
|
|
52
|
+
{
|
|
53
|
+
resource: { attributes: [encodeAttribute("service.name", serviceName)] },
|
|
54
|
+
scopeSpans: [{ scope: { name: scopeName }, spans: [span] }]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const pushAttribute = (attributes, key, value) => {
|
|
61
|
+
if (value === void 0 || value === null) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
65
|
+
attributes.push(encodeAttribute(key, value));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
attributes.push(encodeAttribute(key, JSON.stringify(value)));
|
|
69
|
+
};
|
|
70
|
+
const observeSettled = (promise, onSettle) => {
|
|
71
|
+
const run = async () => {
|
|
72
|
+
let result;
|
|
73
|
+
try {
|
|
74
|
+
result = await promise;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
onSettle(false, error instanceof Error ? error.message : String(error), void 0);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
onSettle(true, void 0, result);
|
|
80
|
+
};
|
|
81
|
+
run().catch(() => void 0);
|
|
82
|
+
};
|
|
83
|
+
const otlpTelemetry = (options) => {
|
|
84
|
+
const { endpoint, headers, recordInputs = false, recordOutputs = false, token, traceId: fixedTraceId, waitUntil } = options;
|
|
85
|
+
const serviceName = options.serviceName ?? "lunora";
|
|
86
|
+
let base = endpoint;
|
|
87
|
+
while (base.endsWith("/")) {
|
|
88
|
+
base = base.slice(0, -1);
|
|
89
|
+
}
|
|
90
|
+
const tracesUrl = `${base}/v1/traces`;
|
|
91
|
+
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers, token);
|
|
92
|
+
const emitSpan = (name, startTs, ok, message, attributes) => {
|
|
93
|
+
const span = {
|
|
94
|
+
attributes,
|
|
95
|
+
endTimeUnixNano: otlpUnixNano(Date.now()),
|
|
96
|
+
// SPAN_KIND_INTERNAL — matches the runtime's `ctx.trace` spans.
|
|
97
|
+
kind: 1,
|
|
98
|
+
name,
|
|
99
|
+
spanId: otlpRandomHex(8),
|
|
100
|
+
startTimeUnixNano: otlpUnixNano(startTs),
|
|
101
|
+
// STATUS_CODE_OK (1) / STATUS_CODE_ERROR (2).
|
|
102
|
+
status: ok ? { code: 1 } : { code: 2, message: message ?? "" },
|
|
103
|
+
traceId: fixedTraceId ?? otlpRandomHex(16)
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
const sent = fetch(tracesUrl, {
|
|
107
|
+
body: JSON.stringify(wrapResourceSpans(span, "@lunora/agent", serviceName)),
|
|
108
|
+
headers: mergedHeaders,
|
|
109
|
+
method: "POST"
|
|
110
|
+
}).catch(() => {
|
|
111
|
+
});
|
|
112
|
+
waitUntil?.(sent);
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
return {
|
|
117
|
+
executeLanguageModelCall: (options_) => {
|
|
118
|
+
const startTs = Date.now();
|
|
119
|
+
const modelId = readField(options_, "modelId");
|
|
120
|
+
const promise = options_.execute();
|
|
121
|
+
const emit = (ok, message, result) => {
|
|
122
|
+
const attributes = [];
|
|
123
|
+
pushAttribute(attributes, "gen_ai.operation.name", "chat");
|
|
124
|
+
pushAttribute(attributes, "gen_ai.request.model", modelId);
|
|
125
|
+
pushAttribute(attributes, "gen_ai.system", readField(options_, "provider"));
|
|
126
|
+
const usage = summarizeUsage(readField(result, "usage"));
|
|
127
|
+
if (usage) {
|
|
128
|
+
pushAttribute(attributes, "gen_ai.usage.input_tokens", usage.inputTokens);
|
|
129
|
+
pushAttribute(attributes, "gen_ai.usage.output_tokens", usage.outputTokens);
|
|
130
|
+
pushAttribute(attributes, "gen_ai.usage.total_tokens", usage.totalTokens);
|
|
131
|
+
}
|
|
132
|
+
if (recordInputs) {
|
|
133
|
+
pushAttribute(attributes, "gen_ai.prompt", readField(options_, "messages"));
|
|
134
|
+
}
|
|
135
|
+
if (recordOutputs) {
|
|
136
|
+
pushAttribute(attributes, "gen_ai.completion", contentText(readField(result, "content")));
|
|
137
|
+
}
|
|
138
|
+
emitSpan(typeof modelId === "string" ? `chat ${modelId}` : "language_model_call", startTs, ok, message, attributes);
|
|
139
|
+
};
|
|
140
|
+
observeSettled(promise, emit);
|
|
141
|
+
return promise;
|
|
142
|
+
},
|
|
143
|
+
executeTool: (options_) => {
|
|
144
|
+
const startTs = Date.now();
|
|
145
|
+
const toolName = toolNameOf(options_);
|
|
146
|
+
const promise = options_.execute();
|
|
147
|
+
const emit = (ok, message) => {
|
|
148
|
+
const attributes = [];
|
|
149
|
+
pushAttribute(attributes, "gen_ai.operation.name", "execute_tool");
|
|
150
|
+
pushAttribute(attributes, "gen_ai.tool.name", toolName);
|
|
151
|
+
pushAttribute(attributes, "gen_ai.tool.call.id", readField(options_, "toolCallId"));
|
|
152
|
+
if (recordInputs) {
|
|
153
|
+
pushAttribute(attributes, "gen_ai.tool.input", toolInputOf(options_));
|
|
154
|
+
}
|
|
155
|
+
emitSpan(typeof toolName === "string" ? `execute_tool ${toolName}` : "execute_tool", startTs, ok, message, attributes);
|
|
156
|
+
};
|
|
157
|
+
observeSettled(promise, emit);
|
|
158
|
+
return promise;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export { otlpTelemetry };
|
|
@@ -122,6 +122,78 @@ interface ConsoleTelemetryOptions extends CommonOptions {
|
|
|
122
122
|
* @experimental
|
|
123
123
|
*/
|
|
124
124
|
declare const consoleTelemetry: (options?: ConsoleTelemetryOptions) => Telemetry;
|
|
125
|
+
/**
|
|
126
|
+
* Options for {@link otlpTelemetry}.
|
|
127
|
+
* @experimental
|
|
128
|
+
*/
|
|
129
|
+
interface OtlpTelemetryOptions extends CommonOptions {
|
|
130
|
+
/**
|
|
131
|
+
* The OTLP-over-HTTP collector base endpoint (e.g. the Lunora Cloud's `/v1`
|
|
132
|
+
* base). Spans are POSTed to `${endpoint}/v1/traces`; a trailing slash is
|
|
133
|
+
* tolerated. On the platform this is the injected `LUNORA_OTLP_ENDPOINT`.
|
|
134
|
+
*/
|
|
135
|
+
endpoint: string;
|
|
136
|
+
/**
|
|
137
|
+
* Extra headers merged onto every POST — typically the correlation headers
|
|
138
|
+
* the platform injects. `Content-Type: application/json` is set by default.
|
|
139
|
+
*/
|
|
140
|
+
headers?: Record<string, string>;
|
|
141
|
+
/** `service.name` resource attribute on every span. Defaults to `lunora`. */
|
|
142
|
+
serviceName?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Bearer token added as `Authorization: Bearer` (wins over any authorization
|
|
145
|
+
* in `headers`) — the injected `LUNORA_OTLP_TOKEN`. Omit for an
|
|
146
|
+
* unauthenticated collector.
|
|
147
|
+
*/
|
|
148
|
+
token?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Trace id (32-hex) to hang every span of this run under, so one agent run
|
|
151
|
+
* reads as one trace. Pass a stable id derived from the run (e.g. the
|
|
152
|
+
* workflow run id). Omitted → each span mints its own trace id (still valid,
|
|
153
|
+
* just ungrouped).
|
|
154
|
+
*/
|
|
155
|
+
traceId?: string;
|
|
156
|
+
/**
|
|
157
|
+
* The Worker request's `waitUntil`, so a fire-and-forget export survives
|
|
158
|
+
* isolate teardown after the turn returns. Omit and the send degrades to
|
|
159
|
+
* best-effort.
|
|
160
|
+
*/
|
|
161
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* An OTLP-over-HTTP telemetry integration for `@lunora/agent`.
|
|
165
|
+
*
|
|
166
|
+
* The OTLP counterpart to the `sentryTelemetry` / `braintrustTelemetry` bridges:
|
|
167
|
+
* it wraps each language-model call and tool execution in an OTLP **span**
|
|
168
|
+
* (`gen_ai.*` semantic-convention attributes — model, provider, token usage,
|
|
169
|
+
* tool name) and ships it to a collector, so agent generations land in the same
|
|
170
|
+
* trace store as the rest of an app's telemetry (the Lunora Cloud, or any OTel
|
|
171
|
+
* collector). Plug it into `defineAgent({ telemetry: { isEnabled: true,
|
|
172
|
+
* integrations: [otlpTelemetry({ endpoint, token })] } })`.
|
|
173
|
+
*
|
|
174
|
+
* Emitting inside the turn's execution wrapper means one span per **real** turn:
|
|
175
|
+
* the agent loop memoizes each `step.do('llm:turn:N')`, so a Workflow replay
|
|
176
|
+
* returns the cached result without re-invoking `execute`, and no duplicate span
|
|
177
|
+
* is emitted. Privacy-safe by default — `recordInputs`/`recordOutputs` both
|
|
178
|
+
* default `false`, so no prompt or generated text leaves the worker without an
|
|
179
|
+
* explicit opt-in; only structural metadata + token counts are recorded.
|
|
180
|
+
*
|
|
181
|
+
* Each export is fire-and-forget (registered with `waitUntil` when supplied);
|
|
182
|
+
* every rejection is swallowed so a flaky collector never surfaces to the run.
|
|
183
|
+
*
|
|
184
|
+
* Two deliberate differences from the SDK-backed bridges, which delegate to a
|
|
185
|
+
* host tracer. First, no `onError`: a failed call already emits a span with
|
|
186
|
+
* `status.code === 2`, so the failure is on the trace and there is no host client
|
|
187
|
+
* to also notify. Second, flat not nested: every span gets `traceId` (shared when
|
|
188
|
+
* `traceId` is set) but no `parentSpanId`, so model-call and tool spans are
|
|
189
|
+
* siblings under the run rather than a tree — OTLP has no ambient span context to
|
|
190
|
+
* parent to here.
|
|
191
|
+
* @param options `endpoint` (+ optional `token`/`headers`/`serviceName`),
|
|
192
|
+
* `traceId` to group a run's spans, `waitUntil`, and the `recordInputs`/
|
|
193
|
+
* `recordOutputs` privacy flags.
|
|
194
|
+
* @experimental
|
|
195
|
+
*/
|
|
196
|
+
declare const otlpTelemetry: (options: OtlpTelemetryOptions) => Telemetry;
|
|
125
197
|
/**
|
|
126
198
|
* The minimal, **structural** slice of `@sentry/cloudflare` (equivalently
|
|
127
199
|
* `@sentry/node`/`@sentry/browser`) this bridge needs. `@sentry/cloudflare` is
|
|
@@ -170,4 +242,4 @@ interface SentryTelemetryOptions extends CommonOptions {
|
|
|
170
242
|
* @experimental
|
|
171
243
|
*/
|
|
172
244
|
declare const sentryTelemetry: (options: SentryTelemetryOptions) => Telemetry;
|
|
173
|
-
export { type BraintrustLike, type BraintrustSpan, type BraintrustTelemetryOptions, type CommonOptions, type ConsoleLogLevel, type ConsoleLogger, type ConsoleTelemetryOptions, type SentryLike, type SentryTelemetryOptions, braintrustTelemetry, combineTelemetry, consoleTelemetry, sentryTelemetry };
|
|
245
|
+
export { type BraintrustLike, type BraintrustSpan, type BraintrustTelemetryOptions, type CommonOptions, type ConsoleLogLevel, type ConsoleLogger, type ConsoleTelemetryOptions, type OtlpTelemetryOptions, type SentryLike, type SentryTelemetryOptions, braintrustTelemetry, combineTelemetry, consoleTelemetry, otlpTelemetry, sentryTelemetry };
|
|
@@ -122,6 +122,78 @@ interface ConsoleTelemetryOptions extends CommonOptions {
|
|
|
122
122
|
* @experimental
|
|
123
123
|
*/
|
|
124
124
|
declare const consoleTelemetry: (options?: ConsoleTelemetryOptions) => Telemetry;
|
|
125
|
+
/**
|
|
126
|
+
* Options for {@link otlpTelemetry}.
|
|
127
|
+
* @experimental
|
|
128
|
+
*/
|
|
129
|
+
interface OtlpTelemetryOptions extends CommonOptions {
|
|
130
|
+
/**
|
|
131
|
+
* The OTLP-over-HTTP collector base endpoint (e.g. the Lunora Cloud's `/v1`
|
|
132
|
+
* base). Spans are POSTed to `${endpoint}/v1/traces`; a trailing slash is
|
|
133
|
+
* tolerated. On the platform this is the injected `LUNORA_OTLP_ENDPOINT`.
|
|
134
|
+
*/
|
|
135
|
+
endpoint: string;
|
|
136
|
+
/**
|
|
137
|
+
* Extra headers merged onto every POST — typically the correlation headers
|
|
138
|
+
* the platform injects. `Content-Type: application/json` is set by default.
|
|
139
|
+
*/
|
|
140
|
+
headers?: Record<string, string>;
|
|
141
|
+
/** `service.name` resource attribute on every span. Defaults to `lunora`. */
|
|
142
|
+
serviceName?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Bearer token added as `Authorization: Bearer` (wins over any authorization
|
|
145
|
+
* in `headers`) — the injected `LUNORA_OTLP_TOKEN`. Omit for an
|
|
146
|
+
* unauthenticated collector.
|
|
147
|
+
*/
|
|
148
|
+
token?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Trace id (32-hex) to hang every span of this run under, so one agent run
|
|
151
|
+
* reads as one trace. Pass a stable id derived from the run (e.g. the
|
|
152
|
+
* workflow run id). Omitted → each span mints its own trace id (still valid,
|
|
153
|
+
* just ungrouped).
|
|
154
|
+
*/
|
|
155
|
+
traceId?: string;
|
|
156
|
+
/**
|
|
157
|
+
* The Worker request's `waitUntil`, so a fire-and-forget export survives
|
|
158
|
+
* isolate teardown after the turn returns. Omit and the send degrades to
|
|
159
|
+
* best-effort.
|
|
160
|
+
*/
|
|
161
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* An OTLP-over-HTTP telemetry integration for `@lunora/agent`.
|
|
165
|
+
*
|
|
166
|
+
* The OTLP counterpart to the `sentryTelemetry` / `braintrustTelemetry` bridges:
|
|
167
|
+
* it wraps each language-model call and tool execution in an OTLP **span**
|
|
168
|
+
* (`gen_ai.*` semantic-convention attributes — model, provider, token usage,
|
|
169
|
+
* tool name) and ships it to a collector, so agent generations land in the same
|
|
170
|
+
* trace store as the rest of an app's telemetry (the Lunora Cloud, or any OTel
|
|
171
|
+
* collector). Plug it into `defineAgent({ telemetry: { isEnabled: true,
|
|
172
|
+
* integrations: [otlpTelemetry({ endpoint, token })] } })`.
|
|
173
|
+
*
|
|
174
|
+
* Emitting inside the turn's execution wrapper means one span per **real** turn:
|
|
175
|
+
* the agent loop memoizes each `step.do('llm:turn:N')`, so a Workflow replay
|
|
176
|
+
* returns the cached result without re-invoking `execute`, and no duplicate span
|
|
177
|
+
* is emitted. Privacy-safe by default — `recordInputs`/`recordOutputs` both
|
|
178
|
+
* default `false`, so no prompt or generated text leaves the worker without an
|
|
179
|
+
* explicit opt-in; only structural metadata + token counts are recorded.
|
|
180
|
+
*
|
|
181
|
+
* Each export is fire-and-forget (registered with `waitUntil` when supplied);
|
|
182
|
+
* every rejection is swallowed so a flaky collector never surfaces to the run.
|
|
183
|
+
*
|
|
184
|
+
* Two deliberate differences from the SDK-backed bridges, which delegate to a
|
|
185
|
+
* host tracer. First, no `onError`: a failed call already emits a span with
|
|
186
|
+
* `status.code === 2`, so the failure is on the trace and there is no host client
|
|
187
|
+
* to also notify. Second, flat not nested: every span gets `traceId` (shared when
|
|
188
|
+
* `traceId` is set) but no `parentSpanId`, so model-call and tool spans are
|
|
189
|
+
* siblings under the run rather than a tree — OTLP has no ambient span context to
|
|
190
|
+
* parent to here.
|
|
191
|
+
* @param options `endpoint` (+ optional `token`/`headers`/`serviceName`),
|
|
192
|
+
* `traceId` to group a run's spans, `waitUntil`, and the `recordInputs`/
|
|
193
|
+
* `recordOutputs` privacy flags.
|
|
194
|
+
* @experimental
|
|
195
|
+
*/
|
|
196
|
+
declare const otlpTelemetry: (options: OtlpTelemetryOptions) => Telemetry;
|
|
125
197
|
/**
|
|
126
198
|
* The minimal, **structural** slice of `@sentry/cloudflare` (equivalently
|
|
127
199
|
* `@sentry/node`/`@sentry/browser`) this bridge needs. `@sentry/cloudflare` is
|
|
@@ -170,4 +242,4 @@ interface SentryTelemetryOptions extends CommonOptions {
|
|
|
170
242
|
* @experimental
|
|
171
243
|
*/
|
|
172
244
|
declare const sentryTelemetry: (options: SentryTelemetryOptions) => Telemetry;
|
|
173
|
-
export { type BraintrustLike, type BraintrustSpan, type BraintrustTelemetryOptions, type CommonOptions, type ConsoleLogLevel, type ConsoleLogger, type ConsoleTelemetryOptions, type SentryLike, type SentryTelemetryOptions, braintrustTelemetry, combineTelemetry, consoleTelemetry, sentryTelemetry };
|
|
245
|
+
export { type BraintrustLike, type BraintrustSpan, type BraintrustTelemetryOptions, type CommonOptions, type ConsoleLogLevel, type ConsoleLogger, type ConsoleTelemetryOptions, type OtlpTelemetryOptions, type SentryLike, type SentryTelemetryOptions, braintrustTelemetry, combineTelemetry, consoleTelemetry, otlpTelemetry, sentryTelemetry };
|
package/dist/telemetry/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { braintrustTelemetry } from '../packem_shared/braintrustTelemetry-wuGDErob.mjs';
|
|
2
2
|
export { combineTelemetry } from '../packem_shared/combineTelemetry-DCyaaWAI.mjs';
|
|
3
3
|
export { consoleTelemetry } from '../packem_shared/consoleTelemetry-z2MiP1jt.mjs';
|
|
4
|
+
export { otlpTelemetry } from '../packem_shared/otlpTelemetry-9ypXork9.mjs';
|
|
4
5
|
export { sentryTelemetry } from '../packem_shared/sentryTelemetry-CgqFJyLO.mjs';
|
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.8",
|
|
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",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"access": "public"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@lunora/ai": "1.0.0-alpha.
|
|
79
|
+
"@lunora/ai": "1.0.0-alpha.19",
|
|
80
80
|
"@lunora/errors": "1.0.0-alpha.6",
|
|
81
81
|
"@lunora/mail": "1.0.0-alpha.16",
|
|
82
82
|
"@lunora/server": "1.0.0-alpha.29",
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { defineWorkflow } from '@lunora/workflow';
|
|
2
|
-
import { runAgentLoop } from './runAgentLoop-B3Q2o-Uz.mjs';
|
|
3
|
-
import { createStreamGenerate, createAgentGenerate, createEpisodeExtract, createGraphExtract, createCompact } from './createAgentGenerate-BQv9YJ01.mjs';
|
|
4
|
-
import { agentDefaultName } from '../naming.mjs';
|
|
5
|
-
import { DEFAULT_AGENT_FUNCTION_PATHS } from './AGENT_MODULE-Dnt_-AAT.mjs';
|
|
6
|
-
import { c as createDispatchRunner } from './createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs';
|
|
7
|
-
|
|
8
|
-
const resolveAgentRun = (contextRun, owner, env) => {
|
|
9
|
-
if (owner === void 0) {
|
|
10
|
-
return contextRun;
|
|
11
|
-
}
|
|
12
|
-
return createDispatchRunner({ env, identity: { userId: owner }, label: "@lunora/agent" });
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const compileAgentWorkflow = (agent, exportName, options) => defineWorkflow({
|
|
16
|
-
handler: async (context) => runAgentLoop({
|
|
17
|
-
agent,
|
|
18
|
-
// Automatic history compaction. Dormant unless the agent declares
|
|
19
|
-
// a `compaction` config; the loop gates on it, so any other agent
|
|
20
|
-
// takes the byte-identical no-compaction path.
|
|
21
|
-
compact: createCompact(),
|
|
22
|
-
env: context.env,
|
|
23
|
-
exportName,
|
|
24
|
-
// Run-end graph extraction. Dormant unless the agent declares a
|
|
25
|
-
// `kind: "graph"` memory source AND the run carries an owner — the
|
|
26
|
-
// loop gates on both, so a semantic-only agent takes the
|
|
27
|
-
// byte-identical no-extraction path.
|
|
28
|
-
extractGraph: createGraphExtract(),
|
|
29
|
-
// Run-end episode recording. Dormant unless the agent declares a
|
|
30
|
-
// `kind: "episodic"` memory source AND the run carries an owner —
|
|
31
|
-
// the loop gates on both, so any other agent is byte-identical.
|
|
32
|
-
extractEpisode: createEpisodeExtract(),
|
|
33
|
-
generate: createAgentGenerate(agent, context.env),
|
|
34
|
-
instanceId: context.event.instanceId,
|
|
35
|
-
params: context.params,
|
|
36
|
-
paths: options?.paths ?? DEFAULT_AGENT_FUNCTION_PATHS,
|
|
37
|
-
// The loop reads its own owner-gated thread back through
|
|
38
|
-
// `agents:*` queries. The default `context.run` forwards no
|
|
39
|
-
// identity, so on an OWNED thread those reads would come back
|
|
40
|
-
// empty and the model would answer blind. `resolveAgentRun`
|
|
41
|
-
// dispatches an owner-scoped run under that verified identity so
|
|
42
|
-
// the owner gate admits the loop's reads (ownerless runs keep the
|
|
43
|
-
// identity-free `context.run`). See `resolve-run.ts`.
|
|
44
|
-
run: resolveAgentRun(context.run, context.params.owner, context.env),
|
|
45
|
-
step: context.step,
|
|
46
|
-
// The streaming seam is wired and ready, but stays dormant until a
|
|
47
|
-
// live token sink is threaded onto the run (a follow-up wires
|
|
48
|
-
// `onTokenDelta` to the stream transport). With no sink the loop
|
|
49
|
-
// takes the byte-identical non-streaming `generate` path.
|
|
50
|
-
streamGenerate: createStreamGenerate(agent, context.env)
|
|
51
|
-
}),
|
|
52
|
-
name: agent.name ?? agentDefaultName(exportName)
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
export { compileAgentWorkflow as default };
|