@themoltnet/pi-extension 0.6.0 → 0.7.0

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.d.ts CHANGED
@@ -52,6 +52,8 @@ export declare function createGondolinWriteOps(vm: VM, localCwd: string): WriteO
52
52
  */
53
53
  export declare function createMoltNetTools(config: MoltNetToolsConfig): ToolDefinition<any, any>[];
54
54
 
55
+ export declare function createPiOtelExtension(options?: PiOtelOptions): (pi: ExtensionAPI) => void;
56
+
55
57
  /**
56
58
  * Factory that builds a pi-specific `executeTask` function suitable for
57
59
  * injection into `AgentRuntime`. The returned function caches the resolved
@@ -186,6 +188,17 @@ declare interface PiJudgeRecipeVersions {
186
188
  sdk: string | null;
187
189
  }
188
190
 
191
+ export declare interface PiOtelOptions {
192
+ /** Agent name for `gen_ai.agent.name` on the root span. */
193
+ agentName?: string;
194
+ /**
195
+ * Extra attributes merged onto every span. Use MoltNet-specific keys
196
+ * like `moltnet.task.id` — any `gen_ai.*` keys here are filtered out
197
+ * since the extension is authoritative for those.
198
+ */
199
+ spanAttributes?: Record<string, string | number | boolean>;
200
+ }
201
+
189
202
  export declare function resolvePiJudgeRecipeVersions(): PiJudgeRecipeVersions;
190
203
 
191
204
  /**
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { Type, complete, getModel } from "@mariozechner/pi-ai";
11
11
  import { RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, loadGuestAssets } from "@earendil-works/gondolin";
12
12
  import { parseEnv } from "node:util";
13
13
  import { fileURLToPath } from "node:url";
14
+ import { SpanStatusCode, context, trace } from "@opentelemetry/api";
14
15
  import { FormatRegistry, Type as Type$1 } from "@sinclair/typebox";
15
16
  import { Value } from "@sinclair/typebox/value";
16
17
  //#region ../api-client/src/generated/core/bodySerializer.gen.ts
@@ -8600,6 +8601,143 @@ function computePiJudgeRecipeCid(inputs) {
8600
8601
  };
8601
8602
  }
8602
8603
  //#endregion
8604
+ //#region src/otel/index.ts
8605
+ var TRACER_NAME = "@themoltnet/pi-extension/otel";
8606
+ function stripReservedAttrs(attrs) {
8607
+ const out = {};
8608
+ for (const [k, v] of Object.entries(attrs)) {
8609
+ if (k.startsWith("gen_ai.")) continue;
8610
+ out[k] = v;
8611
+ }
8612
+ return out;
8613
+ }
8614
+ function createPiOtelExtension(options = {}) {
8615
+ return function piOtelExtension(pi) {
8616
+ const tracer = trace.getTracer(TRACER_NAME);
8617
+ const extraAttrs = stripReservedAttrs(options.spanAttributes ?? {});
8618
+ let sessionSpan;
8619
+ let sessionCtx = context.active();
8620
+ let turnSpan;
8621
+ let turnCtx = context.active();
8622
+ let currentModel;
8623
+ const toolSpans = /* @__PURE__ */ new Map();
8624
+ function drainToolSpans(reason) {
8625
+ for (const [, entry] of toolSpans) {
8626
+ entry.span.setStatus({
8627
+ code: SpanStatusCode.ERROR,
8628
+ message: reason
8629
+ });
8630
+ entry.span.end();
8631
+ }
8632
+ toolSpans.clear();
8633
+ }
8634
+ function endTurnSpan() {
8635
+ if (!turnSpan) return;
8636
+ drainToolSpans("tool span not closed before turn end");
8637
+ turnSpan.end();
8638
+ turnSpan = void 0;
8639
+ turnCtx = sessionCtx;
8640
+ }
8641
+ function endSessionSpan() {
8642
+ drainToolSpans("tool span not closed before session shutdown");
8643
+ endTurnSpan();
8644
+ if (sessionSpan) {
8645
+ sessionSpan.setStatus({ code: SpanStatusCode.OK });
8646
+ sessionSpan.end();
8647
+ sessionSpan = void 0;
8648
+ sessionCtx = context.active();
8649
+ }
8650
+ currentModel = void 0;
8651
+ }
8652
+ pi.on("session_start", (event, ctx) => {
8653
+ endSessionSpan();
8654
+ const agentName = options.agentName ?? "pi";
8655
+ sessionSpan = tracer.startSpan(`invoke_agent ${agentName}`, { attributes: {
8656
+ ...extraAttrs,
8657
+ "gen_ai.operation.name": "invoke_agent",
8658
+ "gen_ai.agent.name": agentName,
8659
+ "session.reason": event.reason,
8660
+ "session.cwd": ctx.cwd
8661
+ } }, context.active());
8662
+ sessionCtx = trace.setSpan(context.active(), sessionSpan);
8663
+ turnCtx = sessionCtx;
8664
+ });
8665
+ pi.on("session_shutdown", () => {
8666
+ endSessionSpan();
8667
+ });
8668
+ pi.on("model_select", (event) => {
8669
+ currentModel = {
8670
+ provider: event.model.provider,
8671
+ id: event.model.id
8672
+ };
8673
+ if (sessionSpan) {
8674
+ sessionSpan.setAttribute("gen_ai.request.model", event.model.id);
8675
+ sessionSpan.setAttribute("gen_ai.provider.name", event.model.provider);
8676
+ }
8677
+ });
8678
+ pi.on("turn_start", (event) => {
8679
+ if (!sessionSpan) return;
8680
+ const modelLabel = currentModel?.id ?? "unknown";
8681
+ turnSpan = tracer.startSpan(`chat ${modelLabel}`, { attributes: {
8682
+ ...extraAttrs,
8683
+ "gen_ai.operation.name": "chat",
8684
+ "gen_ai.request.model": currentModel?.id ?? "unknown",
8685
+ "gen_ai.provider.name": currentModel?.provider ?? "unknown",
8686
+ "turn.index": event.turnIndex
8687
+ } }, sessionCtx);
8688
+ turnCtx = trace.setSpan(sessionCtx, turnSpan);
8689
+ });
8690
+ pi.on("turn_end", (event) => {
8691
+ if (!turnSpan) return;
8692
+ const usage = extractUsage(event.message);
8693
+ if (usage) {
8694
+ turnSpan.setAttribute("gen_ai.usage.input_tokens", usage.input);
8695
+ turnSpan.setAttribute("gen_ai.usage.output_tokens", usage.output);
8696
+ }
8697
+ turnSpan.setAttribute("turn.tool_results", event.toolResults?.length ?? 0);
8698
+ turnSpan.setStatus({ code: SpanStatusCode.OK });
8699
+ endTurnSpan();
8700
+ });
8701
+ pi.on("tool_execution_start", (event) => {
8702
+ const parentCtx = turnSpan ? turnCtx : sessionCtx;
8703
+ const span = tracer.startSpan(`execute_tool ${event.toolName}`, { attributes: {
8704
+ ...extraAttrs,
8705
+ "gen_ai.operation.name": "execute_tool",
8706
+ "gen_ai.tool.name": event.toolName,
8707
+ "gen_ai.tool.call.id": event.toolCallId
8708
+ } }, parentCtx);
8709
+ toolSpans.set(event.toolCallId, {
8710
+ span,
8711
+ startedAt: Date.now()
8712
+ });
8713
+ });
8714
+ pi.on("tool_execution_end", (event) => {
8715
+ const entry = toolSpans.get(event.toolCallId);
8716
+ if (!entry) return;
8717
+ const durationMs = Date.now() - entry.startedAt;
8718
+ entry.span.setAttribute("tool.duration_ms", durationMs);
8719
+ if (event.isError) {
8720
+ entry.span.setAttribute("error.type", "tool_execution_error");
8721
+ entry.span.setStatus({
8722
+ code: SpanStatusCode.ERROR,
8723
+ message: "tool execution failed"
8724
+ });
8725
+ } else entry.span.setStatus({ code: SpanStatusCode.OK });
8726
+ entry.span.end();
8727
+ toolSpans.delete(event.toolCallId);
8728
+ });
8729
+ };
8730
+ }
8731
+ function extractUsage(message) {
8732
+ if (!message || typeof message !== "object" || !("usage" in message) || !("role" in message)) return null;
8733
+ const msg = message;
8734
+ if (msg.role !== "assistant" || !msg.usage) return null;
8735
+ return {
8736
+ input: msg.usage.input ?? 0,
8737
+ output: msg.usage.output ?? 0
8738
+ };
8739
+ }
8740
+ //#endregion
8603
8741
  //#region ../tasks/src/formats.ts
8604
8742
  /**
8605
8743
  * Register TypeBox string formats used across Task / TaskOutput / task-type
@@ -9977,7 +10115,15 @@ async function executePiTask(claimedTask, reporter, opts) {
9977
10115
  const modelHandle = getModel(opts.provider, opts.model);
9978
10116
  const resourceLoader = new DefaultResourceLoader({
9979
10117
  cwd: mountPath,
9980
- agentDir: piAuthDir
10118
+ agentDir: piAuthDir,
10119
+ extensionFactories: [createPiOtelExtension({
10120
+ agentName: opts.agentName,
10121
+ spanAttributes: {
10122
+ "moltnet.task.id": task.id,
10123
+ "moltnet.task.attempt": attemptN,
10124
+ "moltnet.task.type": task.taskType
10125
+ }
10126
+ })]
9981
10127
  });
9982
10128
  await resourceLoader.reload();
9983
10129
  session = (await createAgentSession({
@@ -10094,10 +10240,16 @@ async function executePiTask(claimedTask, reporter, opts) {
10094
10240
  if (reporterOpen) {
10095
10241
  try {
10096
10242
  await reporter.finalize(finalUsage);
10097
- } catch {}
10243
+ } catch (err) {
10244
+ const detail = err instanceof Error ? err.message : String(err);
10245
+ console.error(`executePiTask: reporter.finalize() failed for task ${task.id} attempt ${attemptN}: ${detail}`);
10246
+ }
10098
10247
  try {
10099
10248
  await reporter.close();
10100
- } catch {}
10249
+ } catch (err) {
10250
+ const detail = err instanceof Error ? err.message : String(err);
10251
+ console.error(`executePiTask: reporter.close() failed for task ${task.id} attempt ${attemptN}: ${detail}`);
10252
+ }
10101
10253
  }
10102
10254
  await managed.vm.close();
10103
10255
  }
@@ -10415,4 +10567,4 @@ function moltnetExtension(pi) {
10415
10567
  registerMoltnetReflectCommand(pi, state);
10416
10568
  }
10417
10569
  //#endregion
10418
- export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildPiJudgeRecipeManifest, computePiJudgeRecipeCid, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiTaskExecutor, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, loadCredentials, resolvePiJudgeRecipeVersions, resumeVm, toGuestPath };
10570
+ export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildPiJudgeRecipeManifest, computePiJudgeRecipeCid, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiTaskExecutor, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, loadCredentials, resolvePiJudgeRecipeVersions, resumeVm, toGuestPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "MoltNet pi extension — sandboxed tool execution in Gondolin VMs with MoltNet identity and persistent memory",
6
6
  "license": "MIT",
@@ -29,8 +29,9 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@earendil-works/gondolin": "^0.7.0",
32
+ "@opentelemetry/api": "^1.9.0",
32
33
  "@sinclair/typebox": "^0.34.0",
33
- "@themoltnet/agent-runtime": "0.2.1",
34
+ "@themoltnet/agent-runtime": "0.3.0",
34
35
  "@themoltnet/sdk": "0.95.0"
35
36
  },
36
37
  "peerDependencies": {
@@ -48,6 +49,7 @@
48
49
  "devDependencies": {
49
50
  "@mariozechner/pi-ai": "^0.67.68",
50
51
  "@mariozechner/pi-coding-agent": "^0.67.68",
52
+ "@opentelemetry/sdk-trace-base": "^2.5.1",
51
53
  "@types/node": "^20.11.0",
52
54
  "typescript": "^5.3.3",
53
55
  "vite": "^8.0.0",