From 7d28c60c86304c36d2c6ebd02e64265b6decf467 Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 19 Jun 2026 16:30:48 +0200 Subject: [PATCH] refactor: renamed intent field from `_i` to `i` - Renamed the global `INTENT_FIELD` constant from `_i` to `i`. - Updated documentation strings, type annotations, and test expectations across packages to reflect the new field name. - Ensured consistent usage of the constant in tool schema construction and intent serialization. --- packages/agent/src/agent-loop.ts | 2 +- packages/agent/src/append-only-context.ts | 2 +- packages/agent/src/types.ts | 12 ++++++------ packages/agent/test/append-only-context.test.ts | 10 +++++----- packages/ai/src/dialect/examples.ts | 2 +- packages/ai/src/dialect/pi.md | 2 +- packages/ai/src/types.ts | 4 ++-- .../openai-responses-parallel-tool-calls.test.ts | 12 ++++++------ packages/ai/test/stream-markup-healing.test.ts | 4 ++-- packages/ai/test/tool-examples.test.ts | 2 +- packages/coding-agent/src/eval/py/prelude.py | 2 +- .../src/modes/controllers/event-controller.ts | 2 +- .../test/core/python-tool-bridge.test.ts | 2 +- .../test/session/session-dump-format.test.ts | 2 +- packages/collab-web/src/tool-render/ToolView.tsx | 2 +- packages/collab-web/src/tool-render/types.ts | 2 +- packages/snapcompact/src/snapcompact.ts | 4 ++-- packages/snapcompact/test/snapcompact.test.ts | 16 +++++++++++----- packages/wire/src/index.ts | 2 +- 19 files changed, 46 insertions(+), 40 deletions(-) diff --git a/packages/agent/src/agent-loop.ts b/packages/agent/src/agent-loop.ts index 96ee303f4..e01d25fb4 100644 --- a/packages/agent/src/agent-loop.ts +++ b/packages/agent/src/agent-loop.ts @@ -591,7 +591,7 @@ export function normalizeTools( // specs without their descriptions (top-level + nested schema annotations) // so they are not duplicated on the wire. Strip the STABLE wire schema (the // memoized `stripSchemaDescriptions` result is reused across requests), then - // re-inject `_i` (without its hint, which `describeIntent: false` omits) so + // re-inject `i` (without its hint, which `describeIntent: false` omits) so // intent tracing keeps the field while no descriptions ride the wire. if (pruneDescriptions) { let parameters = stripSchemaDescriptions(toolWireSchema(t)) as TSchema; diff --git a/packages/agent/src/append-only-context.ts b/packages/agent/src/append-only-context.ts index b074cb8c8..898489b9c 100644 --- a/packages/agent/src/append-only-context.ts +++ b/packages/agent/src/append-only-context.ts @@ -32,7 +32,7 @@ export interface StablePrefixSnapshot { /** Options threaded through `build()` so the snapshot reflects loop-time settings. */ export interface BuildOptions { - /** Inject the `_i` intent field into tool schemas (must match agent-loop's normalizeTools). */ + /** Inject the `i` intent field into tool schemas (must match agent-loop's normalizeTools). */ intentTracing: boolean; exampleDialect?: Dialect; /** Strip tool descriptions from the provider-bound specs (must match normalizeTools). */ diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index c55d9998b..c9f081b09 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -273,7 +273,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions { * * When set, the loop reads messages from the append-only log (stable * byte prefix) and caches system prompt + tools. Tools exclude per-turn - * `_i` intent fields. + * `i` intent fields. */ appendOnlyContext?: AppendOnlyContextManager; @@ -584,11 +584,11 @@ export interface AgentTool>) => string | undefined); diff --git a/packages/agent/test/append-only-context.test.ts b/packages/agent/test/append-only-context.test.ts index e4825e675..585752af2 100644 --- a/packages/agent/test/append-only-context.test.ts +++ b/packages/agent/test/append-only-context.test.ts @@ -592,7 +592,7 @@ describe("message sync", () => { // --------------------------------------------------------------------------- describe("intent injection through build()", () => { - it("injects required `_i` into tool schemas when intentTracing is true", () => { + it("injects required `i` into tool schemas when intentTracing is true", () => { const mgr = new AppendOnlyContextManager(); const tool = makeTool("read", "Read", { type: "object", @@ -608,20 +608,20 @@ describe("intent injection through build()", () => { expect(params!.required).toContain(INTENT_FIELD); }); - it("materializes ArkType params and keeps `_i` first in authored order", () => { + it("materializes ArkType params and keeps `i` first in authored order", () => { const mgr = new AppendOnlyContextManager(); const tool = makeTool("write", "Write", type({ path: "string", content: "string" })); const ctx = makeContext({ tools: [tool] }); const result = mgr.build(ctx, { intentTracing: true }); const params = result.tools?.[0]?.parameters as { properties?: Record; required?: string[] }; - // `_i` must lead; authored order (path before content) is preserved rather + // `i` must lead; authored order (path before content) is preserved rather // than ArkType's alphabetized-by-hash order (content, path). expect(Object.keys(params.properties ?? {})).toEqual([INTENT_FIELD, "path", "content"]); expect(params.required).toContain(INTENT_FIELD); }); - it("omits `_i` when intentTracing is false", () => { + it("omits `i` when intentTracing is false", () => { const mgr = new AppendOnlyContextManager(); const tool = makeTool("read", "Read", { type: "object", @@ -679,7 +679,7 @@ describe("tool examples injection through build()", () => { expect(desc).toBe("Find files."); }); - it("injects the `_i` placeholder into examples when intentTracing is on", () => { + it("injects the `i` placeholder into examples when intentTracing is on", () => { const mgr = new AppendOnlyContextManager(); const tool = makeTool("find", "Find files.", findParams, findExamples); const ctx = makeContext({ tools: [tool] }); diff --git a/packages/ai/src/dialect/examples.ts b/packages/ai/src/dialect/examples.ts index 350e987e8..d663fe77b 100644 --- a/packages/ai/src/dialect/examples.ts +++ b/packages/ai/src/dialect/examples.ts @@ -9,7 +9,7 @@ export function renderToolExamples(tool: InbandTool, dialect: Dialect, intentFie if (!examples?.length) return ""; const definition = getDialectDefinition(dialect); const renderCall = (args: Record): string => { - // When intent tracing injects `_i` into the schema, examples must show a + // When intent tracing injects `i` into the schema, examples must show a // placeholder so the model learns to emit it. Keep it first, matching the // schema injection order. const finalArgs = intentField ? { [intentField]: INTENT_PLACEHOLDER, ...args } : args; diff --git a/packages/ai/src/dialect/pi.md b/packages/ai/src/dialect/pi.md index 506368713..1d022fc2c 100644 --- a/packages/ai/src/dialect/pi.md +++ b/packages/ai/src/dialect/pi.md @@ -23,7 +23,7 @@ Call with a verbatim body — everything between `«` and `»` is taken literall Argument values: -- Strings are written bare and verbatim (`path=src/a.ts`). Quote with `"…"` only when the value contains spaces or starts with `"`, `[`, or `{` (`_i="run the tests"`). +- Strings are written bare and verbatim (`path=src/a.ts`). Quote with `"…"` only when the value contains spaces or starts with `"`, `[`, or `{` (`i="run the tests"`). - Numbers, booleans, and `null` are JSON literals (`offset=50`, `force=true`). - Arrays and objects are inline JSON (`paths=["src","test"]`). - The body fence holds the call's first long/multi-line string parameter; its key is implied, never written. diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index ab4551bbd..5ed7f1a19 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -656,8 +656,8 @@ export interface Tool { * Illustrative calls/notes; the AI layer renders them into an `` * block in the model's native tool-call syntax and appends to the wire * description. Author `call`/`bad`/`good` as plain argument objects WITHOUT - * `_i` — when intent tracing injects `_i` into the schema, the renderer adds - * a placeholder `_i` automatically. Type each tool's `examples` against its + * `i` — when intent tracing injects `i` into the schema, the renderer adds + * a placeholder `i` automatically. Type each tool's `examples` against its * own schema (e.g. `readonly ToolExample[]`). */ examples?: readonly ToolExample[]; diff --git a/packages/ai/test/openai-responses-parallel-tool-calls.test.ts b/packages/ai/test/openai-responses-parallel-tool-calls.test.ts index 4ab70faaf..541cfcea4 100644 --- a/packages/ai/test/openai-responses-parallel-tool-calls.test.ts +++ b/packages/ai/test/openai-responses-parallel-tool-calls.test.ts @@ -65,8 +65,8 @@ describe("processResponsesStream: parallel function_call items", () => { const emitted: EmittedEvent[] = []; const stream = { push: (e: unknown) => emitted.push(e as EmittedEvent), end: () => {} } as never; - const argsA = JSON.stringify({ _i: "Reading test", path: "test.txt" }); - const argsB = JSON.stringify({ _i: "Reading test", path: "test.md" }); + const argsA = JSON.stringify({ i: "Reading test", path: "test.txt" }); + const argsB = JSON.stringify({ i: "Reading test", path: "test.md" }); await processResponsesStream( makeStream([ @@ -125,8 +125,8 @@ describe("processResponsesStream: parallel function_call items", () => { expect(blockA?.type).toBe("toolCall"); expect(blockB?.type).toBe("toolCall"); if (blockA?.type !== "toolCall" || blockB?.type !== "toolCall") throw new Error("expected toolCalls"); - expect(blockA.arguments).toEqual({ _i: "Reading test", path: "test.txt" }); - expect(blockB.arguments).toEqual({ _i: "Reading test", path: "test.md" }); + expect(blockA.arguments).toEqual({ i: "Reading test", path: "test.txt" }); + expect(blockB.arguments).toEqual({ i: "Reading test", path: "test.md" }); const ends = emitted.filter(e => e.type === "toolcall_end") as Array<{ toolCall: { id: string; arguments: Record }; @@ -134,8 +134,8 @@ describe("processResponsesStream: parallel function_call items", () => { }>; expect(ends).toHaveLength(2); const byCallId = new Map(ends.map(e => [e.toolCall.id.split("|")[0], e])); - expect(byCallId.get("call_a")?.toolCall.arguments).toEqual({ _i: "Reading test", path: "test.txt" }); - expect(byCallId.get("call_b")?.toolCall.arguments).toEqual({ _i: "Reading test", path: "test.md" }); + expect(byCallId.get("call_a")?.toolCall.arguments).toEqual({ i: "Reading test", path: "test.txt" }); + expect(byCallId.get("call_b")?.toolCall.arguments).toEqual({ i: "Reading test", path: "test.md" }); expect(byCallId.get("call_a")?.contentIndex).toBe(0); expect(byCallId.get("call_b")?.contentIndex).toBe(1); diff --git a/packages/ai/test/stream-markup-healing.test.ts b/packages/ai/test/stream-markup-healing.test.ts index f5d386b9e..7cf36e40c 100644 --- a/packages/ai/test/stream-markup-healing.test.ts +++ b/packages/ai/test/stream-markup-healing.test.ts @@ -72,7 +72,7 @@ function chunk(model: string, delta: SseChoiceDelta, finish: SseChunk["choices"] const REPORTED_DSML_LEAK = "<|DSML|tool_calls>\n" + ' <|DSML|invoke name="bash">\n' + - ' <|DSML|parameter name="_i" string="true">Check Fedora 42 available packages\n' + + ' <|DSML|parameter name="i" string="true">Check Fedora 42 available packages\n' + ' <|DSML|parameter name="command" string="true">docker run --rm --platform linux/arm64 fedora:42 bash -c \'type python3; type git; type sed; type cp; ls /usr/bin/python3 2>/dev/null; rpm -qa | grep -E "^python3|^git-|^sed-|^bash-" | sort\'\n' + ' <|DSML|parameter name="timeout" string="false">15\n' + " \n" + @@ -616,7 +616,7 @@ describe("Ollama provider DSML envelope healing", () => { expect(toolCalls).toHaveLength(1); expect(toolCalls[0].name).toBe("bash"); expect(toolCalls[0].arguments).toMatchObject({ - _i: "Check Fedora 42 available packages", + [INTENT_FIELD]: "Check Fedora 42 available packages", timeout: 15, }); expect(String(toolCalls[0].arguments.command)).toContain("docker run"); diff --git a/packages/ai/test/tool-examples.test.ts b/packages/ai/test/tool-examples.test.ts index a781b7dae..026a6719b 100644 --- a/packages/ai/test/tool-examples.test.ts +++ b/packages/ai/test/tool-examples.test.ts @@ -186,6 +186,6 @@ describe("renderToolExamples", () => { examples: [{ caption: "Find files", call: { paths: ["src/**/*.ts"] } }], }; - expect(renderToolExamples(tool, "anthropic")).not.toContain(INTENT_FIELD); + expect(renderToolExamples(tool, "anthropic")).not.toContain(` { expect(res.status).toBe(200); expect(body).toEqual({ ok: true, value: "file body" }); expect(calls).toHaveLength(1); - // `_i` survives the bridge round trip so transcript renderers have a label. + // `i` survives the bridge round trip so transcript renderers have a label. expect((calls[0]!.args as Record)[INTENT_FIELD]).toBe("py prelude"); } finally { unregister(); diff --git a/packages/coding-agent/test/session/session-dump-format.test.ts b/packages/coding-agent/test/session/session-dump-format.test.ts index eb11c8ea8..48a24d4c3 100644 --- a/packages/coding-agent/test/session/session-dump-format.test.ts +++ b/packages/coding-agent/test/session/session-dump-format.test.ts @@ -170,7 +170,7 @@ describe("formatSessionDumpText markdown-headings transcript", () => { expect(out).toContain("### Tool Result: read"); expect(out).toContain("### Tool Call: read"); expect(out).toContain("path: src/foo.ts"); - // The `_i` intent renders as a `//` comment under the heading, never inside the YAML args. + // The `i` intent renders as a `//` comment under the heading, never inside the YAML args. expect(out).toContain("// Reading the file"); expect(out).not.toContain(`${INTENT_FIELD}:`); // Tool calls render as a readable heading + YAML, never the / XML. diff --git a/packages/collab-web/src/tool-render/ToolView.tsx b/packages/collab-web/src/tool-render/ToolView.tsx index 02268058a..843ffaf81 100644 --- a/packages/collab-web/src/tool-render/ToolView.tsx +++ b/packages/collab-web/src/tool-render/ToolView.tsx @@ -16,7 +16,7 @@ export interface ToolViewProps { result?: ToolResultLike; /** Tool is still executing (live collab view). */ running?: boolean; - /** Model-provided intent (`_i`), shown atop the body. */ + /** Model-provided intent (`i`), shown atop the body. */ intent?: string; /** Streaming partial output tail while running. */ partial?: string; diff --git a/packages/collab-web/src/tool-render/types.ts b/packages/collab-web/src/tool-render/types.ts index 5e116b49e..790a1ecce 100644 --- a/packages/collab-web/src/tool-render/types.ts +++ b/packages/collab-web/src/tool-render/types.ts @@ -49,7 +49,7 @@ export interface ToolRenderHost { export interface ToolRenderProps { /** Wire tool name (may be an alias of the registry key, e.g. `grep` → search). */ name: string; - /** Parsed tool-call arguments with the internal `_i` intent already stripped. */ + /** Parsed tool-call arguments with the internal `i` intent already stripped. */ args: Record; result?: ToolResultLike; /** Tool is still executing (live collab view). */ diff --git a/packages/snapcompact/src/snapcompact.ts b/packages/snapcompact/src/snapcompact.ts index 228ecc3ca..b606cb10f 100644 --- a/packages/snapcompact/src/snapcompact.ts +++ b/packages/snapcompact/src/snapcompact.ts @@ -772,8 +772,8 @@ export function serializeConversation(messages: Message[], options?: SerializeOp if (uselessCallIds.has(block.id)) continue; flushAssistant(); const args = block.arguments as Record; - // Prefer the harness-derived intent, else the raw `_i` arg; render it as - // a one-line `//comment` and drop `_i` from the args below. + // Prefer the harness-derived intent, else the raw intent arg; render it as + // a one-line `//comment` and drop it from the args below. const rawIntent = typeof block.intent === "string" ? block.intent diff --git a/packages/snapcompact/test/snapcompact.test.ts b/packages/snapcompact/test/snapcompact.test.ts index 12686aa10..eb62dde6f 100644 --- a/packages/snapcompact/test/snapcompact.test.ts +++ b/packages/snapcompact/test/snapcompact.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "bun:test"; import type { AssistantMessage, Message, Usage } from "@oh-my-pi/pi-ai"; +import { INTENT_FIELD } from "@oh-my-pi/pi-wire"; import * as snapcompact from "../src"; // Small frames keep render time negligible. Legacy 5x8 shape: 320px → 64 cols @@ -567,11 +568,16 @@ describe("serializeConversation", () => { expect(out).toBe("# User ¶\ndo the thing\n\n# Assistant ¶\ndone"); }); - it("merges a tool call with its paired result into one block, _i as a // comment", () => { + it("merges a tool call with its paired result into one block, intent as a // comment", () => { const out = snapcompact.serializeConversation( [ createAssistantMessage([ - { type: "toolCall", id: "c1", name: "bash", arguments: { _i: "Running tests", command: "bun test" } }, + { + type: "toolCall", + id: "c1", + name: "bash", + arguments: { [INTENT_FIELD]: "Running tests", command: "bun test" }, + }, ]), { ...createToolResultMessage("3 pass"), toolCallId: "c1" } as Message, ], @@ -580,21 +586,21 @@ describe("serializeConversation", () => { expect(out).toBe('# Tool call ¶\n//Running tests\nbash(command="bun test")\n\n3 pass\n'); }); - it("prefers the harness-derived intent over the raw _i arg and squashes newlines", () => { + it("prefers the harness-derived intent over the raw intent arg and squashes newlines", () => { const out = snapcompact.serializeConversation([ createAssistantMessage([ { type: "toolCall", id: "c1", name: "bash", - arguments: { _i: "raw arg", command: "ls" }, + arguments: { [INTENT_FIELD]: "raw arg", command: "ls" }, intent: "Derived\nintent line", }, ]), ]); expect(out).toContain("//Derived intent line"); expect(out).not.toContain("raw arg"); - expect(out).not.toContain("_i="); + expect(out).not.toContain(`${INTENT_FIELD}=`); }); it("folds thinking into the assistant block as italics above the text", () => { diff --git a/packages/wire/src/index.ts b/packages/wire/src/index.ts index e6ce733a9..9d28b25d5 100644 --- a/packages/wire/src/index.ts +++ b/packages/wire/src/index.ts @@ -346,7 +346,7 @@ export type WireFrame = GuestFrame | HostFrame; export const COLLAB_PROTO = 1; /** Parameter key used for intent tracing (e.g. prompt explanation/reasoning) */ -export const INTENT_FIELD = "_i"; +export const INTENT_FIELD = "i"; // ═══════════════════════════════════════════════════════════════════════════ // Envelope & link constants