@plasm_lang/vercel-agent 0.3.123 → 0.3.125
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/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/instrumentation.ts +11 -13
- package/src/nitro/agent-summary.ts +60 -1
- package/src/nitro/build-application.ts +22 -1
- package/src/nitro/paths.ts +5 -0
- package/src/runtime/plasm-agent.ts +30 -30
- package/src/telemetry/eve-agent-runs.ts +103 -0
- package/src/telemetry/eve-tool-loop.ts +145 -0
- package/templates/mcp-radar/agent/instrumentation.ts +1 -1
- package/templates/mcp-radar/lib/run-radar.ts +6 -1
- package/templates/scaffold/agent/instrumentation.ts +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasm_lang/vercel-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.125",
|
|
4
4
|
"description": "Catalog-native TypeScript agent framework (Plasm CGS/CML, Vercel AI SDK, Nitro-oriented)",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@ai-sdk/otel": "1.0.14",
|
|
67
67
|
"@opentelemetry/api": "^1.9.0",
|
|
68
|
-
"@plasm_lang/engine": "^0.3.
|
|
68
|
+
"@plasm_lang/engine": "^0.3.125",
|
|
69
69
|
"@vercel/blob": "^0.27.3",
|
|
70
70
|
"@vercel/connect": "^0.2.6",
|
|
71
71
|
"@vercel/kv": "^3.0.0",
|
package/src/index.ts
CHANGED
package/src/instrumentation.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
registerTelemetry,
|
|
4
|
-
type Telemetry,
|
|
5
|
-
type TelemetryOptions,
|
|
6
|
-
} from "ai";
|
|
1
|
+
import { OpenTelemetry } from "@ai-sdk/otel";
|
|
2
|
+
import { registerTelemetry, type TelemetryOptions } from "ai";
|
|
7
3
|
|
|
8
4
|
/** OpenTelemetry attribute keys for Plasm agent spans (Section 9). */
|
|
9
5
|
export const PlasmSpanAttributes = {
|
|
@@ -25,27 +21,29 @@ export interface AgentInstrumentationOptions {
|
|
|
25
21
|
|
|
26
22
|
let registered = false;
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
/** Register global AI SDK OTEL integration (Eve uses OpenTelemetry + runtimeContext). */
|
|
25
|
+
export function ensureOtelIntegration(): void {
|
|
26
|
+
if (registered) return;
|
|
27
|
+
registerTelemetry(new OpenTelemetry({ runtimeContext: true }));
|
|
28
|
+
registered = true;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
/** Register global AI SDK OTEL integration (eve-style auto-discovered instrumentation). */
|
|
33
32
|
export function registerAgentInstrumentation(
|
|
34
33
|
options: AgentInstrumentationOptions = {},
|
|
35
34
|
): void {
|
|
36
|
-
|
|
37
|
-
registerTelemetry(otelIntegration(options));
|
|
38
|
-
registered = true;
|
|
35
|
+
ensureOtelIntegration();
|
|
39
36
|
void options.serviceName;
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
/** Per-call AI SDK telemetry settings
|
|
39
|
+
/** Per-call AI SDK telemetry settings (OTEL registered in agent/instrumentation.ts). */
|
|
43
40
|
export function createAgentTelemetry(
|
|
44
41
|
options: AgentInstrumentationOptions = {},
|
|
45
42
|
): TelemetryOptions {
|
|
46
|
-
registerAgentInstrumentation(options);
|
|
47
43
|
return {
|
|
48
44
|
isEnabled: true,
|
|
49
45
|
functionId: options.serviceName ?? "plasm-agent",
|
|
46
|
+
recordInputs: true,
|
|
47
|
+
recordOutputs: true,
|
|
50
48
|
};
|
|
51
49
|
}
|
|
@@ -7,10 +7,11 @@ import { exportScheduleTaskManifest } from "../authoring/schedule-manager.js";
|
|
|
7
7
|
import type { AgentDefinition } from "../define-agent.js";
|
|
8
8
|
import type { ProjectDiscovery } from "../discovery/project-walker.js";
|
|
9
9
|
import { frameworkPackageVersion } from "../package-version.js";
|
|
10
|
-
import { plasmAgentSummaryPath } from "./paths.js";
|
|
10
|
+
import { plasmAgentSummaryPath, eveAgentSummaryPath } from "./paths.js";
|
|
11
11
|
|
|
12
12
|
export const VERCEL_PLASM_AGENT_SUMMARY_KIND = "vercel-plasm-agent-summary";
|
|
13
13
|
export const VERCEL_PLASM_AGENT_SUMMARY_VERSION = 3;
|
|
14
|
+
export const VERCEL_EVE_AGENT_SUMMARY_KIND = "vercel-eve-agent-summary";
|
|
14
15
|
|
|
15
16
|
export interface PlasmAgentSummaryPayload {
|
|
16
17
|
kind: typeof VERCEL_PLASM_AGENT_SUMMARY_KIND;
|
|
@@ -131,3 +132,61 @@ export async function emitPlasmAgentSummary(options: {
|
|
|
131
132
|
await writeFile(outPath, `${JSON.stringify(options.summary, null, 2)}\n`, "utf8");
|
|
132
133
|
return outPath;
|
|
133
134
|
}
|
|
135
|
+
|
|
136
|
+
function mapSkillSourceKind(sourceKind: string): "markdown" | "module" | "skill-package" {
|
|
137
|
+
if (sourceKind === "markdown") return "markdown";
|
|
138
|
+
if (sourceKind === "skill-package") return "skill-package";
|
|
139
|
+
return "module";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Eve-compatible summary for Vercel Agent Runs build ingestion (`.eve/agent-summary.json`). */
|
|
143
|
+
export function toEveAgentSummary(
|
|
144
|
+
summary: PlasmAgentSummaryPayload,
|
|
145
|
+
instructionsMarkdown?: string | null,
|
|
146
|
+
): Record<string, unknown> {
|
|
147
|
+
return {
|
|
148
|
+
kind: VERCEL_EVE_AGENT_SUMMARY_KIND,
|
|
149
|
+
schemaVersion: VERCEL_PLASM_AGENT_SUMMARY_VERSION,
|
|
150
|
+
generatorVersion: summary.generatorVersion,
|
|
151
|
+
agent: {
|
|
152
|
+
name: summary.agent.name,
|
|
153
|
+
...(summary.agent.description ? { description: summary.agent.description } : {}),
|
|
154
|
+
modelId: summary.agent.modelId,
|
|
155
|
+
},
|
|
156
|
+
instructions: summary.instructions
|
|
157
|
+
? {
|
|
158
|
+
logicalPath: summary.instructions.logicalPath,
|
|
159
|
+
sourceKind: summary.instructions.sourceKind === "markdown" ? "markdown" : "module",
|
|
160
|
+
markdown: instructionsMarkdown ?? summary.instructions.markdown ?? "",
|
|
161
|
+
}
|
|
162
|
+
: null,
|
|
163
|
+
schedules: summary.schedules,
|
|
164
|
+
tools: summary.tools,
|
|
165
|
+
skills: summary.skills.map((skill) => ({
|
|
166
|
+
name: skill.name,
|
|
167
|
+
description: skill.description,
|
|
168
|
+
logicalPath: skill.logicalPath,
|
|
169
|
+
sourceKind: mapSkillSourceKind(skill.sourceKind),
|
|
170
|
+
})),
|
|
171
|
+
connections: summary.connections,
|
|
172
|
+
channels: summary.channels,
|
|
173
|
+
sandbox: summary.sandbox,
|
|
174
|
+
subagents: summary.subagents,
|
|
175
|
+
diagnostics: summary.diagnostics,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function emitEveCompatibleAgentSummary(options: {
|
|
180
|
+
projectRoot: string;
|
|
181
|
+
summary: PlasmAgentSummaryPayload;
|
|
182
|
+
instructionsMarkdown?: string | null;
|
|
183
|
+
}): Promise<string> {
|
|
184
|
+
const outPath = eveAgentSummaryPath(options.projectRoot);
|
|
185
|
+
await mkdir(path.dirname(outPath), { recursive: true });
|
|
186
|
+
await writeFile(
|
|
187
|
+
outPath,
|
|
188
|
+
`${JSON.stringify(toEveAgentSummary(options.summary, options.instructionsMarkdown), null, 2)}\n`,
|
|
189
|
+
"utf8",
|
|
190
|
+
);
|
|
191
|
+
return outPath;
|
|
192
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
|
|
3
4
|
import type { CompiledSlotMap } from "../cli/compile-authored-slots.js";
|
|
4
5
|
import { loadAuthoredSlots } from "../authoring/slot-loader.js";
|
|
5
6
|
import type { ProjectDiscovery } from "../discovery/project-walker.js";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
buildPlasmAgentSummary,
|
|
9
|
+
emitEveCompatibleAgentSummary,
|
|
10
|
+
emitPlasmAgentSummary,
|
|
11
|
+
} from "./agent-summary.js";
|
|
7
12
|
import { buildNitroOutput } from "./build-nitro-output.js";
|
|
8
13
|
import { createPlasmNitro } from "./create-plasm-nitro.js";
|
|
9
14
|
import { patchVercelOutputConfig } from "./patch-vercel-config.js";
|
|
@@ -18,6 +23,7 @@ import { writeWorkflowDispatchRoute } from "./write-workflow-dispatch-route.js";
|
|
|
18
23
|
export interface PlasmApplicationBuildResult {
|
|
19
24
|
outputDir: string;
|
|
20
25
|
agentSummaryPath: string;
|
|
26
|
+
eveAgentSummaryPath: string;
|
|
21
27
|
vercelOutput: boolean;
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -85,12 +91,27 @@ export async function buildPlasmApplication(options: {
|
|
|
85
91
|
});
|
|
86
92
|
const agentSummaryPath = await emitPlasmAgentSummary({ projectRoot, summary });
|
|
87
93
|
|
|
94
|
+
let instructionsMarkdown: string | null = null;
|
|
95
|
+
if (discovery.instructions?.path) {
|
|
96
|
+
try {
|
|
97
|
+
instructionsMarkdown = await readFile(discovery.instructions.path, "utf8");
|
|
98
|
+
} catch {
|
|
99
|
+
instructionsMarkdown = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const eveAgentSummaryPath = await emitEveCompatibleAgentSummary({
|
|
103
|
+
projectRoot,
|
|
104
|
+
summary,
|
|
105
|
+
instructionsMarkdown,
|
|
106
|
+
});
|
|
107
|
+
|
|
88
108
|
const vercelOutput = isVercelBuildEnvironment();
|
|
89
109
|
const resolvedOutput = vercelOutput ? vercelOutputDir(projectRoot) : outputDir;
|
|
90
110
|
|
|
91
111
|
return {
|
|
92
112
|
outputDir: resolvedOutput,
|
|
93
113
|
agentSummaryPath,
|
|
114
|
+
eveAgentSummaryPath,
|
|
94
115
|
vercelOutput,
|
|
95
116
|
};
|
|
96
117
|
} finally {
|
package/src/nitro/paths.ts
CHANGED
|
@@ -4,6 +4,7 @@ export const PLASM_NITRO_BUILD_DIR = ".plasm/nitro";
|
|
|
4
4
|
export const PLASM_NITRO_ROUTES_DIR = ".plasm/nitro/routes";
|
|
5
5
|
export const PLASM_NITRO_OUTPUT_DIR = ".plasm/nitro-output";
|
|
6
6
|
export const PLASM_AGENT_SUMMARY_PATH = ".plasm/agent-summary.json";
|
|
7
|
+
export const EVE_AGENT_SUMMARY_PATH = ".eve/agent-summary.json";
|
|
7
8
|
|
|
8
9
|
export function plasmNitroBuildDir(projectRoot: string): string {
|
|
9
10
|
return path.join(projectRoot, PLASM_NITRO_BUILD_DIR);
|
|
@@ -21,6 +22,10 @@ export function plasmAgentSummaryPath(projectRoot: string): string {
|
|
|
21
22
|
return path.join(projectRoot, PLASM_AGENT_SUMMARY_PATH);
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export function eveAgentSummaryPath(projectRoot: string): string {
|
|
26
|
+
return path.join(projectRoot, EVE_AGENT_SUMMARY_PATH);
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
export function vercelOutputDir(projectRoot: string): string {
|
|
25
30
|
return path.join(projectRoot, ".vercel", "output");
|
|
26
31
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { type LanguageModel, type ModelMessage, type ToolSet } from "ai";
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
7
|
AgentBuildConfig,
|
|
@@ -19,10 +19,16 @@ import { maybeCompactMessages } from "../runtime/compaction.js";
|
|
|
19
19
|
import { AgentRuntime, type AgentRuntimeConfig } from "../runtime/agent-runtime.js";
|
|
20
20
|
import { createHarnessTools, renderSkillIndex } from "../tools/harness-tools.js";
|
|
21
21
|
import { createPlasmTools } from "../tools/plasm-tools.js";
|
|
22
|
+
import { runEveToolLoop, type AgentStepEvent } from "../telemetry/eve-tool-loop.js";
|
|
23
|
+
import type { EveChannelKind } from "../telemetry/eve-agent-runs.js";
|
|
24
|
+
|
|
25
|
+
export type { AgentStepEvent };
|
|
22
26
|
|
|
23
27
|
export interface PlasmAgentConfig extends AgentRuntimeConfig {
|
|
24
28
|
/** AI Gateway model slug, e.g. `anthropic/claude-sonnet-4.6`. */
|
|
25
29
|
model: string | LanguageModel;
|
|
30
|
+
/** Agent Runs / OTEL function id (defaults from agent root directory name). */
|
|
31
|
+
agentName?: string;
|
|
26
32
|
instructionsPath?: string;
|
|
27
33
|
maxSteps?: number;
|
|
28
34
|
telemetry?: boolean;
|
|
@@ -36,22 +42,18 @@ export interface PlasmAgentConfig extends AgentRuntimeConfig {
|
|
|
36
42
|
getAuthoringContext?: () => AuthoringContext;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
export interface AgentStepEvent {
|
|
40
|
-
toolCalls?: Array<{ toolName: string }>;
|
|
41
|
-
text?: string;
|
|
42
|
-
finishReason?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
45
|
export interface AgentGenerateOptions {
|
|
46
46
|
messages?: ModelMessage[];
|
|
47
47
|
resetConversation?: boolean;
|
|
48
48
|
onStepFinish?: (step: AgentStepEvent) => void | Promise<void>;
|
|
49
|
+
/** Eve Agent Runs channel kind (`schedule`, `http`, `channel:<name>`, …). */
|
|
50
|
+
channelKind?: EveChannelKind;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export interface AgentTurnResult {
|
|
52
54
|
text: string;
|
|
53
55
|
steps: unknown[];
|
|
54
|
-
usage: Awaited<ReturnType<typeof
|
|
56
|
+
usage: Awaited<ReturnType<typeof runEveToolLoop>>["usage"];
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
export class PlasmAgent {
|
|
@@ -67,6 +69,7 @@ export class PlasmAgent {
|
|
|
67
69
|
private readonly hookRunner?: HookRunner;
|
|
68
70
|
private readonly subagentRegistry?: SubagentRegistry;
|
|
69
71
|
private readonly getAuthoringContext?: () => AuthoringContext;
|
|
72
|
+
private readonly agentName: string;
|
|
70
73
|
private conversation: ModelMessage[] = [];
|
|
71
74
|
|
|
72
75
|
constructor(config: PlasmAgentConfig) {
|
|
@@ -77,6 +80,10 @@ export class PlasmAgent {
|
|
|
77
80
|
config.instructionsPath ?? path.join(config.agentRoot, "instructions.md");
|
|
78
81
|
this.maxSteps = config.maxSteps ?? 20;
|
|
79
82
|
this.telemetryEnabled = config.telemetry ?? true;
|
|
83
|
+
this.agentName =
|
|
84
|
+
config.agentName?.trim() ||
|
|
85
|
+
process.env.PLASM_AGENT_NAME?.trim() ||
|
|
86
|
+
path.basename(path.dirname(config.agentRoot));
|
|
80
87
|
this.loadedSkills = config.loadedSkills ?? [];
|
|
81
88
|
this.compaction = config.compaction;
|
|
82
89
|
const skillsFlag = config.experimental?.skills;
|
|
@@ -142,7 +149,7 @@ export class PlasmAgent {
|
|
|
142
149
|
} as ToolSet;
|
|
143
150
|
|
|
144
151
|
const telemetry = this.telemetryEnabled
|
|
145
|
-
? createAgentTelemetry({ serviceName:
|
|
152
|
+
? createAgentTelemetry({ serviceName: this.agentName })
|
|
146
153
|
: { isEnabled: false };
|
|
147
154
|
const model = resolveGatewayModel(this.model, this.modelOptions);
|
|
148
155
|
|
|
@@ -160,31 +167,24 @@ export class PlasmAgent {
|
|
|
160
167
|
|
|
161
168
|
messages = await maybeCompactMessages(messages, this.compaction, this.model);
|
|
162
169
|
|
|
163
|
-
const
|
|
170
|
+
const onStepFinish = async (step: AgentStepEvent) => {
|
|
171
|
+
await options.onStepFinish?.(step);
|
|
172
|
+
if (!this.hookRunner || !this.getAuthoringContext) return;
|
|
173
|
+
const toolsUsed = (step.toolCalls ?? []).map((call) => call.toolName);
|
|
174
|
+
await this.hookRunner.emit("agent:step", this.getAuthoringContext(), { toolsUsed });
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = await runEveToolLoop({
|
|
164
178
|
model,
|
|
165
179
|
system,
|
|
166
180
|
tools,
|
|
167
|
-
stopWhen: stepCountIs(this.maxSteps),
|
|
168
|
-
experimental_telemetry: telemetry,
|
|
169
|
-
onStepFinish: async (step: AgentStepEvent) => {
|
|
170
|
-
await options.onStepFinish?.(step);
|
|
171
|
-
if (!this.hookRunner || !this.getAuthoringContext) return;
|
|
172
|
-
const toolsUsed = (step.toolCalls ?? []).map((call) => call.toolName);
|
|
173
|
-
await this.hookRunner.emit("agent:step", this.getAuthoringContext(), { toolsUsed });
|
|
174
|
-
},
|
|
175
|
-
...(this.modelOptions?.temperature !== undefined
|
|
176
|
-
? { temperature: this.modelOptions.temperature }
|
|
177
|
-
: {}),
|
|
178
|
-
...(this.modelOptions?.maxOutputTokens !== undefined
|
|
179
|
-
? { maxOutputTokens: this.modelOptions.maxOutputTokens }
|
|
180
|
-
: {}),
|
|
181
|
-
...(this.modelOptions?.topP !== undefined ? { topP: this.modelOptions.topP } : {}),
|
|
182
|
-
...(this.modelOptions?.topK !== undefined ? { topK: this.modelOptions.topK } : {}),
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const result = await generateText({
|
|
186
|
-
...generation,
|
|
187
181
|
messages,
|
|
182
|
+
maxSteps: this.maxSteps,
|
|
183
|
+
agentName: this.agentName,
|
|
184
|
+
channelKind: options.channelKind,
|
|
185
|
+
telemetry,
|
|
186
|
+
onStepFinish,
|
|
187
|
+
modelOptions: this.modelOptions,
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
if (!externalMessages) {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
4
|
+
import type { TelemetryOptions } from "ai";
|
|
5
|
+
|
|
6
|
+
import { frameworkPackageVersion } from "../package-version.js";
|
|
7
|
+
|
|
8
|
+
const EVE_TRACER_NAME = "eve";
|
|
9
|
+
|
|
10
|
+
export type EveChannelKind = string;
|
|
11
|
+
|
|
12
|
+
export interface EveEmissionState {
|
|
13
|
+
sessionId: string;
|
|
14
|
+
turnId: string;
|
|
15
|
+
sequence: number;
|
|
16
|
+
stepIndex: number;
|
|
17
|
+
channelKind: EveChannelKind;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function eveEnvironment(): string {
|
|
21
|
+
return process.env.VERCEL_ENV?.trim() || process.env.NODE_ENV?.trim() || "development";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Session id for one agent turn — matches Eve `eve.session.id` on Agent Runs spans. */
|
|
25
|
+
export function createEveSessionId(): string {
|
|
26
|
+
return randomUUID();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Turn id inside a session (Eve default: `turn_0`, `turn_1`, …). */
|
|
30
|
+
export function createEveTurnId(sequence: number): string {
|
|
31
|
+
return `turn_${sequence}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Runtime context keys Vercel Agent Runs reads from AI SDK spans. */
|
|
35
|
+
export function buildEveRuntimeContext(state: EveEmissionState): Record<string, string> {
|
|
36
|
+
return {
|
|
37
|
+
"eve.version": frameworkPackageVersion(),
|
|
38
|
+
"eve.environment": eveEnvironment(),
|
|
39
|
+
"eve.session.id": state.sessionId,
|
|
40
|
+
"eve.turn.id": state.turnId,
|
|
41
|
+
"eve.turn.sequence": String(state.sequence),
|
|
42
|
+
"eve.step.index": String(state.stepIndex),
|
|
43
|
+
"eve.channel.kind": state.channelKind,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Eve-style telemetry options: record I/O + propagate runtime context onto spans. */
|
|
48
|
+
export function enrichEveTelemetry(
|
|
49
|
+
base: TelemetryOptions,
|
|
50
|
+
runtimeContext: Record<string, string>,
|
|
51
|
+
): TelemetryOptions {
|
|
52
|
+
const includeRuntimeContext: Record<string, boolean> = {};
|
|
53
|
+
for (const key of Object.keys(runtimeContext)) {
|
|
54
|
+
includeRuntimeContext[key] = true;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
...base,
|
|
58
|
+
isEnabled: base.isEnabled ?? true,
|
|
59
|
+
recordInputs: base.recordInputs ?? true,
|
|
60
|
+
recordOutputs: base.recordOutputs ?? true,
|
|
61
|
+
includeRuntimeContext,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface EveTurnSpanOptions extends EveEmissionState {
|
|
66
|
+
functionId?: string;
|
|
67
|
+
attributes?: Record<string, string | number | boolean>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function eveTurnSpanAttributes(options: EveTurnSpanOptions): Record<string, string | number | boolean> {
|
|
71
|
+
return {
|
|
72
|
+
"eve.version": frameworkPackageVersion(),
|
|
73
|
+
"eve.environment": eveEnvironment(),
|
|
74
|
+
"eve.session.id": options.sessionId,
|
|
75
|
+
"eve.turn.id": options.turnId,
|
|
76
|
+
"eve.turn.sequence": options.sequence,
|
|
77
|
+
"eve.step.index": options.stepIndex,
|
|
78
|
+
"eve.channel.kind": options.channelKind,
|
|
79
|
+
...(options.functionId ? { "ai.telemetry.functionId": options.functionId } : {}),
|
|
80
|
+
...options.attributes,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Parent span Vercel Agent Runs ingests (`ai.eve.turn` + `eve.session.id`).
|
|
86
|
+
* Eve emits one per tool-loop step; Plasm matches that shape.
|
|
87
|
+
*/
|
|
88
|
+
export async function withEveTurnSpan<T>(
|
|
89
|
+
options: EveTurnSpanOptions,
|
|
90
|
+
fn: () => Promise<T>,
|
|
91
|
+
): Promise<T> {
|
|
92
|
+
const tracer = trace.getTracer(EVE_TRACER_NAME);
|
|
93
|
+
const attributes = eveTurnSpanAttributes(options);
|
|
94
|
+
|
|
95
|
+
return tracer.startActiveSpan("ai.eve.turn", { attributes }, async (span) => {
|
|
96
|
+
try {
|
|
97
|
+
return await fn();
|
|
98
|
+
} catch (err) {
|
|
99
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
streamText,
|
|
3
|
+
stepCountIs,
|
|
4
|
+
type LanguageModel,
|
|
5
|
+
type LanguageModelUsage,
|
|
6
|
+
type ModelMessage,
|
|
7
|
+
type TelemetryOptions,
|
|
8
|
+
type ToolSet,
|
|
9
|
+
} from "ai";
|
|
10
|
+
import type { Context } from "@ai-sdk/provider-utils";
|
|
11
|
+
|
|
12
|
+
import { ensureOtelIntegration } from "../instrumentation.js";
|
|
13
|
+
import {
|
|
14
|
+
buildEveRuntimeContext,
|
|
15
|
+
createEveSessionId,
|
|
16
|
+
createEveTurnId,
|
|
17
|
+
enrichEveTelemetry,
|
|
18
|
+
withEveTurnSpan,
|
|
19
|
+
type EveChannelKind,
|
|
20
|
+
} from "./eve-agent-runs.js";
|
|
21
|
+
|
|
22
|
+
export interface AgentStepEvent {
|
|
23
|
+
toolCalls?: Array<{ toolName: string }>;
|
|
24
|
+
text?: string;
|
|
25
|
+
finishReason?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EveToolLoopModelOptions {
|
|
29
|
+
temperature?: number;
|
|
30
|
+
maxOutputTokens?: number;
|
|
31
|
+
topP?: number;
|
|
32
|
+
topK?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface EveToolLoopOptions {
|
|
36
|
+
model: LanguageModel;
|
|
37
|
+
system: string;
|
|
38
|
+
tools: ToolSet;
|
|
39
|
+
messages: ModelMessage[];
|
|
40
|
+
maxSteps: number;
|
|
41
|
+
agentName: string;
|
|
42
|
+
channelKind?: EveChannelKind;
|
|
43
|
+
telemetry?: TelemetryOptions;
|
|
44
|
+
onStepFinish?: (step: AgentStepEvent) => void | Promise<void>;
|
|
45
|
+
modelOptions?: EveToolLoopModelOptions;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface EveToolLoopResult {
|
|
49
|
+
text: string;
|
|
50
|
+
steps: unknown[];
|
|
51
|
+
usage: LanguageModelUsage;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Eve-compatible tool loop: one `ai.eve.turn` parent span per step, `streamText`
|
|
56
|
+
* child spans via AI SDK OTEL (`OpenTelemetry` + runtime context).
|
|
57
|
+
*/
|
|
58
|
+
export async function runEveToolLoop(options: EveToolLoopOptions): Promise<EveToolLoopResult> {
|
|
59
|
+
ensureOtelIntegration();
|
|
60
|
+
|
|
61
|
+
const sessionId = createEveSessionId();
|
|
62
|
+
const turnId = createEveTurnId(0);
|
|
63
|
+
const channelKind = options.channelKind ?? "unknown";
|
|
64
|
+
|
|
65
|
+
let messages = options.messages;
|
|
66
|
+
let stepIndex = 0;
|
|
67
|
+
let finalText = "";
|
|
68
|
+
let lastUsage: LanguageModelUsage | undefined;
|
|
69
|
+
const aggregatedSteps: unknown[] = [];
|
|
70
|
+
|
|
71
|
+
while (stepIndex < options.maxSteps) {
|
|
72
|
+
const runtimeContext = buildEveRuntimeContext({
|
|
73
|
+
sessionId,
|
|
74
|
+
turnId,
|
|
75
|
+
sequence: 0,
|
|
76
|
+
stepIndex,
|
|
77
|
+
channelKind,
|
|
78
|
+
});
|
|
79
|
+
const telemetry = enrichEveTelemetry(
|
|
80
|
+
options.telemetry ?? { isEnabled: true, functionId: options.agentName },
|
|
81
|
+
runtimeContext,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const stepResult = await withEveTurnSpan(
|
|
85
|
+
{
|
|
86
|
+
sessionId,
|
|
87
|
+
turnId,
|
|
88
|
+
sequence: 0,
|
|
89
|
+
stepIndex,
|
|
90
|
+
channelKind,
|
|
91
|
+
functionId: options.agentName,
|
|
92
|
+
},
|
|
93
|
+
async () => {
|
|
94
|
+
const streamResult = streamText({
|
|
95
|
+
model: options.model,
|
|
96
|
+
system: options.system,
|
|
97
|
+
tools: options.tools,
|
|
98
|
+
messages,
|
|
99
|
+
stopWhen: stepCountIs(1),
|
|
100
|
+
runtimeContext: runtimeContext as Context,
|
|
101
|
+
experimental_telemetry: telemetry,
|
|
102
|
+
onStepFinish: options.onStepFinish,
|
|
103
|
+
...(options.modelOptions?.temperature !== undefined
|
|
104
|
+
? { temperature: options.modelOptions.temperature }
|
|
105
|
+
: {}),
|
|
106
|
+
...(options.modelOptions?.maxOutputTokens !== undefined
|
|
107
|
+
? { maxOutputTokens: options.modelOptions.maxOutputTokens }
|
|
108
|
+
: {}),
|
|
109
|
+
...(options.modelOptions?.topP !== undefined ? { topP: options.modelOptions.topP } : {}),
|
|
110
|
+
...(options.modelOptions?.topK !== undefined ? { topK: options.modelOptions.topK } : {}),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const [text, finishReason, steps, usage, response] = await Promise.all([
|
|
114
|
+
streamResult.text,
|
|
115
|
+
streamResult.finishReason,
|
|
116
|
+
streamResult.steps,
|
|
117
|
+
streamResult.usage,
|
|
118
|
+
streamResult.response,
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
return { text, finishReason, steps, usage, response };
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
finalText = stepResult.text;
|
|
126
|
+
lastUsage = stepResult.usage;
|
|
127
|
+
aggregatedSteps.push(...stepResult.steps);
|
|
128
|
+
messages = stepResult.response.messages as ModelMessage[];
|
|
129
|
+
|
|
130
|
+
stepIndex += 1;
|
|
131
|
+
if (stepResult.finishReason !== "tool-calls") {
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!lastUsage) {
|
|
137
|
+
throw new Error("eve tool loop produced no model steps");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
text: finalText,
|
|
142
|
+
steps: aggregatedSteps,
|
|
143
|
+
usage: lastUsage,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -11,6 +11,8 @@ export const MCP_RADAR_INTENT =
|
|
|
11
11
|
export interface RadarRunOptions {
|
|
12
12
|
force?: boolean;
|
|
13
13
|
reset?: boolean;
|
|
14
|
+
/** Eve Agent Runs `eve.channel.kind` (default `schedule`). */
|
|
15
|
+
channelKind?: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface RadarRunResult {
|
|
@@ -54,7 +56,10 @@ export async function runRadar(
|
|
|
54
56
|
try {
|
|
55
57
|
resetRunAudit();
|
|
56
58
|
const agent = await ctx.getAgent();
|
|
57
|
-
const turn = await agent.generate(buildRadarGoal(options), {
|
|
59
|
+
const turn = await agent.generate(buildRadarGoal(options), {
|
|
60
|
+
resetConversation: false,
|
|
61
|
+
channelKind: options.channelKind ?? "schedule",
|
|
62
|
+
});
|
|
58
63
|
void drainRunAudit();
|
|
59
64
|
|
|
60
65
|
return {
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
2
4
|
import { OpenTelemetry } from "@ai-sdk/otel";
|
|
5
|
+
import { registerOTel } from "@vercel/otel";
|
|
3
6
|
import { registerTelemetry } from "ai";
|
|
4
7
|
|
|
8
|
+
const agentRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const serviceName =
|
|
10
|
+
process.env.PLASM_AGENT_NAME?.trim() || path.basename(path.dirname(agentRoot));
|
|
11
|
+
|
|
5
12
|
export function register(): void {
|
|
6
|
-
registerOTel({ serviceName
|
|
7
|
-
registerTelemetry(new OpenTelemetry());
|
|
13
|
+
registerOTel({ serviceName });
|
|
14
|
+
registerTelemetry(new OpenTelemetry({ runtimeContext: true }));
|
|
8
15
|
}
|
|
9
16
|
|
|
10
17
|
register();
|