From 83c426d75784273c7523c53257fae20078450639 Mon Sep 17 00:00:00 2001 From: zidou-kiyn <3038771237@qq.com> Date: Wed, 30 Sep 2026 23:52:16 +0800 Subject: [PATCH] feat: declare the Codex grammar through pi's constrainedSampling API The freeform property on the tool definition is not read by pi, so apply_patch always went out as a plain JSON function tool, even on providers that support OpenAI grammar tools. Pi's public ToolDefinition.constrainedSampling hook takes grammar variants. Declaring the Codex Lark grammar there sends apply_patch as a native OpenAI custom tool whenever the model's provider sets compat.supportsOpenAIGrammarTools (pi's bundled OpenAI and OpenAI Codex catalogs do), and falls back to the function tool everywhere else. The freeform property is kept for existing consumers. --- src/index.ts | 4 ++++ test/index.test.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/index.ts b/src/index.ts index c47d4f5..ef80466 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1391,6 +1391,10 @@ export function createApplyPatchTool(): ApplyPatchToolDefinition { label: "ApplyPatch", description: APPLY_PATCH_FREEFORM_DESCRIPTION, parameters: APPLY_PATCH_PARAMS, + // Pi's public grammar hook: models whose provider sets `compat.supportsOpenAIGrammarTools` + // receive apply_patch as a native OpenAI custom tool constrained by the Codex Lark grammar. + // Other providers fall back to the plain function tool. + constrainedSampling: { type: "grammar", variants: { openai_lark: APPLY_PATCH_LARK_GRAMMAR } }, prepareArguments: normalizeApplyPatchArguments, promptSnippet: "Apply Codex-format file patches with apply_patch", promptGuidelines: [ diff --git a/test/index.test.ts b/test/index.test.ts index c8e22be..4759881 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -112,11 +112,13 @@ describe("pi-apply-patch", () => { let capturedToolName: string | undefined; let capturedDescription: string | undefined; let capturedFreeform: FreeformToolFormat | undefined; + let capturedConstrainedSampling: ReturnType["constrainedSampling"]; const extensionApi = { registerTool(tool: ReturnType) { capturedToolName = tool.name; capturedDescription = tool.description; capturedFreeform = tool.freeform; + capturedConstrainedSampling = tool.constrainedSampling; }, on() { return () => {}; @@ -138,6 +140,10 @@ describe("pi-apply-patch", () => { syntax: "lark", definition: APPLY_PATCH_LARK_GRAMMAR, }); + expect(capturedConstrainedSampling).toEqual({ + type: "grammar", + variants: { openai_lark: APPLY_PATCH_LARK_GRAMMAR }, + }); }); it("#given GPT model after reload with apply_patch already active #when session starts #then keeps apply_patch active", async () => {