@themoltnet/pi-extension 0.6.0 → 0.8.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
  /**
@@ -259,6 +272,7 @@ declare const Task: TObject< {
259
272
  imposedByAgentId: TUnion<[TString, TNull]>;
260
273
  imposedByHumanId: TUnion<[TString, TNull]>;
261
274
  acceptedAttemptN: TUnion<[TNumber, TNull]>;
275
+ requiredExecutorTrustLevel: TUnion<[TLiteral<"selfDeclared">, TLiteral<"agentSigned">, TLiteral<"releaseVerifiedTool">, TLiteral<"sandboxAttested">]>;
262
276
  status: TUnion<[TLiteral<"queued">, TLiteral<"dispatched">, TLiteral<"running">, TLiteral<"completed">, TLiteral<"failed">, TLiteral<"cancelled">, TLiteral<"expired">]>;
263
277
  queuedAt: TString;
264
278
  completedAt: TUnion<[TString, TNull]>;
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
@@ -2535,6 +2536,7 @@ function createDiaryGrantsNamespace(context) {
2535
2536
  }
2536
2537
  };
2537
2538
  }
2539
+ new TextEncoder();
2538
2540
  //#endregion
2539
2541
  //#region ../../node_modules/.pnpm/@noble+hashes@1.8.0/node_modules/@noble/hashes/esm/utils.js
2540
2542
  /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
@@ -4265,6 +4267,13 @@ etc.sha512Sync = (...m) => {
4265
4267
  return hash.digest();
4266
4268
  };
4267
4269
  //#endregion
4270
+ //#region ../crypto-service/src/executor-attestation.ts
4271
+ etc.sha512Sync = (...m) => {
4272
+ const hash = createHash("sha512");
4273
+ m.forEach((msg) => hash.update(msg));
4274
+ return hash.digest();
4275
+ };
4276
+ //#endregion
4268
4277
  //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/codecs/json.js
4269
4278
  var textEncoder$1 = new TextEncoder();
4270
4279
  new TextDecoder();
@@ -8600,6 +8609,143 @@ function computePiJudgeRecipeCid(inputs) {
8600
8609
  };
8601
8610
  }
8602
8611
  //#endregion
8612
+ //#region src/otel/index.ts
8613
+ var TRACER_NAME = "@themoltnet/pi-extension/otel";
8614
+ function stripReservedAttrs(attrs) {
8615
+ const out = {};
8616
+ for (const [k, v] of Object.entries(attrs)) {
8617
+ if (k.startsWith("gen_ai.")) continue;
8618
+ out[k] = v;
8619
+ }
8620
+ return out;
8621
+ }
8622
+ function createPiOtelExtension(options = {}) {
8623
+ return function piOtelExtension(pi) {
8624
+ const tracer = trace.getTracer(TRACER_NAME);
8625
+ const extraAttrs = stripReservedAttrs(options.spanAttributes ?? {});
8626
+ let sessionSpan;
8627
+ let sessionCtx = context.active();
8628
+ let turnSpan;
8629
+ let turnCtx = context.active();
8630
+ let currentModel;
8631
+ const toolSpans = /* @__PURE__ */ new Map();
8632
+ function drainToolSpans(reason) {
8633
+ for (const [, entry] of toolSpans) {
8634
+ entry.span.setStatus({
8635
+ code: SpanStatusCode.ERROR,
8636
+ message: reason
8637
+ });
8638
+ entry.span.end();
8639
+ }
8640
+ toolSpans.clear();
8641
+ }
8642
+ function endTurnSpan() {
8643
+ if (!turnSpan) return;
8644
+ drainToolSpans("tool span not closed before turn end");
8645
+ turnSpan.end();
8646
+ turnSpan = void 0;
8647
+ turnCtx = sessionCtx;
8648
+ }
8649
+ function endSessionSpan() {
8650
+ drainToolSpans("tool span not closed before session shutdown");
8651
+ endTurnSpan();
8652
+ if (sessionSpan) {
8653
+ sessionSpan.setStatus({ code: SpanStatusCode.OK });
8654
+ sessionSpan.end();
8655
+ sessionSpan = void 0;
8656
+ sessionCtx = context.active();
8657
+ }
8658
+ currentModel = void 0;
8659
+ }
8660
+ pi.on("session_start", (event, ctx) => {
8661
+ endSessionSpan();
8662
+ const agentName = options.agentName ?? "pi";
8663
+ sessionSpan = tracer.startSpan(`invoke_agent ${agentName}`, { attributes: {
8664
+ ...extraAttrs,
8665
+ "gen_ai.operation.name": "invoke_agent",
8666
+ "gen_ai.agent.name": agentName,
8667
+ "session.reason": event.reason,
8668
+ "session.cwd": ctx.cwd
8669
+ } }, context.active());
8670
+ sessionCtx = trace.setSpan(context.active(), sessionSpan);
8671
+ turnCtx = sessionCtx;
8672
+ });
8673
+ pi.on("session_shutdown", () => {
8674
+ endSessionSpan();
8675
+ });
8676
+ pi.on("model_select", (event) => {
8677
+ currentModel = {
8678
+ provider: event.model.provider,
8679
+ id: event.model.id
8680
+ };
8681
+ if (sessionSpan) {
8682
+ sessionSpan.setAttribute("gen_ai.request.model", event.model.id);
8683
+ sessionSpan.setAttribute("gen_ai.provider.name", event.model.provider);
8684
+ }
8685
+ });
8686
+ pi.on("turn_start", (event) => {
8687
+ if (!sessionSpan) return;
8688
+ const modelLabel = currentModel?.id ?? "unknown";
8689
+ turnSpan = tracer.startSpan(`chat ${modelLabel}`, { attributes: {
8690
+ ...extraAttrs,
8691
+ "gen_ai.operation.name": "chat",
8692
+ "gen_ai.request.model": currentModel?.id ?? "unknown",
8693
+ "gen_ai.provider.name": currentModel?.provider ?? "unknown",
8694
+ "turn.index": event.turnIndex
8695
+ } }, sessionCtx);
8696
+ turnCtx = trace.setSpan(sessionCtx, turnSpan);
8697
+ });
8698
+ pi.on("turn_end", (event) => {
8699
+ if (!turnSpan) return;
8700
+ const usage = extractUsage(event.message);
8701
+ if (usage) {
8702
+ turnSpan.setAttribute("gen_ai.usage.input_tokens", usage.input);
8703
+ turnSpan.setAttribute("gen_ai.usage.output_tokens", usage.output);
8704
+ }
8705
+ turnSpan.setAttribute("turn.tool_results", event.toolResults?.length ?? 0);
8706
+ turnSpan.setStatus({ code: SpanStatusCode.OK });
8707
+ endTurnSpan();
8708
+ });
8709
+ pi.on("tool_execution_start", (event) => {
8710
+ const parentCtx = turnSpan ? turnCtx : sessionCtx;
8711
+ const span = tracer.startSpan(`execute_tool ${event.toolName}`, { attributes: {
8712
+ ...extraAttrs,
8713
+ "gen_ai.operation.name": "execute_tool",
8714
+ "gen_ai.tool.name": event.toolName,
8715
+ "gen_ai.tool.call.id": event.toolCallId
8716
+ } }, parentCtx);
8717
+ toolSpans.set(event.toolCallId, {
8718
+ span,
8719
+ startedAt: Date.now()
8720
+ });
8721
+ });
8722
+ pi.on("tool_execution_end", (event) => {
8723
+ const entry = toolSpans.get(event.toolCallId);
8724
+ if (!entry) return;
8725
+ const durationMs = Date.now() - entry.startedAt;
8726
+ entry.span.setAttribute("tool.duration_ms", durationMs);
8727
+ if (event.isError) {
8728
+ entry.span.setAttribute("error.type", "tool_execution_error");
8729
+ entry.span.setStatus({
8730
+ code: SpanStatusCode.ERROR,
8731
+ message: "tool execution failed"
8732
+ });
8733
+ } else entry.span.setStatus({ code: SpanStatusCode.OK });
8734
+ entry.span.end();
8735
+ toolSpans.delete(event.toolCallId);
8736
+ });
8737
+ };
8738
+ }
8739
+ function extractUsage(message) {
8740
+ if (!message || typeof message !== "object" || !("usage" in message) || !("role" in message)) return null;
8741
+ const msg = message;
8742
+ if (msg.role !== "assistant" || !msg.usage) return null;
8743
+ return {
8744
+ input: msg.usage.input ?? 0,
8745
+ output: msg.usage.output ?? 0
8746
+ };
8747
+ }
8748
+ //#endregion
8603
8749
  //#region ../tasks/src/formats.ts
8604
8750
  /**
8605
8751
  * Register TypeBox string formats used across Task / TaskOutput / task-type
@@ -9086,6 +9232,12 @@ var TaskAttemptStatus = Type$1.Union([
9086
9232
  Type$1.Literal("cancelled"),
9087
9233
  Type$1.Literal("timed_out")
9088
9234
  ], { $id: "TaskAttemptStatus" });
9235
+ var ExecutorTrustLevel = Type$1.Union([
9236
+ Type$1.Literal("selfDeclared"),
9237
+ Type$1.Literal("agentSigned"),
9238
+ Type$1.Literal("releaseVerifiedTool"),
9239
+ Type$1.Literal("sandboxAttested")
9240
+ ], { $id: "ExecutorTrustLevel" });
9089
9241
  var OutputKind = Type$1.Union([Type$1.Literal("artifact"), Type$1.Literal("judgment")], { $id: "OutputKind" });
9090
9242
  var TaskMessageKind = Type$1.Union([
9091
9243
  Type$1.Literal("text_delta"),
@@ -9178,6 +9330,7 @@ Type$1.Object({
9178
9330
  imposedByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
9179
9331
  imposedByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
9180
9332
  acceptedAttemptN: Type$1.Union([Type$1.Number(), Type$1.Null()]),
9333
+ requiredExecutorTrustLevel: ExecutorTrustLevel,
9181
9334
  status: TaskStatus,
9182
9335
  queuedAt: IsoTimestamp,
9183
9336
  completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
@@ -9201,6 +9354,10 @@ Type$1.Object({
9201
9354
  status: TaskAttemptStatus,
9202
9355
  output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
9203
9356
  outputCid: Type$1.Union([Cid, Type$1.Null()]),
9357
+ claimedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
9358
+ claimedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
9359
+ completedExecutorFingerprint: Type$1.Union([Cid, Type$1.Null()]),
9360
+ completedExecutorManifest: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
9204
9361
  error: Type$1.Union([TaskError, Type$1.Null()]),
9205
9362
  usage: Type$1.Union([TaskUsage, Type$1.Null()]),
9206
9363
  contentSignature: Type$1.Union([Type$1.String(), Type$1.Null()]),
@@ -9977,7 +10134,15 @@ async function executePiTask(claimedTask, reporter, opts) {
9977
10134
  const modelHandle = getModel(opts.provider, opts.model);
9978
10135
  const resourceLoader = new DefaultResourceLoader({
9979
10136
  cwd: mountPath,
9980
- agentDir: piAuthDir
10137
+ agentDir: piAuthDir,
10138
+ extensionFactories: [createPiOtelExtension({
10139
+ agentName: opts.agentName,
10140
+ spanAttributes: {
10141
+ "moltnet.task.id": task.id,
10142
+ "moltnet.task.attempt": attemptN,
10143
+ "moltnet.task.type": task.taskType
10144
+ }
10145
+ })]
9981
10146
  });
9982
10147
  await resourceLoader.reload();
9983
10148
  session = (await createAgentSession({
@@ -10094,10 +10259,16 @@ async function executePiTask(claimedTask, reporter, opts) {
10094
10259
  if (reporterOpen) {
10095
10260
  try {
10096
10261
  await reporter.finalize(finalUsage);
10097
- } catch {}
10262
+ } catch (err) {
10263
+ const detail = err instanceof Error ? err.message : String(err);
10264
+ console.error(`executePiTask: reporter.finalize() failed for task ${task.id} attempt ${attemptN}: ${detail}`);
10265
+ }
10098
10266
  try {
10099
10267
  await reporter.close();
10100
- } catch {}
10268
+ } catch (err) {
10269
+ const detail = err instanceof Error ? err.message : String(err);
10270
+ console.error(`executePiTask: reporter.close() failed for task ${task.id} attempt ${attemptN}: ${detail}`);
10271
+ }
10101
10272
  }
10102
10273
  await managed.vm.close();
10103
10274
  }
@@ -10415,4 +10586,4 @@ function moltnetExtension(pi) {
10415
10586
  registerMoltnetReflectCommand(pi, state);
10416
10587
  }
10417
10588
  //#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 };
10589
+ 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.8.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,9 +29,10 @@
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/sdk": "0.95.0"
34
+ "@themoltnet/agent-runtime": "0.4.0",
35
+ "@themoltnet/sdk": "0.96.0"
35
36
  },
36
37
  "peerDependencies": {
37
38
  "@mariozechner/pi-coding-agent": ">=0.67.0",
@@ -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",