@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.
- package/LICENSE.md +105 -0
- package/README.md +38 -31
- package/dist/channels.d.mts +74 -0
- package/dist/channels.d.ts +74 -0
- package/dist/channels.mjs +181 -0
- package/dist/component.d.mts +104 -0
- package/dist/component.d.ts +104 -0
- package/dist/component.mjs +418 -0
- package/dist/inbound.d.mts +34 -0
- package/dist/inbound.d.ts +34 -0
- package/dist/inbound.mjs +32 -0
- package/dist/index.d.mts +832 -0
- package/dist/index.d.ts +832 -0
- package/dist/index.mjs +18 -0
- package/dist/naming.d.mts +30 -0
- package/dist/naming.d.ts +30 -0
- package/dist/naming.mjs +8 -0
- package/dist/packem_shared/AGENT_MODULE-Dnt_-AAT.mjs +24 -0
- package/dist/packem_shared/VoiceSessionDO-BdwlLaXC.mjs +297 -0
- package/dist/packem_shared/adaptMcpResult-wtNMvLoP.mjs +65 -0
- package/dist/packem_shared/agentAsTool-CUHlWsmt.mjs +98 -0
- package/dist/packem_shared/base64-BVwtgRJV.mjs +18 -0
- package/dist/packem_shared/braintrustTelemetry-TP7Kwuuj.mjs +47 -0
- package/dist/packem_shared/buildModelMessages-BWFigaoo.mjs +69 -0
- package/dist/packem_shared/codeTool-CjgJOC9t.mjs +122 -0
- package/dist/packem_shared/collectAgenticMemoryTools-QrzpV-WX.mjs +97 -0
- package/dist/packem_shared/combineTelemetry-DCyaaWAI.mjs +43 -0
- package/dist/packem_shared/common-DQXayow6.mjs +89 -0
- package/dist/packem_shared/compileAgentWorkflow-DYFFyx7i.mjs +78 -0
- package/dist/packem_shared/consoleTelemetry--3sWfu1R.mjs +93 -0
- package/dist/packem_shared/createAgentContext-4xJGXNR4.mjs +50 -0
- package/dist/packem_shared/createAgentGenerate-DO7Z96zX.mjs +192 -0
- package/dist/packem_shared/createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs +69 -0
- package/dist/packem_shared/defineAgent-DAwAZC9P.mjs +148 -0
- package/dist/packem_shared/defineSkill-Ctf_S-rz.mjs +22 -0
- package/dist/packem_shared/functionTool-D6lCa2jB.mjs +20 -0
- package/dist/packem_shared/graph-component-Bbaxxymp.mjs +217 -0
- package/dist/packem_shared/memory-D4FPcBsX.mjs +12 -0
- package/dist/packem_shared/normalizeEntityName-BouctxLC.mjs +3 -0
- package/dist/packem_shared/otlpTelemetry-DU5ZmoEV.mjs +177 -0
- package/dist/packem_shared/runAgentLoop-M8PKbtWT.mjs +493 -0
- package/dist/packem_shared/runVoiceTurn-LnqLvCRR.mjs +211 -0
- package/dist/packem_shared/sandboxComponent-DR3pTwBL.mjs +194 -0
- package/dist/packem_shared/sentryTelemetry-A4F5ndh9.mjs +36 -0
- package/dist/packem_shared/types.d-boAM2Yi1.d.mts +1114 -0
- package/dist/packem_shared/types.d-boAM2Yi1.d.ts +1114 -0
- package/dist/sandbox.d.mts +204 -0
- package/dist/sandbox.d.ts +204 -0
- package/dist/sandbox.mjs +113 -0
- package/dist/telemetry/index.d.mts +254 -0
- package/dist/telemetry/index.d.ts +254 -0
- package/dist/telemetry/index.mjs +5 -0
- package/package.json +88 -7
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const assistantMessage = (row) => {
|
|
2
|
+
if (row.toolCalls === void 0 || row.toolCalls.length === 0) {
|
|
3
|
+
return { content: row.content, role: "assistant" };
|
|
4
|
+
}
|
|
5
|
+
return {
|
|
6
|
+
content: [
|
|
7
|
+
...row.content.length > 0 ? [{ text: row.content, type: "text" }] : [],
|
|
8
|
+
...row.toolCalls.map((call) => {
|
|
9
|
+
return { input: call.input, toolCallId: call.id, toolName: call.name, type: "tool-call" };
|
|
10
|
+
})
|
|
11
|
+
],
|
|
12
|
+
role: "assistant"
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const toolMessage = (row) => {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
output: { type: "text", value: row.content },
|
|
20
|
+
toolCallId: row.toolCallId ?? "",
|
|
21
|
+
toolName: row.toolName ?? "",
|
|
22
|
+
type: "tool-result"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
role: "tool"
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const buildModelMessages = (options) => {
|
|
29
|
+
const messages = [];
|
|
30
|
+
if (options.instructions !== void 0 && options.instructions.length > 0) {
|
|
31
|
+
messages.push({ content: options.instructions, role: "system" });
|
|
32
|
+
}
|
|
33
|
+
if (options.memoryContext !== void 0 && options.memoryContext.length > 0) {
|
|
34
|
+
messages.push({ content: `Relevant context retrieved for this conversation:
|
|
35
|
+
|
|
36
|
+
${options.memoryContext}`, role: "system" });
|
|
37
|
+
}
|
|
38
|
+
if (options.summary !== void 0 && options.summary.length > 0) {
|
|
39
|
+
messages.push({ content: `Summary of the earlier conversation:
|
|
40
|
+
|
|
41
|
+
${options.summary}`, role: "system" });
|
|
42
|
+
}
|
|
43
|
+
for (const row of options.history) {
|
|
44
|
+
if (row.status === "awaiting_approval") {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
switch (row.role) {
|
|
48
|
+
case "assistant": {
|
|
49
|
+
messages.push(assistantMessage(row));
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "system": {
|
|
53
|
+
messages.push({ content: row.content, role: "system" });
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "tool": {
|
|
57
|
+
messages.push(toolMessage(row));
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "user": {
|
|
61
|
+
messages.push({ content: row.content, role: "user" });
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return messages;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export { buildModelMessages };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { jsonSchema } from 'ai';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MAX_STEPS = 16;
|
|
5
|
+
const MAX_STEP_OUTPUT_CHARS = 4e3;
|
|
6
|
+
const capOutput = (output) => {
|
|
7
|
+
if (output === void 0) {
|
|
8
|
+
return output;
|
|
9
|
+
}
|
|
10
|
+
const serialized = typeof output === "string" ? output : JSON.stringify(output);
|
|
11
|
+
if (serialized.length <= MAX_STEP_OUTPUT_CHARS) {
|
|
12
|
+
return output;
|
|
13
|
+
}
|
|
14
|
+
return `${serialized.slice(0, MAX_STEP_OUTPUT_CHARS)}… [truncated]`;
|
|
15
|
+
};
|
|
16
|
+
const getPath = (value, path) => {
|
|
17
|
+
let current = value;
|
|
18
|
+
for (const key of path.split(".")) {
|
|
19
|
+
if (current === null || typeof current !== "object" || !Object.hasOwn(current, key)) {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
current = current[key];
|
|
23
|
+
}
|
|
24
|
+
return current;
|
|
25
|
+
};
|
|
26
|
+
const resolveReferences = (value, results) => {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map((item) => resolveReferences(item, results));
|
|
29
|
+
}
|
|
30
|
+
if (value === null || typeof value !== "object") {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
const object = value;
|
|
34
|
+
if (typeof object["$from"] === "string") {
|
|
35
|
+
const from = object["$from"];
|
|
36
|
+
if (!(from in results)) {
|
|
37
|
+
throw new LunoraError("BAD_REQUEST", `@lunora/agent: code step references unknown result "${from}" (define it in an earlier step)`);
|
|
38
|
+
}
|
|
39
|
+
return typeof object["$path"] === "string" ? getPath(results[from], object["$path"]) : results[from];
|
|
40
|
+
}
|
|
41
|
+
const resolved = {};
|
|
42
|
+
for (const [key, nested] of Object.entries(object)) {
|
|
43
|
+
resolved[key] = resolveReferences(nested, results);
|
|
44
|
+
}
|
|
45
|
+
return resolved;
|
|
46
|
+
};
|
|
47
|
+
const runToolScript = async (script, tools, context, maxSteps) => {
|
|
48
|
+
const steps = Array.isArray(script.steps) ? script.steps.slice(0, maxSteps) : [];
|
|
49
|
+
const byId = {};
|
|
50
|
+
const results = [];
|
|
51
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
52
|
+
for (const step of steps) {
|
|
53
|
+
if (seenIds.has(step.id)) {
|
|
54
|
+
throw new LunoraError(
|
|
55
|
+
"BAD_REQUEST",
|
|
56
|
+
`@lunora/agent: duplicate code step id "${step.id}" — each step id must be unique (later steps reference an output by id via $from)`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
seenIds.add(step.id);
|
|
60
|
+
}
|
|
61
|
+
for (const step of steps) {
|
|
62
|
+
const tool = tools[step.tool];
|
|
63
|
+
if (!tool) {
|
|
64
|
+
throw new LunoraError("BAD_REQUEST", `@lunora/agent: code step calls unknown tool "${step.tool}" (available: ${Object.keys(tools).join(", ")})`);
|
|
65
|
+
}
|
|
66
|
+
const input = resolveReferences(step.input ?? {}, byId);
|
|
67
|
+
const stepContext = {
|
|
68
|
+
...context,
|
|
69
|
+
idempotencyKey: `${context.idempotencyKey}:${step.id}`,
|
|
70
|
+
toolCallId: `${context.toolCallId}:${step.id}`
|
|
71
|
+
};
|
|
72
|
+
const output = await tool.execute(input, stepContext);
|
|
73
|
+
byId[step.id] = output;
|
|
74
|
+
results.push({ id: step.id, output: capOutput(output) });
|
|
75
|
+
}
|
|
76
|
+
return { final: results.at(-1)?.output, results };
|
|
77
|
+
};
|
|
78
|
+
const buildCodeDescription = (tools) => `Compose MULTIPLE tool calls in one turn as a script, instead of one call per turn. Provide \`steps\`: an ordered array of \`{ id, tool, input }\`. A later step's \`input\` may reference an earlier step's output with \`{ "$from": "<stepId>", "$path": "optional.dot.path" }\`. Available tools: ${Object.entries(tools).map(([name, tool]) => `"${name}" — ${tool.description}`).join("; ")}.`;
|
|
79
|
+
const CODE_TOOL_SCHEMA = jsonSchema({
|
|
80
|
+
properties: {
|
|
81
|
+
steps: {
|
|
82
|
+
description: "Ordered tool calls; a later input may reference an earlier output via { $from, $path }.",
|
|
83
|
+
items: {
|
|
84
|
+
additionalProperties: false,
|
|
85
|
+
properties: {
|
|
86
|
+
id: { description: "A name later steps reference this output by.", type: "string" },
|
|
87
|
+
input: { additionalProperties: true, description: "The tool input (may embed $from refs).", type: "object" },
|
|
88
|
+
tool: { description: "The tool to call.", type: "string" }
|
|
89
|
+
},
|
|
90
|
+
required: ["id", "tool"],
|
|
91
|
+
type: "object"
|
|
92
|
+
},
|
|
93
|
+
type: "array"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
required: ["steps"],
|
|
97
|
+
type: "object"
|
|
98
|
+
});
|
|
99
|
+
const codeTool = (tools, options = {}) => {
|
|
100
|
+
const provided = tools;
|
|
101
|
+
if (!provided || typeof provided !== "object" || Object.keys(provided).length === 0) {
|
|
102
|
+
throw new LunoraError("INTERNAL", "@lunora/agent: codeTool requires a non-empty map of tools to compose");
|
|
103
|
+
}
|
|
104
|
+
for (const [name, tool] of Object.entries(provided)) {
|
|
105
|
+
if (tool.needsApproval !== void 0 && tool.needsApproval !== false) {
|
|
106
|
+
throw new LunoraError(
|
|
107
|
+
"INTERNAL",
|
|
108
|
+
`@lunora/agent: codeTool cannot compose "${name}" — it has a \`needsApproval\` gate a code-mode script can't pause for. Expose it as a normal top-level tool, or gate the whole script via codeTool's \`needsApproval\`.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
113
|
+
return {
|
|
114
|
+
description: options.description ?? buildCodeDescription(tools),
|
|
115
|
+
execute: (script, context) => runToolScript(script, tools, context, maxSteps),
|
|
116
|
+
inputSchema: CODE_TOOL_SCHEMA,
|
|
117
|
+
isLunoraAgentTool: true,
|
|
118
|
+
...options.needsApproval === void 0 ? {} : { needsApproval: options.needsApproval }
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export { codeTool, resolveReferences, runToolScript };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jsonSchema } from 'ai';
|
|
2
|
+
import { toFunctionReference } from './AGENT_MODULE-Dnt_-AAT.mjs';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SNIPPET_CHARS = 240;
|
|
5
|
+
const MAX_SEARCH_TOPK = 50;
|
|
6
|
+
const snippet = (text, max) => text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
7
|
+
const clampSearchTopK = (value) => {
|
|
8
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
return Math.min(MAX_SEARCH_TOPK, Math.max(1, Math.trunc(value)));
|
|
12
|
+
};
|
|
13
|
+
const toSearchResults = (retrieved, snippetChars) => {
|
|
14
|
+
const raw = retrieved ?? {};
|
|
15
|
+
const chunks = Array.isArray(raw.chunks) ? raw.chunks : [];
|
|
16
|
+
const sources = Array.isArray(raw.sources) ? raw.sources : [];
|
|
17
|
+
return {
|
|
18
|
+
results: chunks.map((chunk) => {
|
|
19
|
+
return {
|
|
20
|
+
id: typeof chunk.id === "string" ? chunk.id : "",
|
|
21
|
+
score: typeof chunk.score === "number" ? chunk.score : 0,
|
|
22
|
+
snippet: snippet(typeof chunk.text === "string" ? chunk.text : "", snippetChars),
|
|
23
|
+
sourceId: typeof chunk.sourceId === "string" ? chunk.sourceId : ""
|
|
24
|
+
};
|
|
25
|
+
}),
|
|
26
|
+
sources
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const searchInputSchema = jsonSchema({
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
properties: {
|
|
32
|
+
query: { description: "Natural-language search query.", type: "string" },
|
|
33
|
+
topK: { description: "Maximum number of hits to return.", type: "number" }
|
|
34
|
+
},
|
|
35
|
+
required: ["query"],
|
|
36
|
+
type: "object"
|
|
37
|
+
});
|
|
38
|
+
const readInputSchema = jsonSchema({
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
properties: {
|
|
41
|
+
id: { description: "The `id` of a hit returned by the search tool.", type: "string" }
|
|
42
|
+
},
|
|
43
|
+
required: ["id"],
|
|
44
|
+
type: "object"
|
|
45
|
+
});
|
|
46
|
+
const buildSearchTool = (source, sourceReference, readToolName) => {
|
|
47
|
+
const target = toFunctionReference(sourceReference);
|
|
48
|
+
const snippetChars = Math.max(1, source.snippetChars ?? DEFAULT_SNIPPET_CHARS);
|
|
49
|
+
const configuredTopK = source.topK;
|
|
50
|
+
const pull = readToolName === void 0 ? "" : ` Use \`${readToolName}\` with a hit's \`id\` to fetch its full text.`;
|
|
51
|
+
return {
|
|
52
|
+
description: `Search long-term memory for passages relevant to a query. Returns ranked { id, sourceId, score, snippet } hits — call again with a refined query to dig deeper.${pull}`,
|
|
53
|
+
execute: async (input, context) => {
|
|
54
|
+
const topK = clampSearchTopK(input.topK) ?? configuredTopK;
|
|
55
|
+
const retrieved = await context.run(target, { query: input.query, ...topK === void 0 ? {} : { topK } });
|
|
56
|
+
return toSearchResults(retrieved, snippetChars);
|
|
57
|
+
},
|
|
58
|
+
inputSchema: searchInputSchema,
|
|
59
|
+
isLunoraAgentTool: true
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const buildReadTool = (readReference) => {
|
|
63
|
+
const target = toFunctionReference(readReference);
|
|
64
|
+
return {
|
|
65
|
+
description: "Fetch the full text of a memory document by the `id` from a search hit.",
|
|
66
|
+
execute: (input, context) => context.run(target, { id: input.id }),
|
|
67
|
+
inputSchema: readInputSchema,
|
|
68
|
+
isLunoraAgentTool: true
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
const collectAgenticMemoryTools = (config, skills) => {
|
|
72
|
+
const sources = [];
|
|
73
|
+
if (config.memory) {
|
|
74
|
+
sources.push({ key: "default", ...config.memory });
|
|
75
|
+
}
|
|
76
|
+
for (const skill of skills) {
|
|
77
|
+
if (skill.knowledge) {
|
|
78
|
+
sources.push({ key: skill.name, ...skill.knowledge });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const tools = {};
|
|
82
|
+
for (const source of sources) {
|
|
83
|
+
if (source.kind === "graph" || source.kind === "episodic" || source.mode !== "agentic" || source.source === void 0) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const searchName = source.key === "default" ? "searchMemory" : `search_${source.key}`;
|
|
87
|
+
const readName = source.key === "default" ? "readMemory" : `read_${source.key}`;
|
|
88
|
+
const hasRead = source.read !== void 0;
|
|
89
|
+
tools[searchName] = buildSearchTool(source, source.source, hasRead ? readName : void 0);
|
|
90
|
+
if (hasRead) {
|
|
91
|
+
tools[readName] = buildReadTool(source.read);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return tools;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { collectAgenticMemoryTools, toSearchResults };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const fanOut = (callbacks) => async (event) => {
|
|
2
|
+
await Promise.allSettled(callbacks.map(async (callback) => callback?.(event)));
|
|
3
|
+
};
|
|
4
|
+
const composeWrappers = (wrappers) => {
|
|
5
|
+
const defined = wrappers.filter((wrapper) => typeof wrapper === "function");
|
|
6
|
+
const combined = (options) => {
|
|
7
|
+
let composed = options.execute;
|
|
8
|
+
for (const wrapper of defined.toReversed()) {
|
|
9
|
+
const inner = composed;
|
|
10
|
+
composed = () => wrapper({ ...options, execute: inner });
|
|
11
|
+
}
|
|
12
|
+
return composed();
|
|
13
|
+
};
|
|
14
|
+
return combined;
|
|
15
|
+
};
|
|
16
|
+
const combineTelemetry = (...integrations) => {
|
|
17
|
+
return {
|
|
18
|
+
executeLanguageModelCall: composeWrappers(integrations.map((integration) => integration.executeLanguageModelCall)),
|
|
19
|
+
executeTool: composeWrappers(integrations.map((integration) => integration.executeTool)),
|
|
20
|
+
onAbort: fanOut(integrations.map((integration) => integration.onAbort)),
|
|
21
|
+
onEmbedEnd: fanOut(integrations.map((integration) => integration.onEmbedEnd)),
|
|
22
|
+
onEmbedStart: fanOut(integrations.map((integration) => integration.onEmbedStart)),
|
|
23
|
+
onEnd: fanOut(integrations.map((integration) => integration.onEnd)),
|
|
24
|
+
onError: fanOut(integrations.map((integration) => integration.onError)),
|
|
25
|
+
onLanguageModelCallEnd: fanOut(integrations.map((integration) => integration.onLanguageModelCallEnd)),
|
|
26
|
+
onLanguageModelCallStart: fanOut(integrations.map((integration) => integration.onLanguageModelCallStart)),
|
|
27
|
+
// eslint-disable-next-line sonarjs/deprecation -- deprecated in the ai@7 type but still actively dispatched (dist onObjectStepEnd fan-out); combine must not drop an integration's callback
|
|
28
|
+
onObjectStepEnd: fanOut(integrations.map((integration) => integration.onObjectStepEnd)),
|
|
29
|
+
// eslint-disable-next-line sonarjs/deprecation -- deprecated in the ai@7 type but still actively dispatched (dist onObjectStepStart fan-out); combine must not drop an integration's callback
|
|
30
|
+
onObjectStepStart: fanOut(integrations.map((integration) => integration.onObjectStepStart)),
|
|
31
|
+
onRerankEnd: fanOut(integrations.map((integration) => integration.onRerankEnd)),
|
|
32
|
+
onRerankStart: fanOut(integrations.map((integration) => integration.onRerankStart)),
|
|
33
|
+
onStart: fanOut(integrations.map((integration) => integration.onStart)),
|
|
34
|
+
onStepEnd: fanOut(integrations.map((integration) => integration.onStepEnd)),
|
|
35
|
+
// eslint-disable-next-line sonarjs/deprecation -- deprecated alias of onStepEnd but ai@7 still fans step-end events to it (dist onStepEnd → onStepFinish); combine must not drop an integration's callback
|
|
36
|
+
onStepFinish: fanOut(integrations.map((integration) => integration.onStepFinish)),
|
|
37
|
+
onStepStart: fanOut(integrations.map((integration) => integration.onStepStart)),
|
|
38
|
+
onToolExecutionEnd: fanOut(integrations.map((integration) => integration.onToolExecutionEnd)),
|
|
39
|
+
onToolExecutionStart: fanOut(integrations.map((integration) => integration.onToolExecutionStart))
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export { combineTelemetry };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const readField = (source, key) => {
|
|
2
|
+
if (source === null || typeof source !== "object") {
|
|
3
|
+
return void 0;
|
|
4
|
+
}
|
|
5
|
+
return source[key];
|
|
6
|
+
};
|
|
7
|
+
const summarizeUsage = (usage) => {
|
|
8
|
+
const summary = {};
|
|
9
|
+
for (const key of ["inputTokens", "outputTokens", "totalTokens"]) {
|
|
10
|
+
const value = readField(usage, key);
|
|
11
|
+
if (typeof value === "number") {
|
|
12
|
+
summary[key] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return Object.keys(summary).length > 0 ? summary : void 0;
|
|
16
|
+
};
|
|
17
|
+
const summarizeGatewayTelemetry = (result) => {
|
|
18
|
+
const gateway = readField(readField(result, "providerMetadata"), "gateway");
|
|
19
|
+
const headers = readField(readField(result, "response"), "headers");
|
|
20
|
+
const readHeader = (name) => {
|
|
21
|
+
const getter = readField(headers, "get");
|
|
22
|
+
const value = typeof getter === "function" ? getter.call(headers, name) : readField(headers, name);
|
|
23
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
24
|
+
};
|
|
25
|
+
const summary = {};
|
|
26
|
+
const cost = readField(gateway, "cost");
|
|
27
|
+
if (typeof cost === "number" && Number.isFinite(cost)) {
|
|
28
|
+
summary.cost = cost;
|
|
29
|
+
}
|
|
30
|
+
const cachedField = readField(gateway, "cached");
|
|
31
|
+
const cacheStatusField = readField(gateway, "cacheStatus");
|
|
32
|
+
const cacheStatus = typeof cacheStatusField === "string" ? cacheStatusField : readHeader("cf-aig-cache-status");
|
|
33
|
+
if (typeof cachedField === "boolean") {
|
|
34
|
+
summary.cached = cachedField;
|
|
35
|
+
} else if (typeof cacheStatus === "string") {
|
|
36
|
+
summary.cached = cacheStatus.toUpperCase() === "HIT";
|
|
37
|
+
}
|
|
38
|
+
const metadataLogId = readField(gateway, "logId") ?? readField(gateway, "log_id");
|
|
39
|
+
const logId = typeof metadataLogId === "string" && metadataLogId.length > 0 ? metadataLogId : readHeader("cf-aig-log-id");
|
|
40
|
+
if (typeof logId === "string" && logId.length > 0) {
|
|
41
|
+
summary.logId = logId;
|
|
42
|
+
}
|
|
43
|
+
return Object.keys(summary).length > 0 ? summary : void 0;
|
|
44
|
+
};
|
|
45
|
+
const describeToolOutcome = (event) => {
|
|
46
|
+
const toolCall = readField(event, "toolCall");
|
|
47
|
+
const nameValue = readField(toolCall, "toolName");
|
|
48
|
+
const name = typeof nameValue === "string" ? nameValue : void 0;
|
|
49
|
+
const toolOutput = readField(event, "toolOutput");
|
|
50
|
+
const outputType = readField(toolOutput, "type");
|
|
51
|
+
const successField = readField(event, "success");
|
|
52
|
+
const success = typeof successField === "boolean" ? successField : outputType !== "tool-error";
|
|
53
|
+
const executionMs = readField(event, "toolExecutionMs");
|
|
54
|
+
return {
|
|
55
|
+
error: success ? void 0 : readField(toolOutput, "error") ?? readField(event, "error"),
|
|
56
|
+
name,
|
|
57
|
+
output: success ? readField(toolOutput, "output") : void 0,
|
|
58
|
+
success,
|
|
59
|
+
toolExecutionMs: typeof executionMs === "number" ? executionMs : void 0
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const toolNameOf = (event) => {
|
|
63
|
+
const nameValue = readField(readField(event, "toolCall"), "toolName");
|
|
64
|
+
return typeof nameValue === "string" ? nameValue : void 0;
|
|
65
|
+
};
|
|
66
|
+
const toolInputOf = (event) => readField(readField(event, "toolCall"), "input");
|
|
67
|
+
const contentText = (content) => {
|
|
68
|
+
if (!Array.isArray(content)) {
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
let text = "";
|
|
72
|
+
for (const part of content) {
|
|
73
|
+
if (readField(part, "type") === "text") {
|
|
74
|
+
const value = readField(part, "text");
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
text += value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return text.length > 0 ? text : void 0;
|
|
81
|
+
};
|
|
82
|
+
const describeError = (error) => {
|
|
83
|
+
if (error instanceof Error) {
|
|
84
|
+
return { message: error.message, name: error.name };
|
|
85
|
+
}
|
|
86
|
+
return error;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export { toolNameOf as a, describeToolOutcome as b, contentText as c, describeError as d, summarizeGatewayTelemetry as e, readField as r, summarizeUsage as s, toolInputOf as t };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defineWorkflow } from '@lunora/workflow';
|
|
2
|
+
import { runAgentLoop } from './runAgentLoop-M8PKbtWT.mjs';
|
|
3
|
+
import { createStreamGenerate, createAgentGenerate, createEpisodeExtract, createGraphExtract, createCompact } from './createAgentGenerate-DO7Z96zX.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-DU5ZmoEV.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, conversationId) => {
|
|
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
|
+
// Tag every generation span with the run's thread as its conversation
|
|
28
|
+
// id (one thread = one multi-turn conversation), so the cloud groups a
|
|
29
|
+
// deployed agent's turns with no app wiring. Absent → ungrouped, as before.
|
|
30
|
+
integrations: [...existingList, otlpTelemetry({ conversationId, endpoint, token })],
|
|
31
|
+
isEnabled: true
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const compileAgentWorkflow = (agent, exportName, options) => defineWorkflow({
|
|
36
|
+
handler: async (context) => {
|
|
37
|
+
const runtimeAgent = withAutoOtlpTelemetry(agent, context.env, context.params.threadKey);
|
|
38
|
+
return runAgentLoop({
|
|
39
|
+
agent: runtimeAgent,
|
|
40
|
+
// Automatic history compaction. Dormant unless the agent declares
|
|
41
|
+
// a `compaction` config; the loop gates on it, so any other agent
|
|
42
|
+
// takes the byte-identical no-compaction path.
|
|
43
|
+
compact: createCompact(),
|
|
44
|
+
env: context.env,
|
|
45
|
+
exportName,
|
|
46
|
+
// Run-end graph extraction. Dormant unless the agent declares a
|
|
47
|
+
// `kind: "graph"` memory source AND the run carries an owner — the
|
|
48
|
+
// loop gates on both, so a semantic-only agent takes the
|
|
49
|
+
// byte-identical no-extraction path.
|
|
50
|
+
extractGraph: createGraphExtract(),
|
|
51
|
+
// Run-end episode recording. Dormant unless the agent declares a
|
|
52
|
+
// `kind: "episodic"` memory source AND the run carries an owner —
|
|
53
|
+
// the loop gates on both, so any other agent is byte-identical.
|
|
54
|
+
extractEpisode: createEpisodeExtract(),
|
|
55
|
+
generate: createAgentGenerate(runtimeAgent, context.env),
|
|
56
|
+
instanceId: context.event.instanceId,
|
|
57
|
+
params: context.params,
|
|
58
|
+
paths: options?.paths ?? DEFAULT_AGENT_FUNCTION_PATHS,
|
|
59
|
+
// The loop reads its own owner-gated thread back through
|
|
60
|
+
// `agents:*` queries. The default `context.run` forwards no
|
|
61
|
+
// identity, so on an OWNED thread those reads would come back
|
|
62
|
+
// empty and the model would answer blind. `resolveAgentRun`
|
|
63
|
+
// dispatches an owner-scoped run under that verified identity so
|
|
64
|
+
// the owner gate admits the loop's reads (ownerless runs keep the
|
|
65
|
+
// identity-free `context.run`). See `resolve-run.ts`.
|
|
66
|
+
run: resolveAgentRun(context.run, context.params.owner, context.env),
|
|
67
|
+
step: context.step,
|
|
68
|
+
// The streaming seam is wired and ready, but stays dormant until a
|
|
69
|
+
// live token sink is threaded onto the run (a follow-up wires
|
|
70
|
+
// `onTokenDelta` to the stream transport). With no sink the loop
|
|
71
|
+
// takes the byte-identical non-streaming `generate` path.
|
|
72
|
+
streamGenerate: createStreamGenerate(runtimeAgent, context.env)
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
name: agent.name ?? agentDefaultName(exportName)
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export { compileAgentWorkflow as default };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { s as summarizeUsage, c as contentText, t as toolInputOf, d as describeError, r as readField, a as toolNameOf, b as describeToolOutcome } from './common-DQXayow6.mjs';
|
|
2
|
+
|
|
3
|
+
const defaultLogger = (level, message, fields) => {
|
|
4
|
+
const target = globalThis.console;
|
|
5
|
+
if (level === "error") {
|
|
6
|
+
target.error(message, fields);
|
|
7
|
+
} else if (level === "warn") {
|
|
8
|
+
target.warn(message, fields);
|
|
9
|
+
} else {
|
|
10
|
+
target.info(message, fields);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const consoleTelemetry = (options = {}) => {
|
|
14
|
+
const { functionId, logger = defaultLogger, recordInputs = false, recordOutputs = false } = options;
|
|
15
|
+
const prefix = functionId === void 0 ? "" : `[${functionId}] `;
|
|
16
|
+
const log = (level, message, fields) => {
|
|
17
|
+
logger(level, `${prefix}${message}`, fields);
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
onAbort: (event) => {
|
|
21
|
+
log("warn", "agent operation aborted", {
|
|
22
|
+
callId: readField(event, "callId"),
|
|
23
|
+
reason: readField(event, "reason")
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
onEnd: (event) => {
|
|
27
|
+
log("info", "agent operation completed", {
|
|
28
|
+
callId: readField(event, "callId"),
|
|
29
|
+
finishReason: readField(event, "finishReason"),
|
|
30
|
+
operationId: readField(event, "operationId"),
|
|
31
|
+
usage: summarizeUsage(readField(event, "usage"))
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
onError: (error) => {
|
|
35
|
+
log("error", "agent operation errored", { error: describeError(error) });
|
|
36
|
+
},
|
|
37
|
+
onLanguageModelCallEnd: (event) => {
|
|
38
|
+
const fields = {
|
|
39
|
+
callId: readField(event, "callId"),
|
|
40
|
+
finishReason: readField(event, "finishReason"),
|
|
41
|
+
modelId: readField(event, "modelId"),
|
|
42
|
+
provider: readField(event, "provider"),
|
|
43
|
+
usage: summarizeUsage(readField(event, "usage"))
|
|
44
|
+
};
|
|
45
|
+
if (recordOutputs) {
|
|
46
|
+
fields["text"] = contentText(readField(event, "content"));
|
|
47
|
+
}
|
|
48
|
+
log("info", "language model call ended", fields);
|
|
49
|
+
},
|
|
50
|
+
onStart: (event) => {
|
|
51
|
+
log("info", "agent operation started", {
|
|
52
|
+
callId: readField(event, "callId"),
|
|
53
|
+
modelId: readField(event, "modelId"),
|
|
54
|
+
operationId: readField(event, "operationId"),
|
|
55
|
+
provider: readField(event, "provider")
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
onStepEnd: (event) => {
|
|
59
|
+
log("info", "agent step ended", {
|
|
60
|
+
finishReason: readField(event, "finishReason"),
|
|
61
|
+
stepNumber: readField(event, "stepNumber"),
|
|
62
|
+
usage: summarizeUsage(readField(event, "usage"))
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
onStepStart: (event) => {
|
|
66
|
+
log("info", "agent step started", { stepNumber: readField(event, "stepNumber") });
|
|
67
|
+
},
|
|
68
|
+
onToolExecutionEnd: (event) => {
|
|
69
|
+
const outcome = describeToolOutcome(event);
|
|
70
|
+
const fields = {
|
|
71
|
+
success: outcome.success,
|
|
72
|
+
tool: outcome.name,
|
|
73
|
+
toolExecutionMs: outcome.toolExecutionMs
|
|
74
|
+
};
|
|
75
|
+
if (!outcome.success) {
|
|
76
|
+
fields["error"] = describeError(outcome.error);
|
|
77
|
+
}
|
|
78
|
+
if (recordOutputs && outcome.success) {
|
|
79
|
+
fields["output"] = outcome.output;
|
|
80
|
+
}
|
|
81
|
+
log(outcome.success ? "info" : "warn", "tool execution ended", fields);
|
|
82
|
+
},
|
|
83
|
+
onToolExecutionStart: (event) => {
|
|
84
|
+
const fields = { tool: toolNameOf(event) };
|
|
85
|
+
if (recordInputs) {
|
|
86
|
+
fields["input"] = toolInputOf(event);
|
|
87
|
+
}
|
|
88
|
+
log("info", "tool execution started", fields);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export { consoleTelemetry };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { c as createDispatchRunner } from './createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs';
|
|
2
|
+
import { LunoraError } from '@lunora/errors';
|
|
3
|
+
import { toFunctionReference, DEFAULT_AGENT_FUNCTION_PATHS } from './AGENT_MODULE-Dnt_-AAT.mjs';
|
|
4
|
+
|
|
5
|
+
const createAgentContext = (env, specs, dispatch) => {
|
|
6
|
+
const agents = {};
|
|
7
|
+
const patchThread = toFunctionReference(DEFAULT_AGENT_FUNCTION_PATHS.patchThread);
|
|
8
|
+
const resolveDispatch = () => dispatch ?? createDispatchRunner({ env, label: "@lunora/agent" });
|
|
9
|
+
for (const spec of specs) {
|
|
10
|
+
const resolve = () => {
|
|
11
|
+
const binding = env[spec.binding];
|
|
12
|
+
if (!binding || typeof binding.create !== "function" || typeof binding.get !== "function") {
|
|
13
|
+
throw new LunoraError(
|
|
14
|
+
"INTERNAL",
|
|
15
|
+
`@lunora/agent: no Workflow binding "${spec.binding}" on env for agent "${spec.exportName}" — run codegen/dev so wrangler.jsonc declares it`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return binding;
|
|
19
|
+
};
|
|
20
|
+
agents[spec.exportName] = {
|
|
21
|
+
cancel: async (id) => {
|
|
22
|
+
const instance = await resolve().get(id);
|
|
23
|
+
await instance.terminate();
|
|
24
|
+
try {
|
|
25
|
+
await resolveDispatch()(patchThread, { instanceId: id, status: "cancelled" });
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
// Carried from the codegen spec (`defineAgent({ publicRun: true })`).
|
|
30
|
+
// Gates the public `agents:agentRun` mutation fail-closed; the
|
|
31
|
+
// server-side `run(...)` below is unaffected.
|
|
32
|
+
publicRun: spec.publicRun === true,
|
|
33
|
+
run: async (input, options) => {
|
|
34
|
+
const instance = await resolve().create({ ...options?.id === void 0 ? {} : { id: options.id }, params: input });
|
|
35
|
+
return { id: instance.id };
|
|
36
|
+
},
|
|
37
|
+
sendEvent: async (id, event) => {
|
|
38
|
+
const instance = await resolve().get(id);
|
|
39
|
+
await instance.sendEvent(event);
|
|
40
|
+
},
|
|
41
|
+
status: async (id) => {
|
|
42
|
+
const instance = await resolve().get(id);
|
|
43
|
+
return instance.status();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return agents;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export { createAgentContext as default };
|