diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index 8a3fb84cf..f53d9009e 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -178,19 +178,15 @@ export interface AgentState { error?: string; } -export interface AgentToolResult { +export interface AgentToolResult { // Content blocks supporting text and images content: (TextContent | ImageContent)[]; // Details to be displayed in a UI or logged details?: T; - /** Normative input for the tool result */ - $normative?: Static; } // Callback for streaming tool execution updates -export type AgentToolUpdateCallback = ( - partialResult: AgentToolResult, -) => void; +export type AgentToolUpdateCallback = (partialResult: AgentToolResult) => void; /** Options passed to renderResult */ export interface RenderResultOptions { diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 9c365e074..008a3a275 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog ## [Unreleased] + +### Breaking Changes + +- Removed `TInput` generic parameter from `ToolResultMessage` interface and removed `$normative` property + ### Added - `hasUnrepresentableStrictObjectMap()` pre-flight check in `tryEnforceStrictSchema`: schemas with `patternProperties` or schema-valued `additionalProperties` now degrade gracefully to non-strict mode instead of throwing during enforcement diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index d2c22db06..05ff67212 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -280,7 +280,7 @@ export interface AssistantMessage { ttft?: number; // Time to first token in milliseconds } -export interface ToolResultMessage { +export interface ToolResultMessage { role: "toolResult"; toolCallId: string; toolName: string; @@ -290,7 +290,6 @@ export interface ToolResultMessage { /** Timestamp when output was pruned (ms since epoch). Undefined if unpruned. */ prunedAt?: number; timestamp: number; // Unix timestamp in milliseconds - $normative?: TInput; } export type Message = UserMessage | DeveloperMessage | AssistantMessage | ToolResultMessage; diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 1c3f120b4..87a494de7 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog ## [Unreleased] +### Breaking Changes + +- `ast_find` parameter `pattern` (string) replaced by `patterns` (string[]) +- `ast_replace` parameters `pattern` + `rewrite` replaced by `ops: Array<{ pat: string; out: string }>` ### Added @@ -18,17 +22,17 @@ - Web search schema exposes `max_tokens`, `temperature`, and `num_search_results` as tool parameters - Web search provider fallback: when an explicit provider is unavailable, resolves the auto chain instead of returning empty results +### Removed + +- Removed `normativeRewrite` setting that rewrote tool call arguments to normalized format in session history +- Removed `buildNormativeUpdateInput()` helper and normative patch transformation logic + ### Fixed - `ast_replace` no longer rejects empty `out` values; an empty string now deletes matched nodes - `ast_replace` no longer trims `pat` and `out` values, preserving intentional whitespace - `gemini_image` tool: corrected `responseModalities` values from `'Image'`/`'Text'` to uppercase `'IMAGE'`/`'TEXT'` matching the API enum -### Breaking Changes - -- `ast_find` parameter `pattern` (string) replaced by `patterns` (string[]) -- `ast_replace` parameters `pattern` + `rewrite` replaced by `ops: Array<{ pat: string; out: string }>` - ## [13.3.14] - 2026-02-28 ### Added @@ -5389,4 +5393,4 @@ Initial public release. - Git branch display in footer - Message queueing during streaming responses - OAuth integration for Gmail and Google Calendar access -- HTML export with syntax highlighting and collapsible sections +- HTML export with syntax highlighting and collapsible sections \ No newline at end of file diff --git a/packages/coding-agent/src/config/settings-schema.ts b/packages/coding-agent/src/config/settings-schema.ts index 9e38f4b0d..9266d7b73 100644 --- a/packages/coding-agent/src/config/settings-schema.ts +++ b/packages/coding-agent/src/config/settings-schema.ts @@ -254,15 +254,6 @@ export const SETTINGS_SCHEMA = { submenu: true, }, }, - normativeRewrite: { - type: "boolean", - default: false, - ui: { - tab: "agent", - label: "Normative rewrite", - description: "Rewrite tool call arguments to normalized format in session history", - }, - }, repeatToolDescriptions: { type: "boolean", default: false, diff --git a/packages/coding-agent/src/patch/normative.ts b/packages/coding-agent/src/patch/normative.ts deleted file mode 100644 index 7e5db0143..000000000 --- a/packages/coding-agent/src/patch/normative.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Normalize applied patch output into a canonical edit tool payload. - */ -import { generateUnifiedDiffString } from "./diff"; -import { normalizeToLF, stripBom } from "./normalize"; -import { parseHunks } from "./parser"; -import type { PatchInput } from "./types"; - -export interface NormativePatchOptions { - path: string; - rename?: string; - oldContent: string; - newContent: string; - contextLines?: number; - anchor?: string | string[]; -} - -/** Normative patch input is the MongoDB-style update variant */ - -function applyAnchors(diff: string, anchors: Array | undefined): string { - if (!anchors || anchors.length === 0) { - return diff; - } - const lines = diff.split("\n"); - let anchorIndex = 0; - for (let i = 0; i < lines.length; i++) { - if (!lines[i].startsWith("@@")) continue; - const anchor = anchors[anchorIndex]; - if (anchor !== undefined) { - lines[i] = anchor.trim().length === 0 ? "@@" : `@@ ${anchor}`; - } - anchorIndex++; - } - return lines.join("\n"); -} - -function deriveAnchors(diff: string): Array { - const hunks = parseHunks(diff); - return hunks.map(hunk => { - if (hunk.oldLines.length === 0 || hunk.newLines.length === 0) { - return undefined; - } - const newLines = new Set(hunk.newLines); - for (const line of hunk.oldLines) { - const trimmed = line.trim(); - if (trimmed.length === 0) continue; - if (!/[A-Za-z0-9_]/.test(trimmed)) continue; - if (newLines.has(line)) { - return trimmed; - } - } - return undefined; - }); -} - -export function buildNormativeUpdateInput(options: NormativePatchOptions): PatchInput { - const normalizedOld = normalizeToLF(stripBom(options.oldContent).text); - const normalizedNew = normalizeToLF(stripBom(options.newContent).text); - const diffResult = generateUnifiedDiffString(normalizedOld, normalizedNew, options.contextLines ?? 3); - let anchors: Array | undefined = - typeof options.anchor === "string" ? [options.anchor] : options.anchor; - if (!anchors) { - anchors = deriveAnchors(diffResult.diff); - } - const diff = applyAnchors(diffResult.diff, anchors); - return { - path: options.path, - op: "update", - rename: options.rename, - diff, - }; -} diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index a6790e5cd..ff454405c 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -651,17 +651,12 @@ export class AgentSession { } if (event.message.role === "toolResult") { - const { toolName, $normative, toolCallId, details, isError, content } = event.message as { + const { toolName, details, isError, content } = event.message as { toolName?: string; - toolCallId?: string; details?: { path?: string; phases?: TodoPhase[] }; - $normative?: Record; isError?: boolean; content?: Array; }; - if ($normative && toolCallId && this.settings.get("normativeRewrite")) { - await this.#rewriteToolCallArgs(toolCallId, $normative); - } // Invalidate streaming edit cache when edit tool completes to prevent stale data if (toolName === "edit" && details?.path) { this.#invalidateFileCacheForPath(details.path); @@ -1259,33 +1254,6 @@ export class AgentSession { } } - /** Rewrite tool call arguments in agent state and persisted session history. */ - async #rewriteToolCallArgs(toolCallId: string, args: Record): Promise { - let updated = false; - const messages = this.agent.state.messages; - for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i]; - if (msg.role !== "assistant") continue; - const assistantMsg = msg as AssistantMessage; - if (!Array.isArray(assistantMsg.content)) continue; - for (const block of assistantMsg.content) { - if (typeof block !== "object" || block === null) continue; - if (!("type" in block) || (block as { type?: string }).type !== "toolCall") continue; - const toolCall = block as { id?: string; arguments?: Record }; - if (toolCall.id === toolCallId) { - toolCall.arguments = args; - updated = true; - break; - } - } - if (updated) break; - } - - if (updated) { - await this.sessionManager.rewriteAssistantToolCallArgs(toolCallId, args); - } - } - /** Emit extension events based on session events */ async #emitExtensionEvent(event: AgentSessionEvent): Promise { if (!this.#extensionRunner) return; diff --git a/packages/coding-agent/src/session/session-manager.ts b/packages/coding-agent/src/session/session-manager.ts index ece2fb40f..8756462f3 100644 --- a/packages/coding-agent/src/session/session-manager.ts +++ b/packages/coding-agent/src/session/session-manager.ts @@ -1819,36 +1819,6 @@ export class SessionManager { await this.#rewriteFile(); } - /** - * Rewrite tool call arguments in the most recent assistant message containing the toolCallId. - * Returns true if a tool call was updated. - */ - async rewriteAssistantToolCallArgs(toolCallId: string, args: Record): Promise { - let updated = false; - for (let i = this.#fileEntries.length - 1; i >= 0; i--) { - const entry = this.#fileEntries[i]; - if (entry.type !== "message" || entry.message.role !== "assistant") continue; - const message = entry.message as { content?: unknown }; - if (!Array.isArray(message.content)) continue; - for (const block of message.content) { - if (typeof block !== "object" || block === null) continue; - if (!("type" in block) || (block as { type?: string }).type !== "toolCall") continue; - const toolCall = block as { id?: string; arguments?: Record }; - if (toolCall.id === toolCallId) { - toolCall.arguments = args; - updated = true; - break; - } - } - if (updated) break; - } - - if (updated && this.persist && this.#sessionFile) { - await this.#rewriteFile(); - } - return updated; - } - /** * Append a custom message entry (for extensions) that participates in LLM context. * @param customType Hook identifier for filtering on reload diff --git a/packages/coding-agent/test/session-manager/rewrite-tool-call.test.ts b/packages/coding-agent/test/session-manager/rewrite-tool-call.test.ts deleted file mode 100644 index 0ecd5067d..000000000 --- a/packages/coding-agent/test/session-manager/rewrite-tool-call.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import type { AssistantMessage, ToolCall } from "@oh-my-pi/pi-ai"; -import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; - -function buildAssistantMessage(content: AssistantMessage["content"]): AssistantMessage { - return { - role: "assistant", - content, - api: "anthropic-messages", - provider: "anthropic", - model: "test", - usage: { - input: 1, - output: 1, - cacheRead: 0, - cacheWrite: 0, - totalTokens: 2, - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, - }, - stopReason: "stop", - timestamp: 1, - }; -} - -describe("SessionManager tool call rewrite", () => { - it("rewrites assistant toolCall arguments in context", async () => { - const session = SessionManager.inMemory(); - const toolCallId = "toolu_rewrite_1"; - - const toolCall = { - type: "toolCall", - id: toolCallId, - name: "edit", - arguments: { path: "file.ts", op: "update", diff: "@@\n-old\n+new" }, - } satisfies ToolCall; - - session.appendMessage(buildAssistantMessage([toolCall])); - - const updated = await session.rewriteAssistantToolCallArgs(toolCallId, { - path: "file.ts", - op: "update", - diff: "@@\n-old\n+newer", - }); - - expect(updated).toBe(true); - - const ctx = session.buildSessionContext(); - const assistant = ctx.messages.find(m => m.role === "assistant") as AssistantMessage; - const updatedCall = assistant.content.find(b => b.type === "toolCall") as ToolCall; - expect(updatedCall.arguments).toEqual({ path: "file.ts", op: "update", diff: "@@\n-old\n+newer" }); - }); -});