@oh-my-pi/pi-agent-core 12.12.3 → 12.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-agent-core",
3
- "version": "12.12.3",
3
+ "version": "12.14.0",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -24,9 +24,9 @@
24
24
  "test": "bun test"
25
25
  },
26
26
  "dependencies": {
27
- "@oh-my-pi/pi-ai": "12.12.3",
28
- "@oh-my-pi/pi-tui": "12.12.3",
29
- "@oh-my-pi/pi-utils": "12.12.3"
27
+ "@oh-my-pi/pi-ai": "12.14.0",
28
+ "@oh-my-pi/pi-tui": "12.14.0",
29
+ "@oh-my-pi/pi-utils": "12.14.0"
30
30
  },
31
31
  "keywords": [
32
32
  "ai",
package/src/agent-loop.ts CHANGED
@@ -123,6 +123,58 @@ function normalizeMessagesForProvider(
123
123
  return changed ? normalized : messages;
124
124
  }
125
125
 
126
+ export const INTENT_FIELD = "agent__intent";
127
+
128
+ function injectIntentIntoSchema(schema: unknown): unknown {
129
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return schema;
130
+ const schemaRecord = schema as Record<string, unknown>;
131
+ const propertiesValue = schemaRecord.properties;
132
+ const properties =
133
+ propertiesValue && typeof propertiesValue === "object" && !Array.isArray(propertiesValue)
134
+ ? (propertiesValue as Record<string, unknown>)
135
+ : {};
136
+ const requiredValue = schemaRecord.required;
137
+ const required = Array.isArray(requiredValue)
138
+ ? requiredValue.filter((item): item is string => typeof item === "string")
139
+ : [];
140
+ if (INTENT_FIELD in properties) {
141
+ if (required.includes(INTENT_FIELD)) return schema;
142
+ return {
143
+ ...schemaRecord,
144
+ required: [...required, INTENT_FIELD],
145
+ };
146
+ }
147
+ return {
148
+ ...schemaRecord,
149
+ properties: {
150
+ ...properties,
151
+ [INTENT_FIELD]: {
152
+ type: "string",
153
+ description:
154
+ "Describe intent as one sentence in present participle form (e.g., Inserting comment before the function) with no trailing period",
155
+ },
156
+ },
157
+ required: [...required, INTENT_FIELD],
158
+ };
159
+ }
160
+
161
+ function injectIntentIntoTools(tools: Context["tools"]): Context["tools"] {
162
+ return tools?.map(tool => ({
163
+ ...tool,
164
+ parameters: injectIntentIntoSchema(tool.parameters) as typeof tool.parameters,
165
+ }));
166
+ }
167
+
168
+ function extractIntent(args: Record<string, unknown>): { intent?: string; strippedArgs: Record<string, unknown> } {
169
+ const intent = args[INTENT_FIELD];
170
+ if (typeof intent !== "string") {
171
+ return { strippedArgs: args };
172
+ }
173
+ const { [INTENT_FIELD]: _ignored, ...strippedArgs } = args;
174
+ const trimmed = intent.trim();
175
+ return { intent: trimmed.length > 0 ? trimmed : undefined, strippedArgs };
176
+ }
177
+
126
178
  /**
127
179
  * Main loop logic shared by agentLoop and agentLoopContinue.
128
180
  */
@@ -199,6 +251,7 @@ async function runLoop(
199
251
  config.getToolContext,
200
252
  config.interruptMode,
201
253
  config.transformToolCallArguments,
254
+ config.intentTracing,
202
255
  );
203
256
  toolResults.push(...toolExecution.toolResults);
204
257
  steeringAfterTools = toolExecution.steeringMessages ?? null;
@@ -261,7 +314,7 @@ async function streamAssistantResponse(
261
314
  const llmContext: Context = {
262
315
  systemPrompt: context.systemPrompt,
263
316
  messages: normalizedMessages,
264
- tools: context.tools,
317
+ tools: config.intentTracing ? injectIntentIntoTools(context.tools) : context.tools,
265
318
  };
266
319
 
267
320
  const streamFunction = streamFn || streamSimple;
@@ -373,6 +426,7 @@ async function executeToolCalls(
373
426
  getToolContext?: AgentLoopConfig["getToolContext"],
374
427
  interruptMode: AgentLoopConfig["interruptMode"] = "immediate",
375
428
  transformToolCallArguments?: AgentLoopConfig["transformToolCallArguments"],
429
+ intentTracing?: AgentLoopConfig["intentTracing"],
376
430
  ): Promise<{ toolResults: ToolResultMessage[]; steeringMessages?: AgentMessage[] }> {
377
431
  type ToolCallContent = Extract<AssistantMessage["content"][number], { type: "toolCall" }>;
378
432
  const toolCalls = assistantMessage.content.filter((c): c is ToolCallContent => c.type === "toolCall");
@@ -412,6 +466,7 @@ async function executeToolCalls(
412
466
  const records = toolCalls.map(toolCall => ({
413
467
  toolCall,
414
468
  tool: tools?.find(t => t.name === toolCall.name),
469
+ args: toolCall.arguments as Record<string, unknown>,
415
470
  started: false,
416
471
  result: undefined as AgentToolResult<any> | undefined,
417
472
  isError: false,
@@ -425,12 +480,22 @@ async function executeToolCalls(
425
480
  }
426
481
 
427
482
  const { toolCall, tool } = record;
483
+ let argsForExecution = toolCall.arguments as Record<string, unknown>;
484
+ if (intentTracing) {
485
+ const { intent, strippedArgs } = extractIntent(toolCall.arguments);
486
+ argsForExecution = strippedArgs;
487
+ if (intent) {
488
+ toolCall.intent = intent;
489
+ }
490
+ }
491
+ record.args = argsForExecution;
428
492
  record.started = true;
429
493
  stream.push({
430
494
  type: "tool_execution_start",
431
495
  toolCallId: toolCall.id,
432
496
  toolName: toolCall.name,
433
- args: toolCall.arguments,
497
+ args: argsForExecution,
498
+ intent: toolCall.intent,
434
499
  });
435
500
 
436
501
  let result: AgentToolResult<any>;
@@ -439,7 +504,7 @@ async function executeToolCalls(
439
504
  try {
440
505
  if (!tool) throw new Error(`Tool ${toolCall.name} not found`);
441
506
 
442
- const validatedArgs = validateToolArguments(tool, toolCall);
507
+ const validatedArgs = validateToolArguments(tool, { ...toolCall, arguments: argsForExecution });
443
508
  const toolContext = getToolContext
444
509
  ? getToolContext({
445
510
  batchId,
@@ -458,7 +523,7 @@ async function executeToolCalls(
458
523
  type: "tool_execution_update",
459
524
  toolCallId: toolCall.id,
460
525
  toolName: toolCall.name,
461
- args: toolCall.arguments,
526
+ args: argsForExecution,
462
527
  partialResult,
463
528
  });
464
529
  },
@@ -512,7 +577,8 @@ async function executeToolCalls(
512
577
  type: "tool_execution_start",
513
578
  toolCallId: toolCall.id,
514
579
  toolName: toolCall.name,
515
- args: toolCall.arguments,
580
+ args: record.args,
581
+ intent: toolCall.intent,
516
582
  });
517
583
  }
518
584
  stream.push({
@@ -568,6 +634,7 @@ function createAbortedToolResult(
568
634
  toolCallId: toolCall.id,
569
635
  toolName: toolCall.name,
570
636
  args: toolCall.arguments,
637
+ intent: toolCall.intent,
571
638
  });
572
639
  stream.push({
573
640
  type: "tool_execution_end",
package/src/agent.ts CHANGED
@@ -137,6 +137,9 @@ export interface AgentOptions {
137
137
  */
138
138
  transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
139
139
 
140
+ /** Enable intent tracing schema injection/stripping in the harness. */
141
+ intentTracing?: boolean;
142
+
140
143
  /**
141
144
  * Cursor exec handlers for local tool execution.
142
145
  */
@@ -193,6 +196,7 @@ export class Agent {
193
196
  #kimiApiFormat?: "openai" | "anthropic";
194
197
  #preferWebsockets?: boolean;
195
198
  #transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
199
+ #intentTracing: boolean;
196
200
 
197
201
  /** Buffered Cursor tool results with text length at time of call (for correct ordering) */
198
202
  #cursorToolResultBuffer: CursorToolResultEntry[] = [];
@@ -220,6 +224,7 @@ export class Agent {
220
224
  this.#kimiApiFormat = opts.kimiApiFormat;
221
225
  this.#preferWebsockets = opts.preferWebsockets;
222
226
  this.#transformToolCallArguments = opts.transformToolCallArguments;
227
+ this.#intentTracing = opts.intentTracing === true;
223
228
  }
224
229
 
225
230
  /**
@@ -641,6 +646,7 @@ export class Agent {
641
646
  cursorExecHandlers: this.#cursorExecHandlers,
642
647
  cursorOnToolResult,
643
648
  transformToolCallArguments: this.#transformToolCallArguments,
649
+ intentTracing: this.#intentTracing,
644
650
  getSteeringMessages: async () => {
645
651
  if (skipInitialSteeringPoll) {
646
652
  skipInitialSteeringPoll = false;
package/src/types.ts CHANGED
@@ -117,6 +117,12 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
117
117
  * Use for deobfuscating secrets or rewriting arguments.
118
118
  */
119
119
  transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
120
+ /**
121
+ * Enable intent tracing for tool calls.
122
+ * When enabled, the harness injects a `_intent: string` field into tool schemas sent to the model,
123
+ * then strips `_intent` from arguments before executing tools.
124
+ */
125
+ intentTracing?: boolean;
120
126
  }
121
127
 
122
128
  export interface ToolCallContext {
@@ -265,6 +271,6 @@ export type AgentEvent =
265
271
  | { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
266
272
  | { type: "message_end"; message: AgentMessage }
267
273
  // Tool execution lifecycle
268
- | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
274
+ | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any; intent?: string }
269
275
  | { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
270
276
  | { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError?: boolean };