@oh-my-pi/pi-agent-core 15.3.0 → 15.3.1

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.
@@ -2,7 +2,7 @@
2
2
  * Agent loop that works with AgentMessage throughout.
3
3
  * Transforms to Message[] only at the LLM call boundary.
4
4
  */
5
- import { EventStream } from "@oh-my-pi/pi-ai";
5
+ import { type Context, EventStream } from "@oh-my-pi/pi-ai";
6
6
  import { type AgentRunCoverage, type AgentRunSummary } from "./run-collector";
7
7
  import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, StreamFn } from "./types";
8
8
  /**
@@ -52,3 +52,4 @@ export declare function agentLoopContinueDetailed(context: AgentContext, config:
52
52
  readonly detailed: () => Promise<AgentLoopDetailedResult>;
53
53
  };
54
54
  export declare const INTENT_FIELD = "_i";
55
+ export declare function normalizeTools(tools: AgentContext["tools"], injectIntent: boolean): Context["tools"];
@@ -21,6 +21,11 @@ export interface StablePrefixSnapshot {
21
21
  tools: Tool[];
22
22
  fingerprint: string;
23
23
  }
24
+ /** Options threaded through `build()` so the snapshot reflects loop-time settings. */
25
+ export interface BuildOptions {
26
+ /** Inject the `_i` intent field into tool schemas (must match agent-loop's normalizeTools). */
27
+ intentTracing: boolean;
28
+ }
24
29
  /**
25
30
  * A frozen prefix (system prompt + tools) that produces stable byte
26
31
  * sequences across `build()` calls.
@@ -38,7 +43,7 @@ export declare class StablePrefix {
38
43
  * Build or rebuild from live context.
39
44
  * Returns `true` if the prefix actually changed (cache miss imminent).
40
45
  */
41
- build(context: AgentContext): boolean;
46
+ build(context: AgentContext, options: BuildOptions): boolean;
42
47
  /** Force rebuild on the next `build()` call. */
43
48
  invalidate(): void;
44
49
  /**
@@ -89,7 +94,7 @@ export declare class AppendOnlyContextManager {
89
94
  #private;
90
95
  readonly prefix: StablePrefix;
91
96
  readonly log: AppendOnlyLog;
92
- build(context: AgentContext): Context;
97
+ build(context: AgentContext, options: BuildOptions): Context;
93
98
  /**
94
99
  * Sync normalized (provider-level) messages into the append-only log.
95
100
  *
@@ -104,5 +109,5 @@ export declare class AppendOnlyContextManager {
104
109
  appendMessage(message: any): void;
105
110
  replaceTailMessage(message: any): void;
106
111
  invalidate(): void;
107
- reset(context: AgentContext): void;
112
+ reset(context: AgentContext, options: BuildOptions): void;
108
113
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-agent-core",
4
- "version": "15.3.0",
4
+ "version": "15.3.1",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -35,9 +35,9 @@
35
35
  "fmt": "biome format --write ."
36
36
  },
37
37
  "dependencies": {
38
- "@oh-my-pi/pi-ai": "15.3.0",
39
- "@oh-my-pi/pi-natives": "15.3.0",
40
- "@oh-my-pi/pi-utils": "15.3.0",
38
+ "@oh-my-pi/pi-ai": "15.3.1",
39
+ "@oh-my-pi/pi-natives": "15.3.1",
40
+ "@oh-my-pi/pi-utils": "15.3.1",
41
41
  "@opentelemetry/api": "^1.9.0"
42
42
  },
43
43
  "devDependencies": {
package/src/agent-loop.ts CHANGED
@@ -363,7 +363,7 @@ function injectIntentIntoSchema(schema: unknown, mode: "require" | "optional" =
363
363
  };
364
364
  }
365
365
 
366
- function normalizeTools(tools: AgentContext["tools"], injectIntent: boolean): Context["tools"] {
366
+ export function normalizeTools(tools: AgentContext["tools"], injectIntent: boolean): Context["tools"] {
367
367
  injectIntent = injectIntent && Bun.env.PI_NO_INTENT !== "1";
368
368
  return tools?.map(t => {
369
369
  const intentMode = resolveIntentMode(t.intent);
@@ -652,7 +652,7 @@ async function streamAssistantResponse(
652
652
  let llmContext: Context;
653
653
  if (config.appendOnlyContext) {
654
654
  config.appendOnlyContext.syncMessages(normalizedMessages);
655
- llmContext = config.appendOnlyContext.build(context);
655
+ llmContext = config.appendOnlyContext.build(context, { intentTracing: !!config.intentTracing });
656
656
  } else {
657
657
  llmContext = {
658
658
  systemPrompt: context.systemPrompt,
@@ -15,7 +15,8 @@
15
15
  */
16
16
 
17
17
  import type { Context, Message, Tool } from "@oh-my-pi/pi-ai";
18
- import type { AgentContext, AgentTool } from "./types";
18
+ import { normalizeTools } from "./agent-loop";
19
+ import type { AgentContext } from "./types";
19
20
 
20
21
  // ---------------------------------------------------------------------------
21
22
  // StablePrefix (formerly ImmutablePrefix)
@@ -28,6 +29,12 @@ export interface StablePrefixSnapshot {
28
29
  fingerprint: string;
29
30
  }
30
31
 
32
+ /** Options threaded through `build()` so the snapshot reflects loop-time settings. */
33
+ export interface BuildOptions {
34
+ /** Inject the `_i` intent field into tool schemas (must match agent-loop's normalizeTools). */
35
+ intentTracing: boolean;
36
+ }
37
+
31
38
  /**
32
39
  * A frozen prefix (system prompt + tools) that produces stable byte
33
40
  * sequences across `build()` calls.
@@ -54,8 +61,8 @@ export class StablePrefix {
54
61
  * Build or rebuild from live context.
55
62
  * Returns `true` if the prefix actually changed (cache miss imminent).
56
63
  */
57
- build(context: AgentContext): boolean {
58
- const snapshot = takeSnapshot(context);
64
+ build(context: AgentContext, options: BuildOptions): boolean {
65
+ const snapshot = takeSnapshot(context, options);
59
66
  if (this.#snapshot && this.#snapshot.fingerprint === snapshot.fingerprint) {
60
67
  return false;
61
68
  }
@@ -154,8 +161,8 @@ export class AppendOnlyContextManager {
154
161
  /** Rolling digest of synced message content — detects in-place rewrites. */
155
162
  #syncedDigest = 0;
156
163
 
157
- build(context: AgentContext): Context {
158
- this.prefix.build(context);
164
+ build(context: AgentContext, options: BuildOptions): Context {
165
+ this.prefix.build(context, options);
159
166
  const { systemPrompt, tools } = this.prefix.toContext();
160
167
  return { systemPrompt, messages: this.log.toMessages(), tools };
161
168
  }
@@ -219,25 +226,36 @@ export class AppendOnlyContextManager {
219
226
  this.prefix.invalidate();
220
227
  }
221
228
 
222
- reset(context: AgentContext): void {
229
+ reset(context: AgentContext, options: BuildOptions): void {
223
230
  this.prefix.invalidate();
224
231
  this.log.clear();
225
232
  this.#lastSyncCount = 0;
226
233
  this.#syncedDigest = 0;
227
- this.prefix.build(context);
234
+ this.prefix.build(context, options);
228
235
  }
229
236
 
230
- /** Fast rolling digest of message content. */
231
- #computeDigest(messages: any[]): number {
237
+ /**
238
+ * Deterministic digest over every field the provider may serialize — role,
239
+ * content, tool calls (both `toolCalls` and OpenAI-wire `tool_calls`),
240
+ * `tool_call_id`, `name`, `id`. Hashed with the same FNV-style rolling
241
+ * accumulator so in-place rewrites of *any* of these fields are visible.
242
+ */
243
+ #computeDigest(messages: readonly unknown[]): number {
232
244
  let hash = 0;
233
245
  for (let i = 0; i < messages.length; i++) {
234
246
  const msg = messages[i];
235
- if (msg && typeof msg === "object") {
236
- const payload =
237
- String(msg.role) + (typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content ?? ""));
238
- for (let j = 0; j < payload.length; j++) {
239
- hash = ((hash << 5) - hash + payload.charCodeAt(j)) | 0;
240
- }
247
+ if (!msg || typeof msg !== "object") continue;
248
+ const m = msg as Record<string, unknown>;
249
+ const payload = JSON.stringify({
250
+ r: m.role ?? null,
251
+ c: m.content ?? null,
252
+ tc: m.toolCalls ?? m.tool_calls ?? null,
253
+ tcid: m.tool_call_id ?? null,
254
+ n: m.name ?? null,
255
+ id: m.id ?? null,
256
+ });
257
+ for (let j = 0; j < payload.length; j++) {
258
+ hash = ((hash << 5) - hash + payload.charCodeAt(j)) | 0;
241
259
  }
242
260
  }
243
261
  return hash >>> 0;
@@ -248,30 +266,17 @@ export class AppendOnlyContextManager {
248
266
  // Snapshot helpers
249
267
  // ---------------------------------------------------------------------------
250
268
 
251
- /**
252
- * Produce a stable serialization of tools that matches what
253
- * `normalizeTools(tools, false)` outputs (no intent injection).
254
- *
255
- * The spread `{ ...agentTool }` preserves all own enumerable properties
256
- * that survive JSON.stringify — functions are dropped, but strings,
257
- * booleans, objects are included.
258
- */
259
- function normalizeTool(t: AgentTool): Tool {
260
- const description = t.description ?? "";
261
- return { ...t, parameters: t.parameters, description };
262
- }
263
-
264
- function takeSnapshot(context: AgentContext): StablePrefixSnapshot {
269
+ function takeSnapshot(context: AgentContext, options: BuildOptions): StablePrefixSnapshot {
265
270
  const systemPrompt = [...context.systemPrompt];
266
- const tools = (context.tools ?? []).map(normalizeTool);
271
+ const tools = normalizeTools(context.tools, options.intentTracing) ?? [];
267
272
  return {
268
273
  systemPrompt,
269
274
  tools,
270
- fingerprint: computeFingerprint(systemPrompt, tools),
275
+ fingerprint: computeFingerprint(systemPrompt, tools, options),
271
276
  };
272
277
  }
273
278
 
274
- function computeFingerprint(systemPrompt: string[], tools: Tool[]): string {
279
+ function computeFingerprint(systemPrompt: string[], tools: Tool[], options: BuildOptions): string {
275
280
  const payload = JSON.stringify({
276
281
  s: systemPrompt,
277
282
  t: tools.map(t => ({
@@ -282,6 +287,7 @@ function computeFingerprint(systemPrompt: string[], tools: Tool[]): string {
282
287
  cf: t.customFormat,
283
288
  cw: t.customWireName,
284
289
  })),
290
+ i: options.intentTracing,
285
291
  });
286
292
  let hash = 0;
287
293
  for (let i = 0; i < payload.length; i++) {