From db5274b48c89329a27ba74cb89fe0fbbea353efb Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 12:32:51 +0200 Subject: [PATCH 01/38] fix(markdown): require double-tilde strikethrough delimiters --- .../src/core/export-html/template.js | 14 ++++++++++ packages/tui/src/components/markdown.ts | 28 +++++++++++++++++-- packages/tui/test/markdown.test.ts | 25 +++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/src/core/export-html/template.js b/packages/coding-agent/src/core/export-html/template.js index 6b53516b..57bdb811 100644 --- a/packages/coding-agent/src/core/export-html/template.js +++ b/packages/coding-agent/src/core/export-html/template.js @@ -1448,9 +1448,23 @@ } // Configure marked with syntax highlighting and HTML escaping for text + const strictStrikethroughRegex = /^(~~)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/; + marked.use({ breaks: true, gfm: true, + tokenizer: { + del(src) { + const match = strictStrikethroughRegex.exec(src); + if (!match) return undefined; + return { + type: 'del', + raw: match[0], + text: match[2], + tokens: this.lexer.inlineTokens(match[2]) + }; + } + }, renderer: { // Code blocks: syntax highlight, no HTML escaping code(token) { diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index 3cdf6373..4399ee53 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -1,8 +1,32 @@ -import { marked, type Token } from "marked"; +import { Marked, type Token, Tokenizer, type Tokens } from "marked"; import { isImageLine } from "../terminal-image.js"; import type { Component } from "../tui.js"; import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils.js"; +const STRICT_STRIKETHROUGH_REGEX = /^(~~)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/; + +class StrictStrikethroughTokenizer extends Tokenizer { + override del(src: string): Tokens.Del | undefined { + const match = STRICT_STRIKETHROUGH_REGEX.exec(src); + if (!match) { + return undefined; + } + + const text = match[2]; + return { + type: "del", + raw: match[0], + text, + tokens: this.lexer.inlineTokens(text), + }; + } +} + +const markdownParser = new Marked(); +markdownParser.setOptions({ + tokenizer: new StrictStrikethroughTokenizer(), +}); + /** * Default text styling for markdown content. * Applied to all text unless overridden by markdown formatting. @@ -112,7 +136,7 @@ export class Markdown implements Component { const normalizedText = this.text.replace(/\t/g, " "); // Parse markdown to HTML-like tokens - const tokens = marked.lexer(normalizedText); + const tokens = markdownParser.lexer(normalizedText); // Convert tokens to styled terminal output const renderedLines: string[] = []; diff --git a/packages/tui/test/markdown.test.ts b/packages/tui/test/markdown.test.ts index e8f0d023..fd265187 100644 --- a/packages/tui/test/markdown.test.ts +++ b/packages/tui/test/markdown.test.ts @@ -1061,6 +1061,31 @@ bar`, }); }); + describe("Strikethrough syntax", () => { + it("should render ~~text~~ as strikethrough", () => { + const markdown = new Markdown("Use ~~strikethrough~~ here", 0, 0, defaultMarkdownTheme); + + const lines = markdown.render(80); + const joinedOutput = lines.join("\n"); + const joinedPlain = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")).join(" "); + + assert.ok(joinedOutput.includes("\x1b[9m"), "Should apply strikethrough styling"); + assert.ok(joinedPlain.includes("strikethrough"), "Should include struck text content"); + assert.ok(!joinedPlain.includes("~~strikethrough~~"), "Should not render delimiters as text"); + }); + + it("should keep ~text~ as plain text", () => { + const markdown = new Markdown("Use ~strikethrough~ literally", 0, 0, defaultMarkdownTheme); + + const lines = markdown.render(80); + const joinedOutput = lines.join("\n"); + const joinedPlain = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")).join(" "); + + assert.ok(joinedPlain.includes("~strikethrough~"), "Single-tilde delimiters should remain visible"); + assert.ok(!joinedOutput.includes("\x1b[9m"), "Single-tilde text should not use strikethrough styling"); + }); + }); + describe("Links", () => { it("should not duplicate URL for autolinked emails", () => { const markdown = new Markdown("Contact user@example.com for help", 0, 0, defaultMarkdownTheme); From 1d6de01c96d45f91412b4b93e6b0803536699587 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 12:39:05 +0200 Subject: [PATCH 02/38] feat(coding-agent): export loadProjectContextFiles() standalone utility closes #3142 --- packages/coding-agent/src/core/resource-loader.ts | 2 +- packages/coding-agent/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/src/core/resource-loader.ts b/packages/coding-agent/src/core/resource-loader.ts index 9f21102d..c1c49b23 100644 --- a/packages/coding-agent/src/core/resource-loader.ts +++ b/packages/coding-agent/src/core/resource-loader.ts @@ -73,7 +73,7 @@ function loadContextFileFromDir(dir: string): { path: string; content: string } return null; } -function loadProjectContextFiles( +export function loadProjectContextFiles( options: { cwd?: string; agentDir?: string } = {}, ): Array<{ path: string; content: string }> { const resolvedCwd = options.cwd ?? process.cwd(); diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index a221998a..75720b2f 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -153,7 +153,7 @@ export type { } from "./core/package-manager.js"; export { DefaultPackageManager } from "./core/package-manager.js"; export type { ResourceCollision, ResourceDiagnostic, ResourceLoader } from "./core/resource-loader.js"; -export { DefaultResourceLoader } from "./core/resource-loader.js"; +export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.js"; // SDK for programmatic usage export { AgentSessionRuntime, From b5007640d9186b11299c943d8e2d3846d4b519b4 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 12:43:52 +0200 Subject: [PATCH 03/38] docs(coding-agent): changelog entry for loadProjectContextFiles export closes #3142 --- packages/coding-agent/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index e5a95ded..fc63a2ab 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Added `--no-context-files` (`-nc`) to disable `AGENTS.md` and `CLAUDE.md` context file discovery and loading ([#3253](https://github.com/badlogic/pi-mono/issues/3253)) +- Exported `loadProjectContextFiles()` as a standalone utility so extensions can discover project context files without instantiating a full `DefaultResourceLoader` ([#3142](https://github.com/badlogic/pi-mono/issues/3142)) ### Fixed From e9ba9e2ebce1b49dca85ab20007038c06e486ea5 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 13:15:58 +0200 Subject: [PATCH 04/38] fix(coding-agent): harden find cancellation and grep match formatting closes #3148 --- packages/coding-agent/CHANGELOG.md | 3 + packages/coding-agent/src/core/tools/find.ts | 222 ++++++++++++------- packages/coding-agent/src/core/tools/grep.ts | 21 +- packages/coding-agent/test/tools.test.ts | 9 + 4 files changed, 176 insertions(+), 79 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index fc63a2ab..73e2f152 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -6,12 +6,15 @@ - Added `--no-context-files` (`-nc`) to disable `AGENTS.md` and `CLAUDE.md` context file discovery and loading ([#3253](https://github.com/badlogic/pi-mono/issues/3253)) - Exported `loadProjectContextFiles()` as a standalone utility so extensions can discover project context files without instantiating a full `DefaultResourceLoader` ([#3142](https://github.com/badlogic/pi-mono/issues/3142)) +- Added `after_provider_response` extension hook so extensions can inspect provider HTTP status codes and headers after each provider response is received and before stream consumption begins ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) ### Fixed - Fixed flaky `edit-tool-no-full-redraw` TUI tests by waiting for asynchronous preview and preflight error rendering instead of relying on fixed render ticks. - Fixed `kimi-coding` default model selection to use `kimi-for-coding` instead of `kimi-k2-thinking` ([#3242](https://github.com/badlogic/pi-mono/issues/3242)) - Fixed `ctrl+z` on native Windows to avoid crashing interactive mode, disable the default suspend binding there, and show a status message when suspend is invoked manually ([#3191](https://github.com/badlogic/pi-mono/issues/3191)) +- Fixed `find` tool cancellation and responsiveness on broad searches by making `.gitignore` discovery and `fd` execution fully abort-aware and non-blocking ([#3148](https://github.com/badlogic/pi-mono/issues/3148)) +- Fixed `grep` broad-search stalls when `context=0` by formatting match lines from ripgrep JSON output instead of doing synchronous per-match file reads ([#3205](https://github.com/badlogic/pi-mono/issues/3205)) ## [0.67.3] - 2026-04-15 diff --git a/packages/coding-agent/src/core/tools/find.ts b/packages/coding-agent/src/core/tools/find.ts index dd71434f..9e9e95b2 100644 --- a/packages/coding-agent/src/core/tools/find.ts +++ b/packages/coding-agent/src/core/tools/find.ts @@ -1,9 +1,10 @@ +import { createInterface } from "node:readline"; import type { AgentTool } from "@mariozechner/pi-agent-core"; import { Text } from "@mariozechner/pi-tui"; import { type Static, Type } from "@sinclair/typebox"; -import { spawnSync } from "child_process"; +import { spawn } from "child_process"; import { existsSync } from "fs"; -import { globSync } from "glob"; +import { glob } from "glob"; import path from "path"; import { keyHint } from "../../modes/interactive/components/keybinding-hints.js"; import { ensureTool } from "../../utils/tools-manager.js"; @@ -133,7 +134,19 @@ export function createFindToolDefinition( return; } - const onAbort = () => reject(new Error("Operation aborted")); + let settled = false; + let stopChild: (() => void) | undefined; + const settle = (fn: () => void) => { + if (settled) return; + settled = true; + signal?.removeEventListener("abort", onAbort); + stopChild = undefined; + fn(); + }; + const onAbort = () => { + stopChild?.(); + settle(() => reject(new Error("Operation aborted"))); + }; signal?.addEventListener("abort", onAbort, { once: true }); (async () => { @@ -145,19 +158,28 @@ export function createFindToolDefinition( // If custom operations provide glob(), use that instead of fd. if (customOps?.glob) { if (!(await ops.exists(searchPath))) { - reject(new Error(`Path not found: ${searchPath}`)); + settle(() => reject(new Error(`Path not found: ${searchPath}`))); + return; + } + if (signal?.aborted) { + settle(() => reject(new Error("Operation aborted"))); return; } const results = await ops.glob(pattern, searchPath, { ignore: ["**/node_modules/**", "**/.git/**"], limit: effectiveLimit, }); - signal?.removeEventListener("abort", onAbort); + if (signal?.aborted) { + settle(() => reject(new Error("Operation aborted"))); + return; + } if (results.length === 0) { - resolve({ - content: [{ type: "text", text: "No files found matching pattern" }], - details: undefined, - }); + settle(() => + resolve({ + content: [{ type: "text", text: "No files found matching pattern" }], + details: undefined, + }), + ); return; } @@ -183,17 +205,23 @@ export function createFindToolDefinition( if (notices.length > 0) { resultOutput += `\n\n[${notices.join(". ")}]`; } - resolve({ - content: [{ type: "text", text: resultOutput }], - details: Object.keys(details).length > 0 ? details : undefined, - }); + settle(() => + resolve({ + content: [{ type: "text", text: resultOutput }], + details: Object.keys(details).length > 0 ? details : undefined, + }), + ); return; } // Default implementation uses fd. const fdPath = await ensureTool("fd", true); + if (signal?.aborted) { + settle(() => reject(new Error("Operation aborted"))); + return; + } if (!fdPath) { - reject(new Error("fd is not available and could not be downloaded")); + settle(() => reject(new Error("fd is not available and could not be downloaded"))); return; } @@ -210,84 +238,128 @@ export function createFindToolDefinition( const rootGitignore = path.join(searchPath, ".gitignore"); if (existsSync(rootGitignore)) gitignoreFiles.add(rootGitignore); try { - const nestedGitignores = globSync("**/.gitignore", { + const nestedGitignores = await glob("**/.gitignore", { cwd: searchPath, dot: true, absolute: true, ignore: ["**/node_modules/**", "**/.git/**"], + signal, }); for (const file of nestedGitignores) gitignoreFiles.add(file); } catch { + if (signal?.aborted) { + settle(() => reject(new Error("Operation aborted"))); + return; + } // ignore } + if (signal?.aborted) { + settle(() => reject(new Error("Operation aborted"))); + return; + } for (const gitignorePath of gitignoreFiles) args.push("--ignore-file", gitignorePath); args.push(pattern, searchPath); - const result = spawnSync(fdPath, args, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }); - signal?.removeEventListener("abort", onAbort); - if (result.error) { - reject(new Error(`Failed to run fd: ${result.error.message}`)); - return; - } + const child = spawn(fdPath, args, { stdio: ["ignore", "pipe", "pipe"] }); + const rl = createInterface({ input: child.stdout }); + let stderr = ""; + const lines: string[] = []; - const output = result.stdout?.trim() || ""; - if (result.status !== 0) { - const errorMsg = result.stderr?.trim() || `fd exited with code ${result.status}`; - if (!output) { - reject(new Error(errorMsg)); + stopChild = () => { + if (!child.killed) { + child.kill(); + } + }; + + const cleanup = () => { + rl.close(); + }; + + child.stderr?.on("data", (chunk) => { + stderr += chunk.toString(); + }); + + rl.on("line", (line) => { + lines.push(line); + }); + + child.on("error", (error) => { + cleanup(); + settle(() => reject(new Error(`Failed to run fd: ${error.message}`))); + }); + + child.on("close", (code) => { + cleanup(); + if (signal?.aborted) { + settle(() => reject(new Error("Operation aborted"))); return; } - } - if (!output) { - resolve({ - content: [{ type: "text", text: "No files found matching pattern" }], - details: undefined, - }); + const output = lines.join("\n"); + if (code !== 0) { + const errorMsg = stderr.trim() || `fd exited with code ${code}`; + if (!output) { + settle(() => reject(new Error(errorMsg))); + return; + } + } + if (!output) { + settle(() => + resolve({ + content: [{ type: "text", text: "No files found matching pattern" }], + details: undefined, + }), + ); + return; + } + + const relativized: string[] = []; + for (const rawLine of lines) { + const line = rawLine.replace(/\r$/, "").trim(); + if (!line) continue; + const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\"); + let relativePath = line; + if (line.startsWith(searchPath)) { + relativePath = line.slice(searchPath.length + 1); + } else { + relativePath = path.relative(searchPath, line); + } + if (hadTrailingSlash && !relativePath.endsWith("/")) relativePath += "/"; + relativized.push(toPosixPath(relativePath)); + } + + const resultLimitReached = relativized.length >= effectiveLimit; + const rawOutput = relativized.join("\n"); + const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER }); + let resultOutput = truncation.content; + const details: FindToolDetails = {}; + const notices: string[] = []; + if (resultLimitReached) { + notices.push( + `${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`, + ); + details.resultLimitReached = effectiveLimit; + } + if (truncation.truncated) { + notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`); + details.truncation = truncation; + } + if (notices.length > 0) { + resultOutput += `\n\n[${notices.join(". ")}]`; + } + settle(() => + resolve({ + content: [{ type: "text", text: resultOutput }], + details: Object.keys(details).length > 0 ? details : undefined, + }), + ); + }); + } catch (e) { + if (signal?.aborted) { + settle(() => reject(new Error("Operation aborted"))); return; } - - const lines = output.split("\n"); - const relativized: string[] = []; - for (const rawLine of lines) { - const line = rawLine.replace(/\r$/, "").trim(); - if (!line) continue; - const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\"); - let relativePath = line; - if (line.startsWith(searchPath)) { - relativePath = line.slice(searchPath.length + 1); - } else { - relativePath = path.relative(searchPath, line); - } - if (hadTrailingSlash && !relativePath.endsWith("/")) relativePath += "/"; - relativized.push(toPosixPath(relativePath)); - } - - const resultLimitReached = relativized.length >= effectiveLimit; - const rawOutput = relativized.join("\n"); - const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER }); - let resultOutput = truncation.content; - const details: FindToolDetails = {}; - const notices: string[] = []; - if (resultLimitReached) { - notices.push( - `${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`, - ); - details.resultLimitReached = effectiveLimit; - } - if (truncation.truncated) { - notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`); - details.truncation = truncation; - } - if (notices.length > 0) { - resultOutput += `\n\n[${notices.join(". ")}]`; - } - resolve({ - content: [{ type: "text", text: resultOutput }], - details: Object.keys(details).length > 0 ? details : undefined, - }); - } catch (e: any) { - signal?.removeEventListener("abort", onAbort); - reject(e); + const error = e instanceof Error ? e : new Error(String(e)); + settle(() => reject(error)); } })(); }); diff --git a/packages/coding-agent/src/core/tools/grep.ts b/packages/coding-agent/src/core/tools/grep.ts index 6802027e..a9c07282 100644 --- a/packages/coding-agent/src/core/tools/grep.ts +++ b/packages/coding-agent/src/core/tools/grep.ts @@ -267,7 +267,7 @@ export function createGrepToolDefinition( }; // Collect matches during streaming, then format them after rg exits. - const matches: Array<{ filePath: string; lineNumber: number }> = []; + const matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = []; rl.on("line", (line) => { if (!line.trim() || matchCount >= effectiveLimit) return; let event: any; @@ -280,7 +280,9 @@ export function createGrepToolDefinition( matchCount++; const filePath = event.data?.path?.text; const lineNumber = event.data?.line_number; - if (filePath && typeof lineNumber === "number") matches.push({ filePath, lineNumber }); + const lineText = event.data?.lines?.text; + if (filePath && typeof lineNumber === "number") + matches.push({ filePath, lineNumber, lineText }); if (matchCount >= effectiveLimit) { matchLimitReached = true; stopChild(true); @@ -312,8 +314,19 @@ export function createGrepToolDefinition( // Format matches after streaming finishes so custom readFile() backends can be async. for (const match of matches) { - const block = await formatBlock(match.filePath, match.lineNumber); - outputLines.push(...block); + if (contextValue === 0 && match.lineText !== undefined) { + const relativePath = formatPath(match.filePath); + const sanitized = match.lineText + .replace(/\r\n/g, "\n") + .replace(/\r/g, "") + .replace(/\n$/, ""); + const { text: truncatedText, wasTruncated } = truncateLine(sanitized); + if (wasTruncated) linesTruncated = true; + outputLines.push(`${relativePath}:${match.lineNumber}: ${truncatedText}`); + } else { + const block = await formatBlock(match.filePath, match.lineNumber); + outputLines.push(...block); + } } const rawOutput = outputLines.join("\n"); diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 7e989839..5cb12279 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -557,6 +557,15 @@ describe("Coding Agent Tools", () => { expect(output).toContain("kept.txt"); expect(output).not.toContain("ignored.txt"); }); + + it("should surface fd glob parse errors", async () => { + await expect( + findTool.execute("test-call-15", { + pattern: "[", + path: testDir, + }), + ).rejects.toThrow(/error parsing glob|fd exited with code 1|fd error/i); + }); }); describe("ls tool", () => { From a91978cf19863af392de609c0e6c44d5af457c06 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 17:06:23 +0200 Subject: [PATCH 05/38] fix(ai): add temporary Anthropic Opus 4.7 model override --- packages/ai/scripts/generate-models.ts | 21 +++++++++ packages/ai/src/models.generated.ts | 61 +++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 902d798c..ea5e14a1 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -769,6 +769,27 @@ async function generateModels() { }); } + // Add missing Claude Opus 4.7 + if (!allModels.some(m => m.provider === "anthropic" && m.id === "claude-opus-4-7")) { + allModels.push({ + id: "claude-opus-4-7", + name: "Claude Opus 4.7", + api: "anthropic-messages", + baseUrl: "https://api.anthropic.com", + provider: "anthropic", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + }); + } + // Add missing Claude Sonnet 4.6 if (!allModels.some(m => m.provider === "anthropic" && m.id === "claude-sonnet-4-6")) { allModels.push({ diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 757d4c68..f647631c 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -1775,6 +1775,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"anthropic-messages">, + "claude-opus-4-7": { + id: "claude-opus-4-7", + name: "Claude Opus 4.7", + api: "anthropic-messages", + provider: "anthropic", + baseUrl: "https://api.anthropic.com", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, "claude-sonnet-4-0": { id: "claude-sonnet-4-0", name: "Claude Sonnet 4 (latest)", @@ -7115,6 +7132,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"openai-completions">, + "anthropic/claude-opus-4.7": { + id: "anthropic/claude-opus-4.7", + name: "Anthropic: Claude Opus 4.7", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"openai-completions">, "anthropic/claude-sonnet-4": { id: "anthropic/claude-sonnet-4", name: "Anthropic: Claude Sonnet 4", @@ -7753,13 +7787,13 @@ export const MODELS = { reasoning: true, input: ["text", "image"], cost: { - input: 0.08, - output: 0.35, - cacheRead: 0.01, + input: 0.07, + output: 0.39999999999999997, + cacheRead: 0.04, cacheWrite: 0, }, contextWindow: 262144, - maxTokens: 4096, + maxTokens: 262144, } satisfies Model<"openai-completions">, "google/gemma-4-26b-a4b-it:free": { id: "google/gemma-4-26b-a4b-it:free", @@ -8456,7 +8490,7 @@ export const MODELS = { cacheWrite: 0, }, contextWindow: 131072, - maxTokens: 131072, + maxTokens: 32768, } satisfies Model<"openai-completions">, "moonshotai/kimi-k2-0905": { id: "moonshotai/kimi-k2-0905", @@ -11452,6 +11486,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"anthropic-messages">, + "anthropic/claude-opus-4.7": { + id: "anthropic/claude-opus-4.7", + name: "Claude Opus 4.7", + api: "anthropic-messages", + provider: "vercel-ai-gateway", + baseUrl: "https://ai-gateway.vercel.sh", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, "anthropic/claude-sonnet-4": { id: "anthropic/claude-sonnet-4", name: "Claude Sonnet 4", From b071e99b27c64f1b0b9aae3f3e9fe0c9d375925d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 17:31:34 +0200 Subject: [PATCH 06/38] docs(changelog): audit unreleased entries since v0.67.3 --- packages/ai/CHANGELOG.md | 1 + packages/coding-agent/CHANGELOG.md | 13 +++++++++++++ packages/tui/CHANGELOG.md | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 186c607d..52cbffbe 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +- Added `claude-opus-4-7` model for Anthropic, OpenRouter. - Changed Anthropic prompt caching to add a `cache_control` breakpoint on the last tool definition, so tool schemas can be cached independently from transcript updates while preserving existing cache retention behavior ([#3260](https://github.com/badlogic/pi-mono/issues/3260)) - Changed Kimi Coding model generation to normalize deprecated `k2p5` to `kimi-for-coding` from models.dev data and removed the old static fallback model list ([#3242](https://github.com/badlogic/pi-mono/issues/3242)) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 73e2f152..06491f79 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,14 +2,27 @@ ## [Unreleased] +### New Features + +- `--no-context-files` (`-nc`) disables automatic `AGENTS.md` / `CLAUDE.md` discovery when you need a clean run without project context injection. See [README.md#context-files](README.md#context-files). +- `loadProjectContextFiles()` is now exported as a standalone utility for extensions and SDK-style integrations that need to inspect the same context-file resolution order used by the CLI. See [README.md#context-files](README.md#context-files). +- New `after_provider_response` extension hook lets extensions inspect provider HTTP status codes and headers immediately after response creation and before stream consumption. See [docs/extensions.md](docs/extensions.md). + ### Added - Added `--no-context-files` (`-nc`) to disable `AGENTS.md` and `CLAUDE.md` context file discovery and loading ([#3253](https://github.com/badlogic/pi-mono/issues/3253)) - Exported `loadProjectContextFiles()` as a standalone utility so extensions can discover project context files without instantiating a full `DefaultResourceLoader` ([#3142](https://github.com/badlogic/pi-mono/issues/3142)) - Added `after_provider_response` extension hook so extensions can inspect provider HTTP status codes and headers after each provider response is received and before stream consumption begins ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) +### Changed + +- Added `claude-opus-4-7` model for Anthropic. +- Changed Anthropic prompt caching to add a `cache_control` breakpoint on the last tool definition, so tool schemas can be cached independently from transcript updates while preserving existing cache retention behavior ([#3260](https://github.com/badlogic/pi-mono/issues/3260)) + ### Fixed +- Fixed markdown strikethrough parsing in interactive rendering and HTML export to require strict double-tilde delimiters (`~~text~~`) with non-whitespace boundaries. +- Fixed shutdown handling to kill tracked detached `bash` tool child processes on exit signals, preventing orphaned background processes. - Fixed flaky `edit-tool-no-full-redraw` TUI tests by waiting for asynchronous preview and preflight error rendering instead of relying on fixed render ticks. - Fixed `kimi-coding` default model selection to use `kimi-for-coding` instead of `kimi-k2-thinking` ([#3242](https://github.com/badlogic/pi-mono/issues/3242)) - Fixed `ctrl+z` on native Windows to avoid crashing interactive mode, disable the default suspend binding there, and show a status message when suspend is invoked manually ([#3191](https://github.com/badlogic/pi-mono/issues/3191)) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index d3c938bf..16c1be4c 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed markdown strikethrough parsing to require strict double-tilde delimiters (`~~text~~`) with non-whitespace boundaries, preventing accidental strikethrough from loose tilde usage. + ## [0.67.3] - 2026-04-15 ### Fixed From 01949c1d4f07ab59828d680794bcf34407dde0a9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 17:32:50 +0200 Subject: [PATCH 07/38] Release v0.67.4 --- package-lock.json | 545 +++++++++++------- packages/agent/CHANGELOG.md | 2 +- packages/agent/package.json | 4 +- packages/ai/CHANGELOG.md | 2 +- packages/ai/package.json | 2 +- packages/coding-agent/CHANGELOG.md | 2 +- .../package-lock.json | 4 +- .../custom-provider-anthropic/package.json | 2 +- .../custom-provider-gitlab-duo/package.json | 2 +- .../custom-provider-qwen-cli/package.json | 2 +- .../extensions/with-deps/package-lock.json | 4 +- .../extensions/with-deps/package.json | 2 +- packages/coding-agent/package.json | 8 +- packages/mom/CHANGELOG.md | 2 +- packages/mom/package.json | 8 +- packages/pods/package.json | 4 +- packages/tui/CHANGELOG.md | 2 +- packages/tui/package.json | 2 +- packages/web-ui/CHANGELOG.md | 2 +- packages/web-ui/example/package.json | 2 +- packages/web-ui/package.json | 6 +- 21 files changed, 366 insertions(+), 243 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60c6044b..fd3ebd48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -874,6 +874,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -891,6 +894,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -908,6 +914,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -925,6 +934,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -1610,6 +1622,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1626,6 +1641,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1642,6 +1660,9 @@ "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1658,6 +1679,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1674,6 +1698,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1925,6 +1952,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1945,6 +1975,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1965,6 +1998,9 @@ "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1985,6 +2021,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2005,6 +2044,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2224,6 +2266,9 @@ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2245,6 +2290,9 @@ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2266,6 +2314,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2287,6 +2338,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2308,6 +2362,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2329,6 +2386,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2583,6 +2643,9 @@ "cpu": [ "arm" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2596,6 +2659,9 @@ "cpu": [ "arm" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2609,6 +2675,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2622,6 +2691,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2635,6 +2707,9 @@ "cpu": [ "loong64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2648,6 +2723,9 @@ "cpu": [ "loong64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2661,6 +2739,9 @@ "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2674,6 +2755,9 @@ "cpu": [ "ppc64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2687,6 +2771,9 @@ "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2700,6 +2787,9 @@ "cpu": [ "riscv64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2713,6 +2803,9 @@ "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2726,6 +2819,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2739,6 +2835,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2901,16 +3000,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.15", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.15.tgz", - "integrity": "sha512-BJdMBY5YO9iHh+lPLYdHv6LbX+J8IcPCYMl1IJdBt2KDWNHwONHrPVHk3ttYBqJd9wxv84wlbN0f7GlQzcQtNQ==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.16.tgz", + "integrity": "sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.13", - "@smithy/types": "^4.14.0", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.4.0", - "@smithy/util-middleware": "^4.2.13", + "@smithy/util-endpoints": "^3.4.1", + "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" }, "engines": { @@ -2918,18 +3017,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.23.14", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", - "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", + "version": "3.23.15", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.15.tgz", + "integrity": "sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.13", - "@smithy/util-stream": "^4.5.22", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" @@ -2939,15 +3038,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", - "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", + "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.13", - "@smithy/property-provider": "^4.2.13", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" }, "engines": { @@ -2955,13 +3054,13 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", - "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" }, @@ -2970,13 +3069,13 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", - "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -2984,12 +3083,12 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", - "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -2997,13 +3096,13 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", - "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3011,13 +3110,13 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", - "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3025,14 +3124,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", - "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", + "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/querystring-builder": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, @@ -3041,12 +3140,12 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", - "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", + "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -3056,12 +3155,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", - "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", + "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3081,13 +3180,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", - "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", + "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3095,18 +3194,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.29", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", - "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", + "version": "4.4.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.30.tgz", + "integrity": "sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/middleware-serde": "^4.2.17", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", - "@smithy/util-middleware": "^4.2.13", + "@smithy/core": "^3.23.15", + "@smithy/middleware-serde": "^4.2.18", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" }, "engines": { @@ -3114,19 +3213,19 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.1.tgz", - "integrity": "sha512-/zY+Gp7Qj2D2hVm3irkCyONER7E9MiX3cUUm/k2ZmhkzZkrPgwVS4aJ5NriZUEN/M0D1hhjrgjUmX04HhRwdWA==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.3.tgz", + "integrity": "sha512-TE8dJNi6JuxzGSxMCVd3i9IEWDndCl3bmluLsBNDWok8olgj65OfkndMhl9SZ7m14c+C5SQn/PcUmrDl57rSFw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/protocol-http": "^5.3.13", - "@smithy/service-error-classification": "^4.2.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", - "@smithy/util-middleware": "^4.2.13", - "@smithy/util-retry": "^4.3.1", + "@smithy/core": "^3.23.15", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/service-error-classification": "^4.2.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, @@ -3135,14 +3234,14 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.17", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", - "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.18.tgz", + "integrity": "sha512-M6CSgnp3v4tYz9ynj2JHbA60woBZcGqEwNjTKjBsNHPV26R1ZX52+0wW8WsZU18q45jD0tw2wL22S17Ze9LpEw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@smithy/core": "^3.23.15", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3150,12 +3249,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", - "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", + "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3163,14 +3262,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", - "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", + "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3178,14 +3277,14 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", - "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.3.tgz", + "integrity": "sha512-lc5jFL++x17sPhIwMWJ3YOnqmSjw/2Po6VLDlUIXvxVWRuJwRXnJ4jOBBLB0cfI5BB5ehIl02Fxr1PDvk/kxDw==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/querystring-builder": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3193,12 +3292,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", - "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", + "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3206,12 +3305,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.13", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", - "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", + "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3219,12 +3318,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", - "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", + "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, @@ -3233,12 +3332,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", - "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", + "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3246,24 +3345,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", - "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.14.tgz", + "integrity": "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0" + "@smithy/types": "^4.14.1" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", - "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", + "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3271,16 +3370,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.13", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", - "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", + "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.13", + "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -3290,17 +3389,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.12.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", - "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", + "version": "4.12.11", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.11.tgz", + "integrity": "sha512-wzz/Wa1CH/Tlhxh0s4DQPEcXSxSVfJ59AZcUh9Gu0c6JTlKuwGf4o/3P2TExv0VbtPFt8odIBG+eQGK2+vTECg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/middleware-endpoint": "^4.4.29", - "@smithy/middleware-stack": "^4.2.13", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", - "@smithy/util-stream": "^4.5.22", + "@smithy/core": "^3.23.15", + "@smithy/middleware-endpoint": "^4.4.30", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" }, "engines": { @@ -3308,9 +3407,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", - "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3320,13 +3419,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", - "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", + "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/querystring-parser": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3397,14 +3496,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.45", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", - "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", + "version": "4.3.47", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.47.tgz", + "integrity": "sha512-zlIuXai3/SHjQUQ8y3g/woLvrH573SK2wNjcDaHu5e9VOcC0JwM1MI0Sq0GZJyN3BwSUneIhpjZ18nsiz5AtQw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3412,17 +3511,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.50", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.50.tgz", - "integrity": "sha512-xpjncL5XozFA3No7WypTsPU1du0fFS8flIyO+Wh2nhCy7bpEapvU7BR55Bg+wrfw+1cRA+8G8UsTjaxgzrMzXg==", + "version": "4.2.52", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.52.tgz", + "integrity": "sha512-cQBz8g68Vnw1W2meXlkb3D/hXJU+Taiyj9P8qLJtjREEV9/Td65xi4A/H1sRQ8EIgX5qbZbvdYPKygKLholZ3w==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.15", - "@smithy/credential-provider-imds": "^4.2.13", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/property-provider": "^4.2.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", + "@smithy/config-resolver": "^4.4.16", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3430,13 +3529,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.0.tgz", - "integrity": "sha512-QQHGPKkw6NPcU6TJ1rNEEa201srPtZiX4k61xL163vvs9sTqW/XKz+UEuJ00uvPqoN+5Rs4Ka1UJ7+Mp03IXJw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.1.tgz", + "integrity": "sha512-wMxNDZJrgS5mQV9oxCs4TWl5767VMgOfqfZ3JHyCkMtGC2ykW9iPqMvFur695Otcc5yxLG8OKO/80tsQBxrhXg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.13", - "@smithy/types": "^4.14.0", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3456,12 +3555,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", - "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", + "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3469,13 +3568,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.1.tgz", - "integrity": "sha512-FwmicpgWOkP5kZUjN3y+3JIom8NLGqSAJBeoIgK0rIToI817TEBHCrd0A2qGeKQlgDeP+Jzn4i0H/NLAXGy9uQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.2.tgz", + "integrity": "sha512-2+KTsJEwTi63NUv4uR9IQ+IFT1yu6Rf6JuoBK2WKaaJ/TRvOiOVGcXAsEqX/TQN2thR9yII21kPUJq1UV/WI2A==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/service-error-classification": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3483,14 +3582,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.22", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", - "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", + "version": "4.5.23", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.23.tgz", + "integrity": "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.16", - "@smithy/node-http-handler": "^4.5.2", - "@smithy/types": "^4.14.0", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.5.3", + "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", @@ -3682,6 +3781,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3698,6 +3800,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3714,6 +3819,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3730,6 +3838,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -6287,6 +6398,9 @@ "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6307,6 +6421,9 @@ "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6327,6 +6444,9 @@ "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6347,6 +6467,9 @@ "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8555,10 +8678,10 @@ }, "packages/agent": { "name": "@mariozechner/pi-agent-core", - "version": "0.67.3", + "version": "0.67.4", "license": "MIT", "dependencies": { - "@mariozechner/pi-ai": "^0.67.3" + "@mariozechner/pi-ai": "^0.67.4" }, "devDependencies": { "@types/node": "^24.3.0", @@ -8588,7 +8711,7 @@ }, "packages/ai": { "name": "@mariozechner/pi-ai", - "version": "0.67.3", + "version": "0.67.4", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.73.0", @@ -8636,13 +8759,13 @@ }, "packages/coding-agent": { "name": "@mariozechner/pi-coding-agent", - "version": "0.67.3", + "version": "0.67.4", "license": "MIT", "dependencies": { "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.67.3", - "@mariozechner/pi-ai": "^0.67.3", - "@mariozechner/pi-tui": "^0.67.3", + "@mariozechner/pi-agent-core": "^0.67.4", + "@mariozechner/pi-ai": "^0.67.4", + "@mariozechner/pi-tui": "^0.67.4", "@silvia-odwyer/photon-node": "^0.3.4", "ajv": "^8.17.1", "chalk": "^5.5.0", @@ -8683,22 +8806,22 @@ }, "packages/coding-agent/examples/extensions/custom-provider-anthropic": { "name": "pi-extension-custom-provider-anthropic", - "version": "1.18.3", + "version": "1.18.4", "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } }, "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo": { "name": "pi-extension-custom-provider-gitlab-duo", - "version": "1.18.3" + "version": "1.18.4" }, "packages/coding-agent/examples/extensions/custom-provider-qwen-cli": { "name": "pi-extension-custom-provider-qwen-cli", - "version": "1.17.3" + "version": "1.17.4" }, "packages/coding-agent/examples/extensions/with-deps": { "name": "pi-extension-with-deps", - "version": "1.31.3", + "version": "1.31.4", "dependencies": { "ms": "^2.1.3" }, @@ -8734,13 +8857,13 @@ }, "packages/mom": { "name": "@mariozechner/pi-mom", - "version": "0.67.3", + "version": "0.67.4", "license": "MIT", "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.67.3", - "@mariozechner/pi-ai": "^0.67.3", - "@mariozechner/pi-coding-agent": "^0.67.3", + "@mariozechner/pi-agent-core": "^0.67.4", + "@mariozechner/pi-ai": "^0.67.4", + "@mariozechner/pi-coding-agent": "^0.67.4", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", @@ -8779,10 +8902,10 @@ }, "packages/pods": { "name": "@mariozechner/pi", - "version": "0.67.3", + "version": "0.67.4", "license": "MIT", "dependencies": { - "@mariozechner/pi-agent-core": "^0.67.3", + "@mariozechner/pi-agent-core": "^0.67.4", "chalk": "^5.5.0" }, "bin": { @@ -8795,7 +8918,7 @@ }, "packages/tui": { "name": "@mariozechner/pi-tui", - "version": "0.67.3", + "version": "0.67.4", "license": "MIT", "dependencies": { "@types/mime-types": "^2.1.4", @@ -8842,12 +8965,12 @@ }, "packages/web-ui": { "name": "@mariozechner/pi-web-ui", - "version": "0.67.3", + "version": "0.67.4", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.67.3", - "@mariozechner/pi-tui": "^0.67.3", + "@mariozechner/pi-ai": "^0.67.4", + "@mariozechner/pi-tui": "^0.67.4", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", @@ -8868,7 +8991,7 @@ }, "packages/web-ui/example": { "name": "pi-web-ui-example", - "version": "1.55.3", + "version": "1.55.4", "dependencies": { "@mariozechner/mini-lit": "^0.2.0", "@mariozechner/pi-ai": "file:../../ai", diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index b16bfac0..c76bc4cd 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.4] - 2026-04-16 ## [0.67.3] - 2026-04-15 diff --git a/packages/agent/package.json b/packages/agent/package.json index 353e9993..c1ac1eed 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-agent-core", - "version": "0.67.3", + "version": "0.67.4", "description": "General-purpose agent with transport abstraction, state management, and attachment support", "type": "module", "main": "./dist/index.js", @@ -17,7 +17,7 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@mariozechner/pi-ai": "^0.67.3" + "@mariozechner/pi-ai": "^0.67.4" }, "keywords": [ "ai", diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 52cbffbe..1d1c0be8 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.4] - 2026-04-16 ### Changed diff --git a/packages/ai/package.json b/packages/ai/package.json index 66877c85..5d7434f9 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-ai", - "version": "0.67.3", + "version": "0.67.4", "description": "Unified LLM API with automatic model discovery and provider configuration", "type": "module", "main": "./dist/index.js", diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 06491f79..35b22c9c 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.4] - 2026-04-16 ### New Features diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json index 735ed093..437767d7 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-extension-custom-provider", - "version": "1.18.3", + "version": "1.18.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-extension-custom-provider", - "version": "1.18.3", + "version": "1.18.4", "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json index 21c572ca..38b3638c 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-anthropic", "private": true, - "version": "1.18.3", + "version": "1.18.4", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json index 6694a0a5..1dbaf8da 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-gitlab-duo", "private": true, - "version": "1.18.3", + "version": "1.18.4", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json b/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json index 7ef3cb4b..985b7fb9 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-qwen-cli", "private": true, - "version": "1.17.3", + "version": "1.17.4", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/with-deps/package-lock.json b/packages/coding-agent/examples/extensions/with-deps/package-lock.json index d6c6656f..031cef60 100644 --- a/packages/coding-agent/examples/extensions/with-deps/package-lock.json +++ b/packages/coding-agent/examples/extensions/with-deps/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-extension-with-deps", - "version": "1.31.3", + "version": "1.31.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-extension-with-deps", - "version": "1.31.3", + "version": "1.31.4", "dependencies": { "ms": "^2.1.3" }, diff --git a/packages/coding-agent/examples/extensions/with-deps/package.json b/packages/coding-agent/examples/extensions/with-deps/package.json index dc5ce841..8669a360 100644 --- a/packages/coding-agent/examples/extensions/with-deps/package.json +++ b/packages/coding-agent/examples/extensions/with-deps/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-with-deps", "private": true, - "version": "1.31.3", + "version": "1.31.4", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 4543b262..a1cf2b1e 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-coding-agent", - "version": "0.67.3", + "version": "0.67.4", "description": "Coding agent CLI with read, bash, edit, write tools and session management", "type": "module", "piConfig": { @@ -40,9 +40,9 @@ }, "dependencies": { "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.67.3", - "@mariozechner/pi-ai": "^0.67.3", - "@mariozechner/pi-tui": "^0.67.3", + "@mariozechner/pi-agent-core": "^0.67.4", + "@mariozechner/pi-ai": "^0.67.4", + "@mariozechner/pi-tui": "^0.67.4", "@silvia-odwyer/photon-node": "^0.3.4", "ajv": "^8.17.1", "chalk": "^5.5.0", diff --git a/packages/mom/CHANGELOG.md b/packages/mom/CHANGELOG.md index 3a637a7a..b038d5c3 100644 --- a/packages/mom/CHANGELOG.md +++ b/packages/mom/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.4] - 2026-04-16 ## [0.67.3] - 2026-04-15 diff --git a/packages/mom/package.json b/packages/mom/package.json index 351732de..7743a9cc 100644 --- a/packages/mom/package.json +++ b/packages/mom/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-mom", - "version": "0.67.3", + "version": "0.67.4", "description": "Slack bot that delegates messages to the pi coding agent", "type": "module", "bin": { @@ -20,9 +20,9 @@ }, "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.67.3", - "@mariozechner/pi-ai": "^0.67.3", - "@mariozechner/pi-coding-agent": "^0.67.3", + "@mariozechner/pi-agent-core": "^0.67.4", + "@mariozechner/pi-ai": "^0.67.4", + "@mariozechner/pi-coding-agent": "^0.67.4", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", diff --git a/packages/pods/package.json b/packages/pods/package.json index 0910bbff..eaae17d0 100644 --- a/packages/pods/package.json +++ b/packages/pods/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi", - "version": "0.67.3", + "version": "0.67.4", "description": "CLI tool for managing vLLM deployments on GPU pods", "type": "module", "bin": { @@ -33,7 +33,7 @@ "node": ">=20.0.0" }, "dependencies": { - "@mariozechner/pi-agent-core": "^0.67.3", + "@mariozechner/pi-agent-core": "^0.67.4", "chalk": "^5.5.0" }, "devDependencies": {} diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 16c1be4c..983d8809 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.4] - 2026-04-16 ### Fixed diff --git a/packages/tui/package.json b/packages/tui/package.json index 355d2b45..538c7016 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-tui", - "version": "0.67.3", + "version": "0.67.4", "description": "Terminal User Interface library with differential rendering for efficient text-based applications", "type": "module", "main": "dist/index.js", diff --git a/packages/web-ui/CHANGELOG.md b/packages/web-ui/CHANGELOG.md index 4bbc697e..31edf8ae 100644 --- a/packages/web-ui/CHANGELOG.md +++ b/packages/web-ui/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.4] - 2026-04-16 ## [0.67.3] - 2026-04-15 diff --git a/packages/web-ui/example/package.json b/packages/web-ui/example/package.json index 26df3602..42a144ea 100644 --- a/packages/web-ui/example/package.json +++ b/packages/web-ui/example/package.json @@ -1,6 +1,6 @@ { "name": "pi-web-ui-example", - "version": "1.55.3", + "version": "1.55.4", "private": true, "type": "module", "scripts": { diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index 3aa5a114..0e1d03ec 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-web-ui", - "version": "0.67.3", + "version": "0.67.4", "description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai", "type": "module", "main": "dist/index.js", @@ -18,8 +18,8 @@ }, "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.67.3", - "@mariozechner/pi-tui": "^0.67.3", + "@mariozechner/pi-ai": "^0.67.4", + "@mariozechner/pi-tui": "^0.67.4", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", From 72619e92466fd2f181d660eb4e3f5eff630ad8f5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 17:56:55 +0200 Subject: [PATCH 08/38] Add [Unreleased] section for next cycle --- packages/agent/CHANGELOG.md | 2 ++ packages/ai/CHANGELOG.md | 2 ++ packages/coding-agent/CHANGELOG.md | 2 ++ packages/mom/CHANGELOG.md | 2 ++ packages/tui/CHANGELOG.md | 2 ++ packages/web-ui/CHANGELOG.md | 2 ++ 6 files changed, 12 insertions(+) diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index c76bc4cd..65e83472 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.4] - 2026-04-16 ## [0.67.3] - 2026-04-15 diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 1d1c0be8..ee0a280d 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.4] - 2026-04-16 ### Changed diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 35b22c9c..d10a3806 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.4] - 2026-04-16 ### New Features diff --git a/packages/mom/CHANGELOG.md b/packages/mom/CHANGELOG.md index b038d5c3..b1185bed 100644 --- a/packages/mom/CHANGELOG.md +++ b/packages/mom/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.4] - 2026-04-16 ## [0.67.3] - 2026-04-15 diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 983d8809..12185e97 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.4] - 2026-04-16 ### Fixed diff --git a/packages/web-ui/CHANGELOG.md b/packages/web-ui/CHANGELOG.md index 31edf8ae..550b72c7 100644 --- a/packages/web-ui/CHANGELOG.md +++ b/packages/web-ui/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.4] - 2026-04-16 ## [0.67.3] - 2026-04-15 From d1c6cb1e0f8bf7051c33d66588999b347ae08b04 Mon Sep 17 00:00:00 2001 From: Markus Ylisiurunen <8409947+markusylisiurunen@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:57:19 +0300 Subject: [PATCH 09/38] fix(ai): Fix a configuration bug with Opus 4.7 adaptive thinking (#3286) --- package-lock.json | 10 +-- packages/ai/package.json | 4 +- packages/ai/src/models.ts | 9 ++- packages/ai/src/providers/amazon-bedrock.ts | 14 +++- packages/ai/src/providers/anthropic.ts | 21 +++-- .../ai/test/anthropic-opus-4-7-smoke.test.ts | 72 +++++++++++++++++ .../test/anthropic-thinking-disable.test.ts | 29 ++++++- .../ai/test/bedrock-thinking-payload.test.ts | 78 +++++++++++++++++++ packages/ai/test/supports-xhigh.test.ts | 6 ++ 9 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 packages/ai/test/anthropic-opus-4-7-smoke.test.ts create mode 100644 packages/ai/test/bedrock-thinking-payload.test.ts diff --git a/package-lock.json b/package-lock.json index fd3ebd48..2462a1ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,9 +64,9 @@ } }, "node_modules/@anthropic-ai/sdk": { - "version": "0.73.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", - "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", + "version": "0.90.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.90.0.tgz", + "integrity": "sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg==", "license": "MIT", "dependencies": { "json-schema-to-ts": "^3.1.1" @@ -8714,8 +8714,8 @@ "version": "0.67.4", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "^0.73.0", - "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@anthropic-ai/sdk": "^0.90.0", + "@aws-sdk/client-bedrock-runtime": "^3.1030.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.14.1", "@sinclair/typebox": "^0.34.41", diff --git a/packages/ai/package.json b/packages/ai/package.json index 5d7434f9..f31cc5bd 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -72,8 +72,8 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@anthropic-ai/sdk": "^0.73.0", - "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@anthropic-ai/sdk": "^0.90.0", + "@aws-sdk/client-bedrock-runtime": "^3.1030.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.14.1", "@sinclair/typebox": "^0.34.41", diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index 101c83f2..8944d07e 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -50,14 +50,19 @@ export function calculateCost(model: Model, usage: Usage * * Supported today: * - GPT-5.2 / GPT-5.3 / GPT-5.4 model families - * - Opus 4.6 models (xhigh maps to adaptive effort "max" on Anthropic-compatible providers) + * - Opus 4.6+ models (xhigh maps to adaptive effort "max" on Anthropic-compatible providers) */ export function supportsXhigh(model: Model): boolean { if (model.id.includes("gpt-5.2") || model.id.includes("gpt-5.3") || model.id.includes("gpt-5.4")) { return true; } - if (model.id.includes("opus-4-6") || model.id.includes("opus-4.6")) { + if ( + model.id.includes("opus-4-6") || + model.id.includes("opus-4.6") || + model.id.includes("opus-4-7") || + model.id.includes("opus-4.7") + ) { return true; } diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index a0aa55c3..00333f0c 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -423,12 +423,14 @@ function handleContentBlockStop( } /** - * Check if the model supports adaptive thinking (Opus 4.6 and Sonnet 4.6). + * Check if the model supports adaptive thinking (Opus 4.6+, Sonnet 4.6). */ function supportsAdaptiveThinking(modelId: string): boolean { return ( modelId.includes("opus-4-6") || modelId.includes("opus-4.6") || + modelId.includes("opus-4-7") || + modelId.includes("opus-4.7") || modelId.includes("sonnet-4-6") || modelId.includes("sonnet-4.6") ); @@ -437,7 +439,7 @@ function supportsAdaptiveThinking(modelId: string): boolean { function mapThinkingLevelToEffort( level: SimpleStreamOptions["reasoning"], modelId: string, -): "low" | "medium" | "high" | "max" { +): "low" | "medium" | "high" | "xhigh" | "max" { switch (level) { case "minimal": case "low": @@ -447,7 +449,13 @@ function mapThinkingLevelToEffort( case "high": return "high"; case "xhigh": - return modelId.includes("opus-4-6") || modelId.includes("opus-4.6") ? "max" : "high"; + if (modelId.includes("opus-4-6") || modelId.includes("opus-4.6")) { + return "max"; + } + if (modelId.includes("opus-4-7") || modelId.includes("opus-4.7")) { + return "xhigh"; + } + return "high"; default: return "high"; } diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 53d29e45..1741b400 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -153,7 +153,7 @@ function convertContentBlocks(content: (TextContent | ImageContent)[]): return blocks; } -export type AnthropicEffort = "low" | "medium" | "high" | "max"; +export type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max"; export interface AnthropicOptions extends StreamOptions { /** @@ -168,9 +168,10 @@ export interface AnthropicOptions extends StreamOptions { */ thinkingBudgetTokens?: number; /** - * Effort level for adaptive thinking (Opus 4.6 and Sonnet 4.6). + * Effort level for adaptive thinking (Opus 4.6+ and Sonnet 4.6). * Controls how much thinking Claude allocates: * - "max": Always thinks with no constraints (Opus 4.6 only) + * - "xhigh": Highest reasoning level (Opus 4.7) * - "high": Always thinks, deep reasoning (default) * - "medium": Moderate thinking, may skip for simple queries * - "low": Minimal thinking, skips for simple tasks @@ -448,13 +449,15 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti }; /** - * Check if a model supports adaptive thinking (Opus 4.6 and Sonnet 4.6) + * Check if a model supports adaptive thinking (Opus 4.6+, Sonnet 4.6) */ function supportsAdaptiveThinking(modelId: string): boolean { - // Opus 4.6 and Sonnet 4.6 model IDs (with or without date suffix) + // Adaptive-thinking model IDs (with or without date suffix) return ( modelId.includes("opus-4-6") || modelId.includes("opus-4.6") || + modelId.includes("opus-4-7") || + modelId.includes("opus-4.7") || modelId.includes("sonnet-4-6") || modelId.includes("sonnet-4.6") ); @@ -462,7 +465,7 @@ function supportsAdaptiveThinking(modelId: string): boolean { /** * Map ThinkingLevel to Anthropic effort levels for adaptive thinking. - * Note: effort "max" is only valid on Opus 4.6. + * Note: effort "max" is only valid on Opus 4.6, while Opus 4.7 supports "xhigh". */ function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"], modelId: string): AnthropicEffort { switch (level) { @@ -475,7 +478,13 @@ function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"], model case "high": return "high"; case "xhigh": - return modelId.includes("opus-4-6") || modelId.includes("opus-4.6") ? "max" : "high"; + if (modelId.includes("opus-4-6") || modelId.includes("opus-4.6")) { + return "max"; + } + if (modelId.includes("opus-4-7") || modelId.includes("opus-4.7")) { + return "xhigh"; + } + return "high"; default: return "high"; } diff --git a/packages/ai/test/anthropic-opus-4-7-smoke.test.ts b/packages/ai/test/anthropic-opus-4-7-smoke.test.ts new file mode 100644 index 00000000..29626d1f --- /dev/null +++ b/packages/ai/test/anthropic-opus-4-7-smoke.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from "vitest"; +import { getModel } from "../src/models.js"; +import { streamSimple } from "../src/stream.js"; +import type { Context } from "../src/types.js"; + +interface AnthropicThinkingPayload { + thinking?: { type: string }; + output_config?: { effort?: string }; +} + +function makeContext(): Context { + return { + systemPrompt: "You are a precise assistant. Follow the user's instructions exactly.", + messages: [ + { + role: "user", + content: + "Compute 48291 * 7317 and 90844 - 17729, add the results, and determine whether the sum is divisible by 11. Reply with exactly this format and nothing else: sum=; divisibleBy11=", + timestamp: Date.now(), + }, + ], + }; +} + +describe.skipIf(!process.env.ANTHROPIC_API_KEY)("Anthropic Opus 4.7 smoke", () => { + it("streams Claude Opus 4.7 with reasoning enabled", { retry: 2, timeout: 30000 }, async () => { + const model = getModel("anthropic", "claude-opus-4-7"); + let capturedPayload: AnthropicThinkingPayload | undefined; + const s = streamSimple(model, makeContext(), { + reasoning: "high", + maxTokens: 1024, + onPayload: (payload) => { + capturedPayload = payload as AnthropicThinkingPayload; + return payload; + }, + }); + + let sawThinking = false; + + for await (const event of s) { + if (event.type === "thinking_start" || event.type === "thinking_delta" || event.type === "thinking_end") { + sawThinking = true; + } + } + + const response = await s.result(); + expect(response.stopReason, response.errorMessage).toBe("stop"); + expect(response.errorMessage).toBeFalsy(); + expect(capturedPayload?.thinking).toEqual({ type: "adaptive" }); + expect(capturedPayload?.output_config).toEqual({ effort: "high" }); + expect(sawThinking).toBe(true); + + const thinkingBlock = response.content.find((block) => block.type === "thinking"); + expect(thinkingBlock?.type).toBe("thinking"); + if (!thinkingBlock || thinkingBlock.type !== "thinking") { + throw new Error("Expected thinking block from Claude Opus 4.7"); + } + expect(typeof thinkingBlock.thinkingSignature).toBe("string"); + const thinkingSignature = thinkingBlock.thinkingSignature; + if (!thinkingSignature) { + throw new Error("Expected thinking signature from Claude Opus 4.7"); + } + expect(thinkingSignature.length).toBeGreaterThan(0); + + const text = response.content + .filter((block) => block.type === "text") + .map((block) => block.text) + .join("") + .trim(); + expect(text).toBe("sum=353418362; divisibleBy11=yes"); + }); +}); diff --git a/packages/ai/test/anthropic-thinking-disable.test.ts b/packages/ai/test/anthropic-thinking-disable.test.ts index 8b154d48..7f406b30 100644 --- a/packages/ai/test/anthropic-thinking-disable.test.ts +++ b/packages/ai/test/anthropic-thinking-disable.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { getModel } from "../src/models.js"; import { streamSimple } from "../src/stream.js"; -import type { Context, Model } from "../src/types.js"; +import type { Context, Model, SimpleStreamOptions } from "../src/types.js"; interface AnthropicThinkingPayload { thinking?: { type: string; budget_tokens?: number }; @@ -14,7 +14,10 @@ function makePayloadCaptureContext(): Context { }; } -async function capturePayload(model: Model<"anthropic-messages">): Promise { +async function capturePayload( + model: Model<"anthropic-messages">, + options?: SimpleStreamOptions, +): Promise { let capturedPayload: AnthropicThinkingPayload | undefined; const payloadCaptureModel: Model<"anthropic-messages"> = { ...model, @@ -22,6 +25,7 @@ async function capturePayload(model: Model<"anthropic-messages">): Promise { capturedPayload = payload as AnthropicThinkingPayload; @@ -113,6 +117,27 @@ describe("Anthropic thinking disable payload", () => { expect(payload.thinking).toEqual({ type: "disabled" }); expect(payload.output_config).toBeUndefined(); }); + + it("sends thinking.type=disabled for Claude Opus 4.7 when thinking is off", async () => { + const payload = await capturePayload(getModel("anthropic", "claude-opus-4-7")); + + expect(payload.thinking).toEqual({ type: "disabled" }); + expect(payload.output_config).toBeUndefined(); + }); + + it("uses adaptive thinking for Claude Opus 4.7 when reasoning is enabled", async () => { + const payload = await capturePayload(getModel("anthropic", "claude-opus-4-7"), { reasoning: "high" }); + + expect(payload.thinking).toEqual({ type: "adaptive" }); + expect(payload.output_config).toEqual({ effort: "high" }); + }); + + it("maps xhigh reasoning to effort=xhigh for Claude Opus 4.7", async () => { + const payload = await capturePayload(getModel("anthropic", "claude-opus-4-7"), { reasoning: "xhigh" }); + + expect(payload.thinking).toEqual({ type: "adaptive" }); + expect(payload.output_config).toEqual({ effort: "xhigh" }); + }); }); describe.skipIf(!process.env.ANTHROPIC_API_KEY)("Anthropic thinking disable E2E", () => { diff --git a/packages/ai/test/bedrock-thinking-payload.test.ts b/packages/ai/test/bedrock-thinking-payload.test.ts new file mode 100644 index 00000000..b9f7c86f --- /dev/null +++ b/packages/ai/test/bedrock-thinking-payload.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "vitest"; +import { getModel } from "../src/models.js"; +import { streamBedrock } from "../src/providers/amazon-bedrock.js"; +import type { Context, Model, SimpleStreamOptions } from "../src/types.js"; + +interface BedrockThinkingPayload { + additionalModelRequestFields?: { + thinking?: { type: string; budget_tokens?: number }; + output_config?: { effort?: string }; + anthropic_beta?: string[]; + }; +} + +function makeContext(): Context { + return { + messages: [{ role: "user", content: "Hello", timestamp: Date.now() }], + }; +} + +async function capturePayload( + model: Model<"bedrock-converse-stream">, + options?: SimpleStreamOptions, +): Promise { + let capturedPayload: BedrockThinkingPayload | undefined; + const s = streamBedrock(model, makeContext(), { + ...options, + reasoning: options?.reasoning ?? "high", + signal: AbortSignal.abort(), + onPayload: (payload) => { + capturedPayload = payload as BedrockThinkingPayload; + return payload; + }, + }); + + for await (const event of s) { + if (event.type === "error") { + break; + } + } + + if (!capturedPayload) { + throw new Error("Expected Bedrock payload to be captured before request abort"); + } + + return capturedPayload; +} + +describe("Bedrock thinking payload", () => { + it("uses adaptive thinking for Claude Opus 4.7 when reasoning is enabled", async () => { + const baseModel = getModel("amazon-bedrock", "global.anthropic.claude-opus-4-6-v1"); + const model: Model<"bedrock-converse-stream"> = { + ...baseModel, + id: "global.anthropic.claude-opus-4-7-v1", + name: "Claude Opus 4.7 (Global)", + }; + + const payload = await capturePayload(model); + + expect(payload.additionalModelRequestFields?.thinking).toEqual({ type: "adaptive" }); + expect(payload.additionalModelRequestFields?.output_config).toEqual({ effort: "high" }); + expect(payload.additionalModelRequestFields?.anthropic_beta).toBeUndefined(); + }); + + it("maps xhigh reasoning to effort=xhigh for Claude Opus 4.7", async () => { + const baseModel = getModel("amazon-bedrock", "global.anthropic.claude-opus-4-6-v1"); + const model: Model<"bedrock-converse-stream"> = { + ...baseModel, + id: "global.anthropic.claude-opus-4-7-v1", + name: "Claude Opus 4.7 (Global)", + }; + + const payload = await capturePayload(model, { reasoning: "xhigh" }); + + expect(payload.additionalModelRequestFields?.thinking).toEqual({ type: "adaptive" }); + expect(payload.additionalModelRequestFields?.output_config).toEqual({ effort: "xhigh" }); + expect(payload.additionalModelRequestFields?.anthropic_beta).toBeUndefined(); + }); +}); diff --git a/packages/ai/test/supports-xhigh.test.ts b/packages/ai/test/supports-xhigh.test.ts index b3e5aaf6..c0c4db0c 100644 --- a/packages/ai/test/supports-xhigh.test.ts +++ b/packages/ai/test/supports-xhigh.test.ts @@ -8,6 +8,12 @@ describe("supportsXhigh", () => { expect(supportsXhigh(model!)).toBe(true); }); + it("returns true for Anthropic Opus 4.7 on anthropic-messages API", () => { + const model = getModel("anthropic", "claude-opus-4-7"); + expect(model).toBeDefined(); + expect(supportsXhigh(model!)).toBe(true); + }); + it("returns false for non-Opus Anthropic models", () => { const model = getModel("anthropic", "claude-sonnet-4-5"); expect(model).toBeDefined(); From 54bbaece5ccc152dae393e57a4665381396d7139 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 19:58:40 +0200 Subject: [PATCH 10/38] fix(ai): rebuild models --- packages/ai/src/models.generated.ts | 91 ++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index f647631c..1104fa13 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -260,6 +260,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "anthropic.claude-opus-4-7": { + id: "anthropic.claude-opus-4-7", + name: "Claude Opus 4.7", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "anthropic.claude-sonnet-4-20250514-v1:0": { id: "anthropic.claude-sonnet-4-20250514-v1:0", name: "Claude Sonnet 4", @@ -413,6 +430,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "eu.anthropic.claude-opus-4-7": { + id: "eu.anthropic.claude-opus-4-7", + name: "Claude Opus 4.7 (EU)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "eu.anthropic.claude-sonnet-4-20250514-v1:0": { id: "eu.anthropic.claude-sonnet-4-20250514-v1:0", name: "Claude Sonnet 4 (EU)", @@ -515,6 +549,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "global.anthropic.claude-opus-4-7": { + id: "global.anthropic.claude-opus-4-7", + name: "Claude Opus 4.7 (Global)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "global.anthropic.claude-sonnet-4-20250514-v1:0": { id: "global.anthropic.claude-sonnet-4-20250514-v1:0", name: "Claude Sonnet 4 (Global)", @@ -1348,6 +1399,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"bedrock-converse-stream">, + "us.anthropic.claude-opus-4-7": { + id: "us.anthropic.claude-opus-4-7", + name: "Claude Opus 4.7 (US)", + api: "bedrock-converse-stream", + provider: "amazon-bedrock", + baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"bedrock-converse-stream">, "us.anthropic.claude-sonnet-4-20250514-v1:0": { id: "us.anthropic.claude-sonnet-4-20250514-v1:0", name: "Claude Sonnet 4 (US)", @@ -6210,6 +6278,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 128000, } satisfies Model<"anthropic-messages">, + "claude-opus-4-7": { + id: "claude-opus-4-7", + name: "Claude Opus 4.7", + api: "anthropic-messages", + provider: "opencode", + baseUrl: "https://opencode.ai/zen", + reasoning: true, + input: ["text", "image"], + cost: { + input: 5, + output: 25, + cacheRead: 0.5, + cacheWrite: 6.25, + }, + contextWindow: 1000000, + maxTokens: 128000, + } satisfies Model<"anthropic-messages">, "claude-sonnet-4": { id: "claude-sonnet-4", name: "Claude Sonnet 4", @@ -13669,15 +13754,15 @@ export const MODELS = { provider: "vercel-ai-gateway", baseUrl: "https://ai-gateway.vercel.sh", reasoning: true, - input: ["text"], + input: ["text", "image"], cost: { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0, }, - contextWindow: 202800, - maxTokens: 64000, + contextWindow: 202752, + maxTokens: 202752, } satisfies Model<"anthropic-messages">, "zai/glm-5v-turbo": { id: "zai/glm-5v-turbo", From 5b84152b17ee51a0eb7c13d4e53ba19f36375f26 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 20:00:54 +0200 Subject: [PATCH 11/38] revert(tui): temporarily undo Zellij Kitty-query bypass This reverts 3929e0c18183c1e1541e7ade9e735bef173f599c. The Zellij-specific Kitty-query skip regresses Shift+Enter newline handling in Zellij. Restoring the previous behavior for now while we work on a safer fix that preserves both Alt and modified Enter handling.\n\nContext: https://github.com/badlogic/pi-mono/issues/3259 --- packages/coding-agent/CHANGELOG.md | 1 - packages/coding-agent/docs/terminal-setup.md | 6 -- packages/tui/CHANGELOG.md | 6 -- packages/tui/src/terminal.ts | 11 --- packages/tui/test/terminal.test.ts | 76 -------------------- 5 files changed, 100 deletions(-) delete mode 100644 packages/tui/test/terminal.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d10a3806..8b6787f3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -49,7 +49,6 @@ - Fixed the `plan-mode` example extension to allow `eza` in the read-only bash allowlist instead of the deprecated `exa` command ([#3240](https://github.com/badlogic/pi-mono/pull/3240) by [@rwachtler](https://github.com/rwachtler)) - Fixed `google-vertex` API key resolution to treat `gcp-vertex-credentials` as an Application Default Credentials marker instead of a literal API key, so marker-based setups correctly fall back to ADC ([#3221](https://github.com/badlogic/pi-mono/pull/3221) by [@deepkilo](https://github.com/deepkilo)) - Fixed RPC `prompt` to wait for prompt preflight success before emitting its single authoritative response, while still treating handled and queued prompts as success ([#3049](https://github.com/badlogic/pi-mono/issues/3049)) -- Fixed Alt keybindings inside Zellij by skipping the Kitty keyboard protocol query there and enabling xterm `modifyOtherKeys` mode 2 directly ([#3163](https://github.com/badlogic/pi-mono/issues/3163)) - Fixed `/scoped-models` reordering to propagate into the `/model` scoped tab, preserving the user-defined scoped model order instead of re-sorting it ([#3217](https://github.com/badlogic/pi-mono/issues/3217)) - Fixed `session_shutdown` to fire on `SIGHUP` and `SIGTERM` in interactive, print, and RPC modes so extensions can run shutdown cleanup on those signal-driven exits ([#3212](https://github.com/badlogic/pi-mono/issues/3212)) - Fixed screenshot path parsing to handle lower case am/pm in macOS screenshot filenames ([#3194](https://github.com/badlogic/pi-mono/pull/3194) by [@jay-aye-see-kay](https://github.com/jay-aye-see-kay)) diff --git a/packages/coding-agent/docs/terminal-setup.md b/packages/coding-agent/docs/terminal-setup.md index fd80e231..74b0df2a 100644 --- a/packages/coding-agent/docs/terminal-setup.md +++ b/packages/coding-agent/docs/terminal-setup.md @@ -32,12 +32,6 @@ If you want `Shift+Enter` to keep working in tmux via that remap, add `ctrl+j` t } ``` -## Zellij - -Pi detects Zellij automatically and skips the Kitty keyboard protocol query there. -Zellij currently forwards that query to the outer terminal but still sends Alt as legacy ESC-prefixed sequences, which can break Alt keybindings if applications enable Kitty mode. -Pi uses xterm `modifyOtherKeys` mode 2 inside Zellij instead, so no extra Zellij config is required. - ## WezTerm Create `~/.wezterm.lua`: diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 12185e97..9f3e7221 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -8,12 +8,6 @@ - Fixed markdown strikethrough parsing to require strict double-tilde delimiters (`~~text~~`) with non-whitespace boundaries, preventing accidental strikethrough from loose tilde usage. -## [0.67.3] - 2026-04-15 - -### Fixed - -- Fixed Alt keybindings inside Zellij by skipping the Kitty keyboard protocol query there and enabling xterm `modifyOtherKeys` mode 2 directly ([#3163](https://github.com/badlogic/pi-mono/issues/3163)) - ## [0.67.2] - 2026-04-14 ### Added diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts index 1ef9b451..d98d1954 100644 --- a/packages/tui/src/terminal.ts +++ b/packages/tui/src/terminal.ts @@ -184,17 +184,6 @@ export class ProcessTerminal implements Terminal { private queryAndEnableKittyProtocol(): void { this.setupStdinBuffer(); process.stdin.on("data", this.stdinDataHandler!); - - // Zellij forwards the Kitty query to the outer terminal, which can make - // Pi enable its Kitty parser even though Zellij still sends Alt as - // legacy ESC-prefixed sequences. Skip the Kitty query there and use - // modifyOtherKeys directly instead. - if (process.env.ZELLIJ) { - process.stdout.write("\x1b[>4;2m"); - this._modifyOtherKeysActive = true; - return; - } - process.stdout.write("\x1b[?u"); setTimeout(() => { if (!this._kittyProtocolActive && !this._modifyOtherKeysActive) { diff --git a/packages/tui/test/terminal.test.ts b/packages/tui/test/terminal.test.ts deleted file mode 100644 index 77ecaf83..00000000 --- a/packages/tui/test/terminal.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import assert from "node:assert"; -import { describe, it } from "node:test"; -import { ProcessTerminal } from "../src/terminal.js"; - -function withEnv(name: string, value: string | undefined, fn: () => void): void { - const previous = process.env[name]; - if (value === undefined) delete process.env[name]; - else process.env[name] = value; - try { - fn(); - } finally { - if (previous === undefined) delete process.env[name]; - else process.env[name] = previous; - } -} - -describe("ProcessTerminal", () => { - it("should skip the Kitty query inside Zellij and enable modifyOtherKeys immediately", () => { - const terminal = new ProcessTerminal(); - const writes: string[] = []; - const stdinOnCalls: Array<{ event: string | symbol; listener: (...args: unknown[]) => void }> = []; - const stdinRemoveCalls: Array<{ event: string | symbol; listener: (...args: unknown[]) => void }> = []; - const stdoutRemoveCalls: Array<{ event: string | symbol; listener: (...args: unknown[]) => void }> = []; - - const originalStdoutWrite = process.stdout.write; - const originalStdinOn = process.stdin.on; - const originalStdinRemoveListener = process.stdin.removeListener; - const originalStdinPause = process.stdin.pause; - const originalStdoutRemoveListener = process.stdout.removeListener; - - process.stdout.write = ((chunk: string | Uint8Array) => { - writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8")); - return true; - }) as typeof process.stdout.write; - process.stdin.on = ((event: string | symbol, listener: (...args: unknown[]) => void) => { - stdinOnCalls.push({ event, listener }); - return process.stdin; - }) as typeof process.stdin.on; - process.stdin.removeListener = ((event: string | symbol, listener: (...args: unknown[]) => void) => { - stdinRemoveCalls.push({ event, listener }); - return process.stdin; - }) as typeof process.stdin.removeListener; - process.stdin.pause = (() => process.stdin) as typeof process.stdin.pause; - process.stdout.removeListener = ((event: string | symbol, listener: (...args: unknown[]) => void) => { - stdoutRemoveCalls.push({ event, listener }); - return process.stdout; - }) as typeof process.stdout.removeListener; - - try { - withEnv("ZELLIJ", "1", () => { - ( - terminal as unknown as { - queryAndEnableKittyProtocol(): void; - } - ).queryAndEnableKittyProtocol(); - }); - - assert.deepStrictEqual(writes, ["\x1b[>4;2m"]); - assert.strictEqual(stdinOnCalls.length, 1); - assert.strictEqual(stdinOnCalls[0]?.event, "data"); - - terminal.stop(); - - assert.deepStrictEqual(writes, ["\x1b[>4;2m", "\x1b[?2004l", "\x1b[>4;0m"]); - assert.strictEqual(stdinRemoveCalls.length, 1); - assert.strictEqual(stdinRemoveCalls[0]?.event, "data"); - assert.strictEqual(stdoutRemoveCalls.length, 0); - } finally { - process.stdout.write = originalStdoutWrite; - process.stdin.on = originalStdinOn; - process.stdin.removeListener = originalStdinRemoveListener; - process.stdin.pause = originalStdinPause; - process.stdout.removeListener = originalStdoutRemoveListener; - } - }); -}); From 85ff56a0b496f895dcc6f32b118b7b96d0a7ea37 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 20:04:24 +0200 Subject: [PATCH 12/38] docs(changelog): add missing unreleased entries --- packages/ai/CHANGELOG.md | 4 ++++ packages/coding-agent/CHANGELOG.md | 5 +++++ packages/tui/CHANGELOG.md | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index ee0a280d..9924f973 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Opus 4.7 adaptive thinking configuration across Anthropic and Bedrock providers by recognizing Opus 4.7 adaptive-thinking support and mapping `xhigh` reasoning to provider-supported effort values ([#3286](https://github.com/badlogic/pi-mono/pull/3286) by [@markusylisiurunen](https://github.com/markusylisiurunen)) + ## [0.67.4] - 2026-04-16 ### Changed diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 8b6787f3..d862bae3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Fixed + +- Fixed Opus 4.7 adaptive thinking configuration across Anthropic and Bedrock providers by recognizing Opus 4.7 adaptive-thinking support and mapping `xhigh` reasoning to provider-supported effort values ([#3286](https://github.com/badlogic/pi-mono/pull/3286) by [@markusylisiurunen](https://github.com/markusylisiurunen)) +- Fixed Zellij `Shift+Enter` regressions by reverting the Zellij-specific Kitty keyboard query bypass and restoring the previous keyboard negotiation behavior ([#3259](https://github.com/badlogic/pi-mono/issues/3259)) + ## [0.67.4] - 2026-04-16 ### New Features diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 9f3e7221..8cbee984 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Zellij `Shift+Enter` regressions by reverting the Zellij-specific Kitty keyboard query bypass and restoring the previous keyboard negotiation behavior ([#3259](https://github.com/badlogic/pi-mono/issues/3259)) + ## [0.67.4] - 2026-04-16 ### Fixed From 23259e5f194bb2c4ec6693800899deb68e1fc0ad Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 20:05:42 +0200 Subject: [PATCH 13/38] Release v0.67.5 --- package-lock.json | 44 +++++++++---------- packages/agent/CHANGELOG.md | 2 +- packages/agent/package.json | 4 +- packages/ai/CHANGELOG.md | 2 +- packages/ai/package.json | 2 +- packages/coding-agent/CHANGELOG.md | 2 +- .../package-lock.json | 4 +- .../custom-provider-anthropic/package.json | 2 +- .../custom-provider-gitlab-duo/package.json | 2 +- .../custom-provider-qwen-cli/package.json | 2 +- .../extensions/with-deps/package-lock.json | 4 +- .../extensions/with-deps/package.json | 2 +- packages/coding-agent/package.json | 8 ++-- packages/mom/CHANGELOG.md | 2 +- packages/mom/package.json | 8 ++-- packages/pods/package.json | 4 +- packages/tui/CHANGELOG.md | 2 +- packages/tui/package.json | 2 +- packages/web-ui/CHANGELOG.md | 2 +- packages/web-ui/example/package.json | 2 +- packages/web-ui/package.json | 6 +-- 21 files changed, 54 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2462a1ff..16022ac2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8678,10 +8678,10 @@ }, "packages/agent": { "name": "@mariozechner/pi-agent-core", - "version": "0.67.4", + "version": "0.67.5", "license": "MIT", "dependencies": { - "@mariozechner/pi-ai": "^0.67.4" + "@mariozechner/pi-ai": "^0.67.5" }, "devDependencies": { "@types/node": "^24.3.0", @@ -8711,7 +8711,7 @@ }, "packages/ai": { "name": "@mariozechner/pi-ai", - "version": "0.67.4", + "version": "0.67.5", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.90.0", @@ -8759,13 +8759,13 @@ }, "packages/coding-agent": { "name": "@mariozechner/pi-coding-agent", - "version": "0.67.4", + "version": "0.67.5", "license": "MIT", "dependencies": { "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.67.4", - "@mariozechner/pi-ai": "^0.67.4", - "@mariozechner/pi-tui": "^0.67.4", + "@mariozechner/pi-agent-core": "^0.67.5", + "@mariozechner/pi-ai": "^0.67.5", + "@mariozechner/pi-tui": "^0.67.5", "@silvia-odwyer/photon-node": "^0.3.4", "ajv": "^8.17.1", "chalk": "^5.5.0", @@ -8806,22 +8806,22 @@ }, "packages/coding-agent/examples/extensions/custom-provider-anthropic": { "name": "pi-extension-custom-provider-anthropic", - "version": "1.18.4", + "version": "1.18.5", "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } }, "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo": { "name": "pi-extension-custom-provider-gitlab-duo", - "version": "1.18.4" + "version": "1.18.5" }, "packages/coding-agent/examples/extensions/custom-provider-qwen-cli": { "name": "pi-extension-custom-provider-qwen-cli", - "version": "1.17.4" + "version": "1.17.5" }, "packages/coding-agent/examples/extensions/with-deps": { "name": "pi-extension-with-deps", - "version": "1.31.4", + "version": "1.31.5", "dependencies": { "ms": "^2.1.3" }, @@ -8857,13 +8857,13 @@ }, "packages/mom": { "name": "@mariozechner/pi-mom", - "version": "0.67.4", + "version": "0.67.5", "license": "MIT", "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.67.4", - "@mariozechner/pi-ai": "^0.67.4", - "@mariozechner/pi-coding-agent": "^0.67.4", + "@mariozechner/pi-agent-core": "^0.67.5", + "@mariozechner/pi-ai": "^0.67.5", + "@mariozechner/pi-coding-agent": "^0.67.5", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", @@ -8902,10 +8902,10 @@ }, "packages/pods": { "name": "@mariozechner/pi", - "version": "0.67.4", + "version": "0.67.5", "license": "MIT", "dependencies": { - "@mariozechner/pi-agent-core": "^0.67.4", + "@mariozechner/pi-agent-core": "^0.67.5", "chalk": "^5.5.0" }, "bin": { @@ -8918,7 +8918,7 @@ }, "packages/tui": { "name": "@mariozechner/pi-tui", - "version": "0.67.4", + "version": "0.67.5", "license": "MIT", "dependencies": { "@types/mime-types": "^2.1.4", @@ -8965,12 +8965,12 @@ }, "packages/web-ui": { "name": "@mariozechner/pi-web-ui", - "version": "0.67.4", + "version": "0.67.5", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.67.4", - "@mariozechner/pi-tui": "^0.67.4", + "@mariozechner/pi-ai": "^0.67.5", + "@mariozechner/pi-tui": "^0.67.5", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", @@ -8991,7 +8991,7 @@ }, "packages/web-ui/example": { "name": "pi-web-ui-example", - "version": "1.55.4", + "version": "1.55.5", "dependencies": { "@mariozechner/mini-lit": "^0.2.0", "@mariozechner/pi-ai": "file:../../ai", diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 65e83472..28725090 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.5] - 2026-04-16 ## [0.67.4] - 2026-04-16 diff --git a/packages/agent/package.json b/packages/agent/package.json index c1ac1eed..9625f167 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-agent-core", - "version": "0.67.4", + "version": "0.67.5", "description": "General-purpose agent with transport abstraction, state management, and attachment support", "type": "module", "main": "./dist/index.js", @@ -17,7 +17,7 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@mariozechner/pi-ai": "^0.67.4" + "@mariozechner/pi-ai": "^0.67.5" }, "keywords": [ "ai", diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 9924f973..3ca9cd7b 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/ai/package.json b/packages/ai/package.json index f31cc5bd..c68ca28b 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-ai", - "version": "0.67.4", + "version": "0.67.5", "description": "Unified LLM API with automatic model discovery and provider configuration", "type": "module", "main": "./dist/index.js", diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index d862bae3..69c8ea6f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json index 437767d7..4d3be44c 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-extension-custom-provider", - "version": "1.18.4", + "version": "1.18.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-extension-custom-provider", - "version": "1.18.4", + "version": "1.18.5", "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json index 38b3638c..60695b62 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-anthropic", "private": true, - "version": "1.18.4", + "version": "1.18.5", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json index 1dbaf8da..e59edd43 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-gitlab-duo", "private": true, - "version": "1.18.4", + "version": "1.18.5", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json b/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json index 985b7fb9..f2f82bca 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-qwen-cli", "private": true, - "version": "1.17.4", + "version": "1.17.5", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/with-deps/package-lock.json b/packages/coding-agent/examples/extensions/with-deps/package-lock.json index 031cef60..48804236 100644 --- a/packages/coding-agent/examples/extensions/with-deps/package-lock.json +++ b/packages/coding-agent/examples/extensions/with-deps/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-extension-with-deps", - "version": "1.31.4", + "version": "1.31.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-extension-with-deps", - "version": "1.31.4", + "version": "1.31.5", "dependencies": { "ms": "^2.1.3" }, diff --git a/packages/coding-agent/examples/extensions/with-deps/package.json b/packages/coding-agent/examples/extensions/with-deps/package.json index 8669a360..0280528c 100644 --- a/packages/coding-agent/examples/extensions/with-deps/package.json +++ b/packages/coding-agent/examples/extensions/with-deps/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-with-deps", "private": true, - "version": "1.31.4", + "version": "1.31.5", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index a1cf2b1e..279bf1b6 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-coding-agent", - "version": "0.67.4", + "version": "0.67.5", "description": "Coding agent CLI with read, bash, edit, write tools and session management", "type": "module", "piConfig": { @@ -40,9 +40,9 @@ }, "dependencies": { "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.67.4", - "@mariozechner/pi-ai": "^0.67.4", - "@mariozechner/pi-tui": "^0.67.4", + "@mariozechner/pi-agent-core": "^0.67.5", + "@mariozechner/pi-ai": "^0.67.5", + "@mariozechner/pi-tui": "^0.67.5", "@silvia-odwyer/photon-node": "^0.3.4", "ajv": "^8.17.1", "chalk": "^5.5.0", diff --git a/packages/mom/CHANGELOG.md b/packages/mom/CHANGELOG.md index b1185bed..24f0fe5f 100644 --- a/packages/mom/CHANGELOG.md +++ b/packages/mom/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.5] - 2026-04-16 ## [0.67.4] - 2026-04-16 diff --git a/packages/mom/package.json b/packages/mom/package.json index 7743a9cc..54c1843b 100644 --- a/packages/mom/package.json +++ b/packages/mom/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-mom", - "version": "0.67.4", + "version": "0.67.5", "description": "Slack bot that delegates messages to the pi coding agent", "type": "module", "bin": { @@ -20,9 +20,9 @@ }, "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.67.4", - "@mariozechner/pi-ai": "^0.67.4", - "@mariozechner/pi-coding-agent": "^0.67.4", + "@mariozechner/pi-agent-core": "^0.67.5", + "@mariozechner/pi-ai": "^0.67.5", + "@mariozechner/pi-coding-agent": "^0.67.5", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", diff --git a/packages/pods/package.json b/packages/pods/package.json index eaae17d0..8d45b394 100644 --- a/packages/pods/package.json +++ b/packages/pods/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi", - "version": "0.67.4", + "version": "0.67.5", "description": "CLI tool for managing vLLM deployments on GPU pods", "type": "module", "bin": { @@ -33,7 +33,7 @@ "node": ">=20.0.0" }, "dependencies": { - "@mariozechner/pi-agent-core": "^0.67.4", + "@mariozechner/pi-agent-core": "^0.67.5", "chalk": "^5.5.0" }, "devDependencies": {} diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 8cbee984..c41a6c9c 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/tui/package.json b/packages/tui/package.json index 538c7016..4764c886 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-tui", - "version": "0.67.4", + "version": "0.67.5", "description": "Terminal User Interface library with differential rendering for efficient text-based applications", "type": "module", "main": "dist/index.js", diff --git a/packages/web-ui/CHANGELOG.md b/packages/web-ui/CHANGELOG.md index 550b72c7..3d9a671a 100644 --- a/packages/web-ui/CHANGELOG.md +++ b/packages/web-ui/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.5] - 2026-04-16 ## [0.67.4] - 2026-04-16 diff --git a/packages/web-ui/example/package.json b/packages/web-ui/example/package.json index 42a144ea..ed460ed5 100644 --- a/packages/web-ui/example/package.json +++ b/packages/web-ui/example/package.json @@ -1,6 +1,6 @@ { "name": "pi-web-ui-example", - "version": "1.55.4", + "version": "1.55.5", "private": true, "type": "module", "scripts": { diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index 0e1d03ec..f7f0f423 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-web-ui", - "version": "0.67.4", + "version": "0.67.5", "description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai", "type": "module", "main": "dist/index.js", @@ -18,8 +18,8 @@ }, "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.67.4", - "@mariozechner/pi-tui": "^0.67.4", + "@mariozechner/pi-ai": "^0.67.5", + "@mariozechner/pi-tui": "^0.67.5", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", From aa78fa91fe7974718b6b54bff3cfb0f3f7bb9fda Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 20:06:43 +0200 Subject: [PATCH 14/38] Add [Unreleased] section for next cycle --- packages/agent/CHANGELOG.md | 2 ++ packages/ai/CHANGELOG.md | 2 ++ packages/ai/src/models.generated.ts | 18 +++++++++--------- packages/coding-agent/CHANGELOG.md | 2 ++ packages/mom/CHANGELOG.md | 2 ++ packages/tui/CHANGELOG.md | 2 ++ packages/web-ui/CHANGELOG.md | 2 ++ 7 files changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 28725090..a95cea40 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.5] - 2026-04-16 ## [0.67.4] - 2026-04-16 diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 3ca9cd7b..e7c7ad1b 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 1104fa13..84077d1a 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -9898,7 +9898,7 @@ export const MODELS = { input: 0.26, output: 0.78, cacheRead: 0.052000000000000005, - cacheWrite: 0, + cacheWrite: 0.325, }, contextWindow: 1000000, maxTokens: 32768, @@ -9915,7 +9915,7 @@ export const MODELS = { input: 0.26, output: 0.78, cacheRead: 0, - cacheWrite: 0, + cacheWrite: 0.325, }, contextWindow: 1000000, maxTokens: 32768, @@ -9932,7 +9932,7 @@ export const MODELS = { input: 0.26, output: 0.78, cacheRead: 0, - cacheWrite: 0, + cacheWrite: 0.325, }, contextWindow: 1000000, maxTokens: 32768, @@ -10170,7 +10170,7 @@ export const MODELS = { input: 0.195, output: 0.975, cacheRead: 0.039, - cacheWrite: 0, + cacheWrite: 0.24375, }, contextWindow: 1000000, maxTokens: 65536, @@ -10204,7 +10204,7 @@ export const MODELS = { input: 0.65, output: 3.25, cacheRead: 0.13, - cacheWrite: 0, + cacheWrite: 0.8125, }, contextWindow: 1000000, maxTokens: 65536, @@ -10238,7 +10238,7 @@ export const MODELS = { input: 0.78, output: 3.9, cacheRead: 0.156, - cacheWrite: 0, + cacheWrite: 0.975, }, contextWindow: 262144, maxTokens: 32768, @@ -10527,7 +10527,7 @@ export const MODELS = { input: 0.065, output: 0.26, cacheRead: 0, - cacheWrite: 0, + cacheWrite: 0.08125, }, contextWindow: 1000000, maxTokens: 65536, @@ -10544,7 +10544,7 @@ export const MODELS = { input: 0.26, output: 1.56, cacheRead: 0, - cacheWrite: 0, + cacheWrite: 0.325, }, contextWindow: 1000000, maxTokens: 65536, @@ -10561,7 +10561,7 @@ export const MODELS = { input: 0.325, output: 1.95, cacheRead: 0, - cacheWrite: 0, + cacheWrite: 0.40625, }, contextWindow: 1000000, maxTokens: 65536, diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 69c8ea6f..c1747b16 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/mom/CHANGELOG.md b/packages/mom/CHANGELOG.md index 24f0fe5f..43c728e0 100644 --- a/packages/mom/CHANGELOG.md +++ b/packages/mom/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.5] - 2026-04-16 ## [0.67.4] - 2026-04-16 diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index c41a6c9c..0d46e4ba 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/web-ui/CHANGELOG.md b/packages/web-ui/CHANGELOG.md index 3d9a671a..e812f1e2 100644 --- a/packages/web-ui/CHANGELOG.md +++ b/packages/web-ui/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.5] - 2026-04-16 ## [0.67.4] - 2026-04-16 From d131fcd4baa867485b371b0ce76259a3f1df13e9 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 20:27:04 +0200 Subject: [PATCH 15/38] feat(coding-agent): add after_provider_response hook closes #3128 --- packages/agent/src/agent.ts | 4 ++++ packages/ai/CHANGELOG.md | 8 ++------ packages/ai/src/providers/amazon-bedrock.ts | 7 +++++++ packages/ai/src/providers/anthropic.ts | 6 +++++- .../src/providers/azure-openai-responses.ts | 9 +++++---- packages/ai/src/providers/faux.ts | 1 + .../ai/src/providers/google-gemini-cli.ts | 9 +++++++++ .../src/providers/openai-codex-responses.ts | 13 +++++-------- .../ai/src/providers/openai-completions.ts | 6 +++++- packages/ai/src/providers/openai-responses.ts | 9 +++++---- packages/ai/src/providers/simple-options.ts | 1 + packages/ai/src/types.ts | 10 ++++++++++ packages/ai/src/utils/headers.ts | 7 +++++++ packages/coding-agent/docs/extensions.md | 19 ++++++++++++++++++- .../examples/extensions/provider-payload.ts | 4 ++++ .../coding-agent/src/core/extensions/index.ts | 1 + .../coding-agent/src/core/extensions/types.ts | 9 +++++++++ packages/coding-agent/src/core/sdk.ts | 11 +++++++++++ packages/coding-agent/test/suite/harness.ts | 11 +++++++++++ 19 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 packages/ai/src/utils/headers.ts diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index b7d69546..1017239b 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -98,6 +98,7 @@ export interface AgentOptions { streamFn?: StreamFn; getApiKey?: (provider: string) => Promise | string | undefined; onPayload?: SimpleStreamOptions["onPayload"]; + onResponse?: SimpleStreamOptions["onResponse"]; beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise; afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise; steeringMode?: QueueMode; @@ -165,6 +166,7 @@ export class Agent { public streamFn: StreamFn; public getApiKey?: (provider: string) => Promise | string | undefined; public onPayload?: SimpleStreamOptions["onPayload"]; + public onResponse?: SimpleStreamOptions["onResponse"]; public beforeToolCall?: ( context: BeforeToolCallContext, signal?: AbortSignal, @@ -192,6 +194,7 @@ export class Agent { this.streamFn = options.streamFn ?? streamSimple; this.getApiKey = options.getApiKey; this.onPayload = options.onPayload; + this.onResponse = options.onResponse; this.beforeToolCall = options.beforeToolCall; this.afterToolCall = options.afterToolCall; this.steeringQueue = new PendingMessageQueue(options.steeringMode ?? "one-at-a-time"); @@ -411,6 +414,7 @@ export class Agent { reasoning: this._state.thinkingLevel === "off" ? undefined : this._state.thinkingLevel, sessionId: this.sessionId, onPayload: this.onPayload, + onResponse: this.onResponse, transport: this.transport, thinkingBudgets: this.thinkingBudgets, maxRetryDelayMs: this.maxRetryDelayMs, diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index e7c7ad1b..39cbfe11 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,13 +2,9 @@ ## [Unreleased] -## [0.67.5] - 2026-04-16 +### Added -### Fixed - -- Fixed Opus 4.7 adaptive thinking configuration across Anthropic and Bedrock providers by recognizing Opus 4.7 adaptive-thinking support and mapping `xhigh` reasoning to provider-supported effort values ([#3286](https://github.com/badlogic/pi-mono/pull/3286) by [@markusylisiurunen](https://github.com/markusylisiurunen)) - -## [0.67.4] - 2026-04-16 +- Added `onResponse` to `StreamOptions` so callers can inspect provider HTTP status and headers after each response arrives and before the response stream is consumed ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) ### Changed diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index 00333f0c..f72356b5 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -168,6 +168,13 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt const command = new ConverseStreamCommand(commandInput); const response = await client.send(command, { abortSignal: options.signal }); + if (response.$metadata.httpStatusCode !== undefined) { + const responseHeaders: Record = {}; + if (response.$metadata.requestId) { + responseHeaders["x-amzn-requestid"] = response.$metadata.requestId; + } + await options?.onResponse?.({ status: response.$metadata.httpStatusCode, headers: responseHeaders }, model); + } for await (const item of response.stream!) { if (item.messageStart) { diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 1741b400..1d6ee9bc 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -26,6 +26,7 @@ import type { ToolResultMessage, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; +import { headersToRecord } from "../utils/headers.js"; import { parseStreamingJson } from "../utils/json-parse.js"; import { sanitizeSurrogates } from "../utils/sanitize-unicode.js"; @@ -258,7 +259,10 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti if (nextParams !== undefined) { params = nextParams as MessageCreateParamsStreaming; } - const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal }); + const { data: anthropicStream, response } = await client.messages + .stream({ ...params, stream: true }, { signal: options?.signal }) + .withResponse(); + await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model); stream.push({ type: "start", partial: output }); type Block = (ThinkingContent | TextContent | (ToolCall & { partialJson: string })) & { index: number }; diff --git a/packages/ai/src/providers/azure-openai-responses.ts b/packages/ai/src/providers/azure-openai-responses.ts index 9dc5261e..b9981935 100644 --- a/packages/ai/src/providers/azure-openai-responses.ts +++ b/packages/ai/src/providers/azure-openai-responses.ts @@ -12,6 +12,7 @@ import type { StreamOptions, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; +import { headersToRecord } from "../utils/headers.js"; import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js"; import { buildBaseOptions, clampReasoning } from "./simple-options.js"; @@ -90,10 +91,10 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses" if (nextParams !== undefined) { params = nextParams as ResponseCreateParamsStreaming; } - const openaiStream = await client.responses.create( - params, - options?.signal ? { signal: options.signal } : undefined, - ); + const { data: openaiStream, response } = await client.responses + .create(params, options?.signal ? { signal: options.signal } : undefined) + .withResponse(); + await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model); stream.push({ type: "start", partial: output }); await processResponsesStream(openaiStream, output, stream, model); diff --git a/packages/ai/src/providers/faux.ts b/packages/ai/src/providers/faux.ts index 328fd87f..7bba1b72 100644 --- a/packages/ai/src/providers/faux.ts +++ b/packages/ai/src/providers/faux.ts @@ -435,6 +435,7 @@ export function registerFauxProvider(options: RegisterFauxProviderOptions = {}): queueMicrotask(async () => { try { + await streamOptions?.onResponse?.({ status: 200, headers: {} }, requestModel); if (!step) { let message = createErrorMessage( new Error("No more faux responses queued"), diff --git a/packages/ai/src/providers/google-gemini-cli.ts b/packages/ai/src/providers/google-gemini-cli.ts index 259ca069..fa6f98f1 100644 --- a/packages/ai/src/providers/google-gemini-cli.ts +++ b/packages/ai/src/providers/google-gemini-cli.ts @@ -21,6 +21,7 @@ import type { ToolCall, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; +import { headersToRecord } from "../utils/headers.js"; import { sanitizeSurrogates } from "../utils/sanitize-unicode.js"; import { convertMessages, @@ -408,6 +409,10 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli", GoogleGe body: requestBodyJson, signal: options?.signal, }); + await options?.onResponse?.( + { status: response.status, headers: headersToRecord(response.headers) }, + model, + ); if (response.ok) { break; // Success, exit retry loop @@ -757,6 +762,10 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli", GoogleGe body: requestBodyJson, signal: options?.signal, }); + await options?.onResponse?.( + { status: currentResponse.status, headers: headersToRecord(currentResponse.headers) }, + model, + ); if (!currentResponse.ok) { const retryErrorText = await currentResponse.text(); diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/providers/openai-codex-responses.ts index 4cca39b7..248443ec 100644 --- a/packages/ai/src/providers/openai-codex-responses.ts +++ b/packages/ai/src/providers/openai-codex-responses.ts @@ -33,6 +33,7 @@ import type { Usage, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; +import { headersToRecord } from "../utils/headers.js"; import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js"; import { buildBaseOptions, clampReasoning } from "./simple-options.js"; @@ -214,6 +215,10 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses" body: bodyJson, signal: options?.signal, }); + await options?.onResponse?.( + { status: response.status, headers: headersToRecord(response.headers) }, + model, + ); if (response.ok) { break; @@ -529,14 +534,6 @@ function getWebSocketConstructor(): WebSocketConstructor | null { return ctor as unknown as WebSocketConstructor; } -function headersToRecord(headers: Headers): Record { - const out: Record = {}; - for (const [key, value] of headers.entries()) { - out[key] = value; - } - return out; -} - function getWebSocketReadyState(socket: WebSocketLike): number | undefined { const readyState = (socket as { readyState?: unknown }).readyState; return typeof readyState === "number" ? readyState : undefined; diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index 6656540f..e2ab39b1 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -27,6 +27,7 @@ import type { ToolResultMessage, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; +import { headersToRecord } from "../utils/headers.js"; import { parseStreamingJson } from "../utils/json-parse.js"; import { sanitizeSurrogates } from "../utils/sanitize-unicode.js"; import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js"; @@ -91,7 +92,10 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions", OpenA if (nextParams !== undefined) { params = nextParams as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming; } - const openaiStream = await client.chat.completions.create(params, { signal: options?.signal }); + const { data: openaiStream, response } = await client.chat.completions + .create(params, { signal: options?.signal }) + .withResponse(); + await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model); stream.push({ type: "start", partial: output }); let currentBlock: TextContent | ThinkingContent | (ToolCall & { partialArgs?: string }) | null = null; diff --git a/packages/ai/src/providers/openai-responses.ts b/packages/ai/src/providers/openai-responses.ts index 35bfeffe..cc99bbb0 100644 --- a/packages/ai/src/providers/openai-responses.ts +++ b/packages/ai/src/providers/openai-responses.ts @@ -14,6 +14,7 @@ import type { Usage, } from "../types.js"; import { AssistantMessageEventStream } from "../utils/event-stream.js"; +import { headersToRecord } from "../utils/headers.js"; import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js"; import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js"; import { buildBaseOptions, clampReasoning } from "./simple-options.js"; @@ -96,10 +97,10 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIRes if (nextParams !== undefined) { params = nextParams as ResponseCreateParamsStreaming; } - const openaiStream = await client.responses.create( - params, - options?.signal ? { signal: options.signal } : undefined, - ); + const { data: openaiStream, response } = await client.responses + .create(params, options?.signal ? { signal: options.signal } : undefined) + .withResponse(); + await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model); stream.push({ type: "start", partial: output }); await processResponsesStream(openaiStream, output, stream, model, { diff --git a/packages/ai/src/providers/simple-options.ts b/packages/ai/src/providers/simple-options.ts index 71c15847..6e478e64 100644 --- a/packages/ai/src/providers/simple-options.ts +++ b/packages/ai/src/providers/simple-options.ts @@ -10,6 +10,7 @@ export function buildBaseOptions(model: Model, options?: SimpleStreamOption sessionId: options?.sessionId, headers: options?.headers, onPayload: options?.onPayload, + onResponse: options?.onResponse, maxRetryDelayMs: options?.maxRetryDelayMs, metadata: options?.metadata, }; diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index 3c2a0d1a..79281c6c 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -57,6 +57,11 @@ export type CacheRetention = "none" | "short" | "long"; export type Transport = "sse" | "websocket" | "auto"; +export interface ProviderResponse { + status: number; + headers: Record; +} + export interface StreamOptions { temperature?: number; maxTokens?: number; @@ -83,6 +88,11 @@ export interface StreamOptions { * Return undefined to keep the payload unchanged. */ onPayload?: (payload: unknown, model: Model) => unknown | undefined | Promise; + /** + * Optional callback invoked after an HTTP response is received and before + * its body stream is consumed. + */ + onResponse?: (response: ProviderResponse, model: Model) => void | Promise; /** * Optional custom HTTP headers to include in API requests. * Merged with provider defaults; can override default headers. diff --git a/packages/ai/src/utils/headers.ts b/packages/ai/src/utils/headers.ts new file mode 100644 index 00000000..fae8a036 --- /dev/null +++ b/packages/ai/src/utils/headers.ts @@ -0,0 +1,7 @@ +export function headersToRecord(headers: Headers): Record { + const result: Record = {}; + for (const [key, value] of headers.entries()) { + result[key] = value; + } + return result; +} diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index af1a2513..7175bab4 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -246,6 +246,7 @@ user sends prompt ──────────────────── │ ├─► turn_start │ │ │ ├─► context (can modify messages) │ │ │ ├─► before_provider_request (can inspect or replace payload) + │ ├─► after_provider_response (status + headers, before stream consume) │ │ │ │ │ │ LLM responds, may call tools: │ │ │ │ ├─► tool_execution_start │ │ @@ -534,6 +535,22 @@ pi.on("before_provider_request", (event, ctx) => { This is mainly useful for debugging provider serialization and cache behavior. +#### after_provider_response + +Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order. + +```typescript +pi.on("after_provider_response", (event, ctx) => { + // event.status - HTTP status code + // event.headers - normalized response headers + if (event.status === 429) { + console.log("rate limited", event.headers["retry-after"]); + } +}); +``` + +Header availability depends on provider and transport. Providers that abstract HTTP responses may not expose headers. + ### Model Events #### model_select @@ -2231,7 +2248,7 @@ All examples in [examples/extensions/](../examples/extensions/). | `dirty-repo-guard.ts` | Warn on dirty git repo | `on("session_before_*")`, `exec` | | `input-transform.ts` | Transform user input | `on("input")` | | `model-status.ts` | React to model changes | `on("model_select")`, `setStatus` | -| `provider-payload.ts` | Inspect or patch provider payloads | `on("before_provider_request")` | +| `provider-payload.ts` | Inspect payloads and provider response headers | `on("before_provider_request")`, `on("after_provider_response")` | | `system-prompt-header.ts` | Display system prompt info | `on("agent_start")`, `getSystemPrompt` | | `claude-rules.ts` | Load rules from files | `on("session_start")`, `on("before_agent_start")` | | `file-trigger.ts` | File watcher triggers messages | `sendMessage` | diff --git a/packages/coding-agent/examples/extensions/provider-payload.ts b/packages/coding-agent/examples/extensions/provider-payload.ts index 55d8cf59..738a4412 100644 --- a/packages/coding-agent/examples/extensions/provider-payload.ts +++ b/packages/coding-agent/examples/extensions/provider-payload.ts @@ -11,4 +11,8 @@ export default function (pi: ExtensionAPI) { // Optional: replace the payload instead of only logging it. // return { ...event.payload, temperature: 0 }; }); + + pi.on("after_provider_response", (event) => { + appendFileSync(logFile, `[${event.status}] ${JSON.stringify(event.headers)}\n\n`, "utf8"); + }); } diff --git a/packages/coding-agent/src/core/extensions/index.ts b/packages/coding-agent/src/core/extensions/index.ts index cca8c914..686bd15e 100644 --- a/packages/coding-agent/src/core/extensions/index.ts +++ b/packages/coding-agent/src/core/extensions/index.ts @@ -20,6 +20,7 @@ export type { } from "./runner.js"; export { ExtensionRunner } from "./runner.js"; export type { + AfterProviderResponseEvent, AgentEndEvent, AgentStartEvent, // Re-exports diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index f986db85..28af6703 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -543,6 +543,13 @@ export interface BeforeProviderRequestEvent { payload: unknown; } +/** Fired after a provider response is received and before the response stream is consumed. */ +export interface AfterProviderResponseEvent { + type: "after_provider_response"; + status: number; + headers: Record; +} + /** Fired after user submits prompt but before agent loop. */ export interface BeforeAgentStartEvent { type: "before_agent_start"; @@ -863,6 +870,7 @@ export type ExtensionEvent = | SessionEvent | ContextEvent | BeforeProviderRequestEvent + | AfterProviderResponseEvent | BeforeAgentStartEvent | AgentStartEvent | AgentEndEvent @@ -1010,6 +1018,7 @@ export interface ExtensionAPI { event: "before_provider_request", handler: ExtensionHandler, ): void; + on(event: "after_provider_response", handler: ExtensionHandler): void; on(event: "before_agent_start", handler: ExtensionHandler): void; on(event: "agent_start", handler: ExtensionHandler): void; on(event: "agent_end", handler: ExtensionHandler): void; diff --git a/packages/coding-agent/src/core/sdk.ts b/packages/coding-agent/src/core/sdk.ts index 7e4512cb..5831e1ff 100644 --- a/packages/coding-agent/src/core/sdk.ts +++ b/packages/coding-agent/src/core/sdk.ts @@ -314,6 +314,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} } return runner.emitBeforeProviderRequest(payload); }, + onResponse: async (response, _model) => { + const runner = extensionRunnerRef.current; + if (!runner?.hasHandlers("after_provider_response")) { + return; + } + await runner.emit({ + type: "after_provider_response", + status: response.status, + headers: response.headers, + }); + }, sessionId: sessionManager.getSessionId(), transformContext: async (messages) => { const runner = extensionRunnerRef.current; diff --git a/packages/coding-agent/test/suite/harness.ts b/packages/coding-agent/test/suite/harness.ts index c1cdb81b..865b8780 100644 --- a/packages/coding-agent/test/suite/harness.ts +++ b/packages/coding-agent/test/suite/harness.ts @@ -142,6 +142,17 @@ export async function createHarness(options: HarnessOptions = {}): Promise { + const runner = extensionRunnerRef.current; + if (!runner?.hasHandlers("after_provider_response")) { + return; + } + await runner.emit({ + type: "after_provider_response", + status: response.status, + headers: response.headers, + }); + }, transformContext: async (messages: AgentMessage[]) => { const runner = extensionRunnerRef.current; if (!runner) return messages; From a5f9f47d135d0b234a841500c5af9db224169017 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 20:32:23 +0200 Subject: [PATCH 16/38] fix(ai): restore changelog and sdk type compatibility --- packages/ai/CHANGELOG.md | 8 ++++++++ packages/ai/src/providers/anthropic.ts | 12 +++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 39cbfe11..86b9ebaa 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -6,6 +6,14 @@ - Added `onResponse` to `StreamOptions` so callers can inspect provider HTTP status and headers after each response arrives and before the response stream is consumed ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) +## [0.67.5] - 2026-04-16 + +### Fixed + +- Fixed Opus 4.7 adaptive thinking configuration across Anthropic and Bedrock providers by recognizing Opus 4.7 adaptive-thinking support and mapping `xhigh` reasoning to provider-supported effort values ([#3286](https://github.com/badlogic/pi-mono/pull/3286) by [@markusylisiurunen](https://github.com/markusylisiurunen)) + +## [0.67.4] - 2026-04-16 + ### Changed - Added `claude-opus-4-7` model for Anthropic, OpenRouter. diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 1d6ee9bc..861bab8a 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -674,15 +674,21 @@ function buildParams( params.tools = convertTools(context.tools, isOAuthToken, cacheControl); } - // Configure thinking mode: adaptive (Opus 4.6 and Sonnet 4.6), + // Configure thinking mode: adaptive (Opus 4.6+ and Sonnet 4.6), // budget-based (older models), or explicitly disabled. if (model.reasoning) { if (options?.thinkingEnabled) { if (supportsAdaptiveThinking(model.id)) { - // Adaptive thinking: Claude decides when and how much to think + // Adaptive thinking: Claude decides when and how much to think. + // The Anthropic SDK types can lag newly supported effort values such as "xhigh". params.thinking = { type: "adaptive" }; if (options.effort) { - params.output_config = { effort: options.effort }; + params.output_config = + options.effort === "xhigh" + ? ({ effort: options.effort } as unknown as NonNullable< + MessageCreateParamsStreaming["output_config"] + >) + : { effort: options.effort }; } } else { // Budget-based thinking for older models From 3b575e3ffbb38ffd013b4fb6ed66eef78e3654d7 Mon Sep 17 00:00:00 2001 From: stembi Date: Thu, 16 Apr 2026 20:38:31 +0200 Subject: [PATCH 17/38] Restore full state when cycling through presets for none preset (#3272) Added OriginalState interface to manage preset state and updated preset clearing logic to restore original state. --- .../examples/extensions/preset.ts | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/examples/extensions/preset.ts b/packages/coding-agent/examples/extensions/preset.ts index d644632d..039c637b 100644 --- a/packages/coding-agent/examples/extensions/preset.ts +++ b/packages/coding-agent/examples/extensions/preset.ts @@ -97,10 +97,17 @@ function loadPresets(cwd: string): PresetsConfig { return { ...globalPresets, ...projectPresets }; } +interface OriginalState { + model: import("@mariozechner/pi-coding-agent").Model | undefined; + thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; + tools: string[]; +} + export default function presetExtension(pi: ExtensionAPI) { let presets: PresetsConfig = {}; let activePresetName: string | undefined; let activePreset: Preset | undefined; + let originalState: OriginalState | undefined; // Register --preset CLI flag pi.registerFlag("preset", { @@ -112,6 +119,15 @@ export default function presetExtension(pi: ExtensionAPI) { * Apply a preset configuration. */ async function applyPreset(name: string, preset: Preset, ctx: ExtensionContext): Promise { + // Snapshot state before the first preset is applied (i.e. only when transitioning from no-preset) + if (activePresetName === undefined) { + originalState = { + model: ctx.model, + thinkingLevel: pi.getThinkingLevel(), + tools: pi.getActiveTools(), + }; + } + // Apply model if specified if (preset.provider && preset.model) { const model = ctx.modelRegistry.find(preset.provider, preset.model); @@ -248,10 +264,18 @@ export default function presetExtension(pi: ExtensionAPI) { if (!result) return; if (result === "(none)") { - // Clear preset and restore defaults + // Clear preset and restore original state activePresetName = undefined; activePreset = undefined; - pi.setActiveTools(["read", "bash", "edit", "write"]); + if (originalState) { + if (originalState.model) { + await pi.setModel(originalState.model); + } + pi.setThinkingLevel(originalState.thinkingLevel); + pi.setActiveTools(originalState.tools); + } else { + pi.setActiveTools(["read", "bash", "edit", "write"]); + } ctx.ui.notify("Preset cleared, defaults restored", "info"); updateStatus(ctx); return; @@ -296,7 +320,15 @@ export default function presetExtension(pi: ExtensionAPI) { if (nextName === "(none)") { activePresetName = undefined; activePreset = undefined; - pi.setActiveTools(["read", "bash", "edit", "write"]); + if (originalState) { + if (originalState.model) { + await pi.setModel(originalState.model); + } + pi.setThinkingLevel(originalState.thinkingLevel); + pi.setActiveTools(originalState.tools); + } else { + pi.setActiveTools(["read", "bash", "edit", "write"]); + } ctx.ui.notify("Preset cleared, defaults restored", "info"); updateStatus(ctx); return; From f822408c775f5fdd95e53e43c90b066c02de5204 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 21:37:20 +0200 Subject: [PATCH 18/38] fix(coding-agent): compact startup header and resource listings (#3267) Use ctrl+o expansion for startup help and loaded resources, with compact comma-separated collapsed lists and full expanded context paths. fixes #3147 --- .../src/modes/interactive/interactive-mode.ts | 129 +++++++++++++++-- .../test/interactive-mode-status.test.ts | 133 +++++++++++++++++- 2 files changed, 244 insertions(+), 18 deletions(-) diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index c1f5089d..3e148c44 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -126,6 +126,22 @@ function isExpandable(obj: unknown): obj is Expandable { return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function"; } +class ExpandableText extends Text implements Expandable { + constructor( + private readonly getCollapsedText: () => string, + private readonly getExpandedText: () => string, + expanded = false, + paddingX = 0, + paddingY = 0, + ) { + super(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY); + } + + setExpanded(expanded: boolean): void { + this.setText(expanded ? this.getExpandedText() : this.getCollapsedText()); + } +} + type CompactionQueuedMessage = { text: string; mode: "steer" | "followUp"; @@ -499,7 +515,7 @@ export class InteractiveMode { // Build startup instructions using keybinding hint helpers const hint = (keybinding: AppKeybinding, description: string) => keyHint(keybinding, description); - const instructions = [ + const expandedInstructions = [ hint("app.interrupt", "to interrupt"), hint("app.clear", "to clear"), rawKeyHint(`${keyText("app.clear")} twice`, "to exit"), @@ -520,11 +536,28 @@ export class InteractiveMode { hint("app.clipboard.pasteImage", "to paste image"), rawKeyHint("drop files", "to attach"), ].join("\n"); + const compactInstructions = [ + hint("app.interrupt", "interrupt"), + rawKeyHint(`${keyText("app.clear")}/${keyText("app.exit")}`, "clear/exit"), + rawKeyHint("/", "commands"), + rawKeyHint("!", "bash"), + hint("app.tools.expand", "more"), + ].join(theme.fg("muted", " · ")); + const compactOnboarding = theme.fg( + "dim", + `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`, + ); const onboarding = theme.fg( "dim", `Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`, ); - this.builtInHeader = new Text(`${logo}\n${instructions}\n\n${onboarding}`, 1, 0); + this.builtInHeader = new ExpandableText( + () => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, + () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, + this.toolOutputExpanded, + 1, + 0, + ); // Setup UI layout this.headerContainer.addChild(new Spacer(1)); @@ -836,6 +869,23 @@ export class InteractiveMode { return result; } + private formatContextPath(p: string): string { + const cwd = path.resolve(this.sessionManager.getCwd()); + const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p); + const relativePath = path.relative(cwd, absolutePath); + const isInsideCwd = + relativePath === "" || + (!relativePath.startsWith("..") && + !relativePath.startsWith(`..${path.sep}`) && + !path.isAbsolute(relativePath)); + + if (isInsideCwd) { + return relativePath || "."; + } + + return this.formatDisplayPath(absolutePath); + } + /** * Get a short path relative to the package root for display. */ @@ -1057,6 +1107,38 @@ export class InteractiveMode { } const sectionHeader = (name: string, color: ThemeColor = "mdHeading") => theme.fg(color, `[${name}]`); + const compactPath = (resourcePath: string, sourceInfo?: SourceInfo): string => { + const shortPath = this.getShortPath(resourcePath, sourceInfo); + const normalizedPath = shortPath.replace(/\\/g, "/"); + const segments = normalizedPath.split("/").filter((segment) => segment.length > 0 && segment !== "~"); + if (segments.length > 0) { + return segments[segments.length - 1]!; + } + return shortPath; + }; + const formatCompactList = (items: string[], options?: { sort?: boolean }): string => { + const labels = items.map((item) => item.trim()).filter((item) => item.length > 0); + if (options?.sort !== false) { + labels.sort((a, b) => a.localeCompare(b)); + } + return theme.fg("dim", ` ${labels.join(", ")}`); + }; + const addLoadedSection = ( + name: string, + collapsedBody: string, + expandedBody = collapsedBody, + color: ThemeColor = "mdHeading", + ): void => { + const section = new ExpandableText( + () => `${sectionHeader(name, color)}\n${collapsedBody}`, + () => `${sectionHeader(name, color)}\n${expandedBody}`, + this.toolOutputExpanded, + 0, + 0, + ); + this.chatContainer.addChild(section); + this.chatContainer.addChild(new Spacer(1)); + }; const skillsResult = this.session.resourceLoader.getSkills(); const promptsResult = this.session.resourceLoader.getPrompts(); @@ -1096,8 +1178,11 @@ export class InteractiveMode { const contextList = contextFiles .map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`)) .join("\n"); - this.chatContainer.addChild(new Text(`${sectionHeader("Context")}\n${contextList}`, 0, 0)); - this.chatContainer.addChild(new Spacer(1)); + const contextCompactList = formatCompactList( + contextFiles.map((contextFile) => this.formatContextPath(contextFile.path)), + { sort: false }, + ); + addLoadedSection("Context", contextCompactList, contextList); } const skills = skillsResult.skills; @@ -1109,8 +1194,8 @@ export class InteractiveMode { formatPath: (item) => this.formatDisplayPath(item.path), formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo), }); - this.chatContainer.addChild(new Text(`${sectionHeader("Skills")}\n${skillList}`, 0, 0)); - this.chatContainer.addChild(new Spacer(1)); + const skillCompactList = formatCompactList(skills.map((skill) => skill.name)); + addLoadedSection("Skills", skillCompactList, skillList); } const templates = this.session.promptTemplates; @@ -1129,8 +1214,8 @@ export class InteractiveMode { return template ? `/${template.name}` : this.formatDisplayPath(item.path); }, }); - this.chatContainer.addChild(new Text(`${sectionHeader("Prompts")}\n${templateList}`, 0, 0)); - this.chatContainer.addChild(new Spacer(1)); + const promptCompactList = formatCompactList(templates.map((template) => `/${template.name}`)); + addLoadedSection("Prompts", promptCompactList, templateList); } if (extensions.length > 0) { @@ -1139,8 +1224,10 @@ export class InteractiveMode { formatPath: (item) => this.formatDisplayPath(item.path), formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo), }); - this.chatContainer.addChild(new Text(`${sectionHeader("Extensions", "mdHeading")}\n${extList}`, 0, 0)); - this.chatContainer.addChild(new Spacer(1)); + const extensionCompactList = formatCompactList( + extensions.map((extension) => compactPath(extension.path, extension.sourceInfo)), + ); + addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading"); } // Show loaded themes (excluding built-in) @@ -1157,8 +1244,12 @@ export class InteractiveMode { formatPath: (item) => this.formatDisplayPath(item.path), formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo), }); - this.chatContainer.addChild(new Text(`${sectionHeader("Themes")}\n${themeList}`, 0, 0)); - this.chatContainer.addChild(new Spacer(1)); + const themeCompactList = formatCompactList( + customThemes.map( + (loadedTheme) => loadedTheme.name ?? compactPath(loadedTheme.sourcePath!, loadedTheme.sourceInfo), + ), + ); + addLoadedSection("Themes", themeCompactList, themeList); } } @@ -1607,6 +1698,9 @@ export class InteractiveMode { if (factory) { // Create and add custom header this.customHeader = factory(this.ui, theme); + if (isExpandable(this.customHeader)) { + this.customHeader.setExpanded(this.toolOutputExpanded); + } if (index !== -1) { this.headerContainer.children[index] = this.customHeader; } else { @@ -1616,6 +1710,9 @@ export class InteractiveMode { } else { // Restore built-in header this.customHeader = undefined; + if (isExpandable(this.builtInHeader)) { + this.builtInHeader.setExpanded(this.toolOutputExpanded); + } if (index !== -1) { this.headerContainer.children[index] = this.builtInHeader; } @@ -3080,6 +3177,10 @@ export class InteractiveMode { private setToolsExpanded(expanded: boolean): void { this.toolOutputExpanded = expanded; + const activeHeader = this.customHeader ?? this.builtInHeader; + if (isExpandable(activeHeader)) { + activeHeader.setExpanded(expanded); + } for (const child of this.chatContainer.children) { if (isExpandable(child)) { child.setExpanded(expanded); @@ -4152,6 +4253,10 @@ export class InteractiveMode { try { await this.session.reload(); this.keybindings.reload(); + const activeHeader = this.customHeader ?? this.builtInHeader; + if (isExpandable(activeHeader)) { + activeHeader.setExpanded(this.toolOutputExpanded); + } setRegisteredThemes(this.session.resourceLoader.getThemes().themes); this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock(); const themeName = this.settingsManager.getTheme(); diff --git a/packages/coding-agent/test/interactive-mode-status.test.ts b/packages/coding-agent/test/interactive-mode-status.test.ts index 3d148495..6dfa8709 100644 --- a/packages/coding-agent/test/interactive-mode-status.test.ts +++ b/packages/coding-agent/test/interactive-mode-status.test.ts @@ -1,3 +1,5 @@ +import { homedir } from "node:os"; +import * as path from "node:path"; import { Container } from "@mariozechner/pi-tui"; import { beforeAll, describe, expect, test, vi } from "vitest"; import { InteractiveMode } from "../src/modes/interactive/interactive-mode.js"; @@ -60,6 +62,27 @@ describe("InteractiveMode.showStatus", () => { }); }); +describe("InteractiveMode.setToolsExpanded", () => { + test("applies expansion state to the active header and chat entries", () => { + const header = { setExpanded: vi.fn() }; + const chatChild = { setExpanded: vi.fn() }; + const fakeThis: any = { + toolOutputExpanded: false, + customHeader: undefined, + builtInHeader: header, + chatContainer: { children: [chatChild] }, + ui: { requestRender: vi.fn() }, + }; + + (InteractiveMode as any).prototype.setToolsExpanded.call(fakeThis, true); + + expect(fakeThis.toolOutputExpanded).toBe(true); + expect(header.setExpanded).toHaveBeenCalledWith(true); + expect(chatChild.setExpanded).toHaveBeenCalledWith(true); + expect(fakeThis.ui.requestRender).toHaveBeenCalledTimes(1); + }); +}); + describe("InteractiveMode.createExtensionUIContext setTheme", () => { test("persists theme changes to settings manager", () => { initTheme("dark"); @@ -116,31 +139,40 @@ describe("InteractiveMode.showLoadedResources", () => { function createShowLoadedResourcesThis(options: { quietStartup: boolean; verbose?: boolean; - skills?: Array<{ filePath: string }>; + toolOutputExpanded?: boolean; + cwd?: string; + contextFiles?: Array<{ path: string; content?: string }>; + extensions?: Array<{ path: string }>; + skills?: Array<{ filePath: string; name: string }>; skillDiagnostics?: Array<{ type: "warning" | "error" | "collision"; message: string }>; }) { const fakeThis: any = { options: { verbose: options.verbose ?? false }, + toolOutputExpanded: options.toolOutputExpanded ?? false, chatContainer: new Container(), settingsManager: { getQuietStartup: () => options.quietStartup, }, + sessionManager: { + getCwd: () => options.cwd ?? "/tmp/project", + }, session: { promptTemplates: [], extensionRunner: undefined, resourceLoader: { getPathMetadata: () => new Map(), - getAgentsFiles: () => ({ agentsFiles: [] }), + getAgentsFiles: () => ({ agentsFiles: options.contextFiles ?? [] }), getSkills: () => ({ skills: options.skills ?? [], diagnostics: options.skillDiagnostics ?? [], }), getPrompts: () => ({ prompts: [], diagnostics: [] }), - getExtensions: () => ({ extensions: [], errors: [], runtime: {} }), + getExtensions: () => ({ extensions: options.extensions ?? [], errors: [], runtime: {} }), getThemes: () => ({ themes: [], diagnostics: [] }), }, }, - formatDisplayPath: (p: string) => p, + formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p), + formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p), buildScopeGroups: () => [], formatScopeGroups: () => "resource-list", getShortPath: (p: string) => p, @@ -151,10 +183,99 @@ describe("InteractiveMode.showLoadedResources", () => { return fakeThis; } + test("shows a compact resource listing by default", () => { + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: false, + skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }], + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + const output = renderAll(fakeThis.chatContainer); + expect(output).toContain("[Skills]"); + expect(output).toContain("commit"); + expect(output).not.toContain("resource-list"); + }); + + test("shows full resource listing when expanded", () => { + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: false, + toolOutputExpanded: true, + skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }], + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + const output = renderAll(fakeThis.chatContainer); + expect(output).toContain("[Skills]"); + expect(output).toContain("resource-list"); + expect(output).not.toContain("commit"); + }); + + test("abbreviates extensions in compact listing", () => { + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: false, + extensions: [{ path: "/tmp/extensions/answer.ts" }, { path: "/tmp/extensions/btw.ts" }], + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + const output = renderAll(fakeThis.chatContainer); + expect(output).toContain("[Extensions]"); + expect(output).toContain("answer.ts, btw.ts"); + expect(output).not.toContain("extensions/answer.ts"); + }); + + test("shows context paths relative to cwd while preserving full external paths", () => { + const home = homedir(); + const cwd = path.join(home, "Development", "pi-mono"); + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: false, + cwd, + contextFiles: [{ path: path.join(home, ".pi", "agent", "AGENTS.md") }, { path: path.join(cwd, "AGENTS.md") }], + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + const output = renderAll(fakeThis.chatContainer).replace(/\\/g, "/"); + expect(output).toContain("[Context]"); + expect(output).toContain("~/.pi/agent/AGENTS.md, AGENTS.md"); + expect(output).not.toContain(`${cwd.replace(/\\/g, "/")}/AGENTS.md`); + }); + + test("shows full context paths when expanded", () => { + const home = homedir(); + const cwd = path.join(home, "Development", "pi-mono"); + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: false, + toolOutputExpanded: true, + cwd, + contextFiles: [{ path: path.join(home, ".pi", "agent", "AGENTS.md") }, { path: path.join(cwd, "AGENTS.md") }], + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + const output = renderAll(fakeThis.chatContainer).replace(/\\/g, "/"); + expect(output).toContain("[Context]"); + expect(output).toContain("~/.pi/agent/AGENTS.md"); + expect(output).toContain("~/Development/pi-mono/AGENTS.md"); + expect(output).not.toContain("~/.pi/agent/AGENTS.md, AGENTS.md"); + }); + test("does not show verbose listing on quiet startup during reload", () => { const fakeThis = createShowLoadedResourcesThis({ quietStartup: true, - skills: [{ filePath: "/tmp/skill/SKILL.md" }], + skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }], }); (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { @@ -169,7 +290,7 @@ describe("InteractiveMode.showLoadedResources", () => { test("still shows diagnostics on quiet startup when requested", () => { const fakeThis = createShowLoadedResourcesThis({ quietStartup: true, - skills: [{ filePath: "/tmp/skill/SKILL.md" }], + skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }], skillDiagnostics: [{ type: "warning", message: "duplicate skill name" }], }); From 7b45c52807e6144fc058bfdd07272533a5e14cbe Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 21:42:57 +0200 Subject: [PATCH 19/38] fix(coding-agent): restore verbose startup expansion closes #3147 --- packages/coding-agent/CHANGELOG.md | 4 ++ .../examples/extensions/preset.ts | 3 +- .../src/modes/interactive/interactive-mode.ts | 63 +++++++++++++++++-- .../test/interactive-mode-status.test.ts | 19 ++++++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c1747b16..eea5c9e3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `--verbose` startup output to begin with expanded startup help and loaded resource listings after the compact startup header change ([#3147](https://github.com/badlogic/pi-mono/issues/3147)) + ## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/coding-agent/examples/extensions/preset.ts b/packages/coding-agent/examples/extensions/preset.ts index 039c637b..a0cf663e 100644 --- a/packages/coding-agent/examples/extensions/preset.ts +++ b/packages/coding-agent/examples/extensions/preset.ts @@ -40,6 +40,7 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; +import type { Api, Model } from "@mariozechner/pi-ai"; import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import { DynamicBorder, getAgentDir } from "@mariozechner/pi-coding-agent"; import { Container, Key, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; @@ -98,7 +99,7 @@ function loadPresets(cwd: string): PresetsConfig { } interface OriginalState { - model: import("@mariozechner/pi-coding-agent").Model | undefined; + model: Model | undefined; thinkingLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; tools: string[]; } diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 3e148c44..4630c527 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -58,7 +58,7 @@ import type { import { FooterDataProvider, type ReadonlyFooterDataProvider } from "../../core/footer-data-provider.js"; import { type AppKeybinding, KeybindingsManager } from "../../core/keybindings.js"; import { createCompactionSummaryMessage } from "../../core/messages.js"; -import { findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js"; +import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js"; import { DefaultPackageManager } from "../../core/package-manager.js"; import type { ResourceDiagnostic } from "../../core/resource-loader.js"; import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js"; @@ -159,6 +159,14 @@ function isTruthyEnvFlag(value: string | undefined): boolean { return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes"; } +function isUnknownModel(model: Model | undefined): boolean { + return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown"; +} + +function hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider { + return providerId in defaultModelPerProvider; +} + /** * Options for InteractiveMode initialization. */ @@ -554,7 +562,7 @@ export class InteractiveMode { this.builtInHeader = new ExpandableText( () => `${logo}\n${compactInstructions}\n${compactOnboarding}\n\n${onboarding}`, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, - this.toolOutputExpanded, + this.getStartupExpansionState(), 1, 0, ); @@ -886,6 +894,10 @@ export class InteractiveMode { return this.formatDisplayPath(absolutePath); } + private getStartupExpansionState(): boolean { + return this.options.verbose || this.toolOutputExpanded; + } + /** * Get a short path relative to the package root for display. */ @@ -1132,7 +1144,7 @@ export class InteractiveMode { const section = new ExpandableText( () => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, - this.toolOutputExpanded, + this.getStartupExpansionState(), 0, 0, ); @@ -4124,6 +4136,7 @@ export class InteractiveMode { private async showLoginDialog(providerId: string): Promise { const providerInfo = this.session.modelRegistry.authStorage.getOAuthProviders().find((p) => p.id === providerId); const providerName = providerInfo?.name || providerId; + const previousModel = this.session.model; // Providers that use callback servers (can paste redirect URL) const usesCallbackServer = providerInfo?.usesCallbackServer ?? false; @@ -4199,8 +4212,50 @@ export class InteractiveMode { // Success restoreEditor(); this.session.modelRegistry.refresh(); + + let selectedModel: Model | undefined; + let selectionError: string | undefined; + if (isUnknownModel(previousModel)) { + const availableModels = this.session.modelRegistry.getAvailable(); + const providerModels = availableModels.filter((model) => model.provider === providerId); + if (!hasDefaultModelProvider(providerId)) { + selectionError = `Logged in to ${providerName}, but no default model is configured for provider "${providerId}". Use /model to select a model.`; + } else if (providerModels.length === 0) { + selectionError = `Logged in to ${providerName}, but no models are available for that provider. Use /model to select a model.`; + } else { + const defaultModelId = defaultModelPerProvider[providerId]; + selectedModel = providerModels.find((model) => model.id === defaultModelId); + if (!selectedModel) { + selectionError = `Logged in to ${providerName}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`; + } else { + try { + await this.session.setModel(selectedModel); + } catch (error: unknown) { + selectedModel = undefined; + const errorMessage = error instanceof Error ? error.message : String(error); + selectionError = `Logged in to ${providerName}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`; + } + } + } + } + await this.updateAvailableProviderCount(); - this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`); + this.footer.invalidate(); + this.updateEditorBorderColor(); + if (selectedModel) { + this.showStatus( + `Logged in to ${providerName}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`, + ); + void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel); + this.checkDaxnutsEasterEgg(selectedModel); + } else { + this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`); + if (selectionError) { + this.showError(selectionError); + } else { + void this.maybeWarnAboutAnthropicSubscriptionAuth(); + } + } } catch (error: unknown) { restoreEditor(); const errorMsg = error instanceof Error ? error.message : String(error); diff --git a/packages/coding-agent/test/interactive-mode-status.test.ts b/packages/coding-agent/test/interactive-mode-status.test.ts index 6dfa8709..e2af115b 100644 --- a/packages/coding-agent/test/interactive-mode-status.test.ts +++ b/packages/coding-agent/test/interactive-mode-status.test.ts @@ -173,6 +173,7 @@ describe("InteractiveMode.showLoadedResources", () => { }, formatDisplayPath: (p: string) => (InteractiveMode as any).prototype.formatDisplayPath.call(fakeThis, p), formatContextPath: (p: string) => (InteractiveMode as any).prototype.formatContextPath.call(fakeThis, p), + getStartupExpansionState: () => (InteractiveMode as any).prototype.getStartupExpansionState.call(fakeThis), buildScopeGroups: () => [], formatScopeGroups: () => "resource-list", getShortPath: (p: string) => p, @@ -216,6 +217,24 @@ describe("InteractiveMode.showLoadedResources", () => { expect(output).not.toContain("commit"); }); + test("shows full resource listing on verbose startup even when tool output is collapsed", () => { + const fakeThis = createShowLoadedResourcesThis({ + quietStartup: true, + verbose: true, + toolOutputExpanded: false, + skills: [{ filePath: "/tmp/skill/SKILL.md", name: "commit" }], + }); + + (InteractiveMode as any).prototype.showLoadedResources.call(fakeThis, { + force: false, + }); + + const output = renderAll(fakeThis.chatContainer); + expect(output).toContain("[Skills]"); + expect(output).toContain("resource-list"); + expect(output).not.toContain("commit"); + }); + test("abbreviates extensions in compact listing", () => { const fakeThis = createShowLoadedResourcesThis({ quietStartup: false, From acbf8eca0634c6579277d4404c3d5e8663b17d76 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 22:12:06 +0200 Subject: [PATCH 20/38] feat(ai): add thinkingDisplay option for Anthropic and Bedrock Claude Exposes the new ThinkingConfig.display field on Anthropic and Bedrock Claude providers. Defaults to 'summarized' so Claude Opus 4.7 and Mythos Preview keep returning thinking text despite Anthropic's silent default change to 'omitted'. Set to 'omitted' explicitly to skip thinking streaming for faster time-to-first-text-token. --- packages/ai/CHANGELOG.md | 5 +++++ packages/ai/src/index.ts | 4 ++-- packages/ai/src/providers/amazon-bedrock.ts | 19 +++++++++++++++++- packages/ai/src/providers/anthropic.ts | 22 +++++++++++++++++++-- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 86b9ebaa..241b7792 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -5,6 +5,11 @@ ### Added - Added `onResponse` to `StreamOptions` so callers can inspect provider HTTP status and headers after each response arrives and before the response stream is consumed ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) +- Added `thinkingDisplay` (`"summarized" | "omitted"`) to `AnthropicOptions` and `BedrockOptions`, wiring it through to the Anthropic/Bedrock `thinking` config. Defaults to `"summarized"` so Claude Opus 4.7 and Mythos Preview keep returning thinking text; set it to `"omitted"` to skip thinking streaming for faster time-to-first-text-token. + +### Changed + +- Bumped `@anthropic-ai/sdk` to `0.90.0` so `ThinkingConfigAdaptive.display` and `ThinkingConfigEnabled.display` are typed by the SDK. ## [0.67.5] - 2026-04-16 diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts index 1c885a85..6a1ee2c0 100644 --- a/packages/ai/src/index.ts +++ b/packages/ai/src/index.ts @@ -4,8 +4,8 @@ export { Type } from "@sinclair/typebox"; export * from "./api-registry.js"; export * from "./env-api-keys.js"; export * from "./models.js"; -export type { BedrockOptions } from "./providers/amazon-bedrock.js"; -export type { AnthropicOptions } from "./providers/anthropic.js"; +export type { BedrockOptions, BedrockThinkingDisplay } from "./providers/amazon-bedrock.js"; +export type { AnthropicEffort, AnthropicOptions, AnthropicThinkingDisplay } from "./providers/anthropic.js"; export type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses.js"; export * from "./providers/faux.js"; export type { GoogleOptions } from "./providers/google.js"; diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index f72356b5..4b2e61f6 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -46,6 +46,8 @@ import { sanitizeSurrogates } from "../utils/sanitize-unicode.js"; import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js"; import { transformMessages } from "./transform-messages.js"; +export type BedrockThinkingDisplay = "summarized" | "omitted"; + export interface BedrockOptions extends StreamOptions { region?: string; profile?: string; @@ -56,6 +58,17 @@ export interface BedrockOptions extends StreamOptions { thinkingBudgets?: ThinkingBudgets; /* Only supported by Claude 4.x models, see https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-extended-thinking.html#claude-messages-extended-thinking-tool-use-interleaved */ interleavedThinking?: boolean; + /** + * Controls how Claude's thinking content is returned in responses. + * - "summarized": Thinking blocks contain summarized thinking text (default here). + * - "omitted": Thinking content is redacted but the signature still travels back + * for multi-turn continuity, reducing time-to-first-text-token. + * + * Note: Anthropic's API default for Claude Opus 4.7 and Mythos Preview is + * "omitted". We default to "summarized" here to keep behavior consistent with + * older Claude 4 models. Only applies to Claude models on Bedrock. + */ + thinkingDisplay?: BedrockThinkingDisplay; /** Key-value pairs attached to the inference request for cost allocation tagging. * Keys: max 64 chars, no `aws:` prefix. Values: max 256 chars. Max 50 pairs. * Tags appear in AWS Cost Explorer split cost allocation data. @@ -759,9 +772,12 @@ function buildAdditionalModelRequestFields( } if (model.id.includes("anthropic.claude") || model.id.includes("anthropic/claude")) { + // Default to "summarized" so Opus 4.7 and Mythos Preview behave like + // older Claude 4 models (whose API default is also "summarized"). + const display: BedrockThinkingDisplay = options.thinkingDisplay ?? "summarized"; const result: Record = supportsAdaptiveThinking(model.id) ? { - thinking: { type: "adaptive" }, + thinking: { type: "adaptive", display }, output_config: { effort: mapThinkingLevelToEffort(options.reasoning, model.id) }, } : (() => { @@ -781,6 +797,7 @@ function buildAdditionalModelRequestFields( thinking: { type: "enabled", budget_tokens: budget, + display, }, }; })(); diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index 861bab8a..47686327 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -156,6 +156,8 @@ function convertContentBlocks(content: (TextContent | ImageContent)[]): export type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max"; +export type AnthropicThinkingDisplay = "summarized" | "omitted"; + export interface AnthropicOptions extends StreamOptions { /** * Enable extended thinking. @@ -179,6 +181,18 @@ export interface AnthropicOptions extends StreamOptions { * Ignored for older models. */ effort?: AnthropicEffort; + /** + * Controls how thinking content is returned in API responses. + * - "summarized": Thinking blocks contain summarized thinking text (default here). + * - "omitted": Thinking blocks return an empty thinking field; the encrypted + * signature still travels back for multi-turn continuity. Use for faster + * time-to-first-text-token when your UI does not surface thinking. + * + * Note: Anthropic's API default for Claude Opus 4.7 and Claude Mythos Preview + * is "omitted". We default to "summarized" here to keep behavior consistent + * with older Claude 4 models. Set this explicitly to "omitted" to opt in. + */ + thinkingDisplay?: AnthropicThinkingDisplay; interleavedThinking?: boolean; toolChoice?: "auto" | "any" | "none" | { type: "tool"; name: string }; /** @@ -678,11 +692,14 @@ function buildParams( // budget-based (older models), or explicitly disabled. if (model.reasoning) { if (options?.thinkingEnabled) { + // Default to "summarized" so Opus 4.7 and Mythos Preview behave like + // older Claude 4 models (whose API default is also "summarized"). + const display: AnthropicThinkingDisplay = options.thinkingDisplay ?? "summarized"; if (supportsAdaptiveThinking(model.id)) { // Adaptive thinking: Claude decides when and how much to think. - // The Anthropic SDK types can lag newly supported effort values such as "xhigh". - params.thinking = { type: "adaptive" }; + params.thinking = { type: "adaptive", display }; if (options.effort) { + // The Anthropic SDK types can lag newly supported effort values such as "xhigh". params.output_config = options.effort === "xhigh" ? ({ effort: options.effort } as unknown as NonNullable< @@ -695,6 +712,7 @@ function buildParams( params.thinking = { type: "enabled", budget_tokens: options.thinkingBudgetTokens || 1024, + display, }; } } else if (options?.thinkingEnabled === false) { From e8743e870b9456f13e5873c8b308c2999b9059ee Mon Sep 17 00:00:00 2001 From: Omair Ahmed Date: Thu, 16 Apr 2026 16:13:36 -0400 Subject: [PATCH 21/38] feat(tui): use OSC 8 hyperlinks in Markdown when terminal supports them (#3248) TerminalCapabilities already tracks hyperlinks: boolean and returns true for Ghostty, Kitty, WezTerm, and iTerm2, but nothing generated OSC 8 sequences. This completes that stub. Changes to packages/tui: - terminal-image.ts: add hyperlink(text, url) and setCapabilities() - index.ts: export hyperlink and setCapabilities - utils.ts: extend AnsiCodeTracker to track active OSC 8 URLs - process() now handles OSC 8 open/close sequences - getActiveCodes() re-emits the OSC 8 open at each line start - getLineEndReset() closes the OSC 8 hyperlink before each line break This ensures hyperlinks wrap correctly across multiple lines. - components/markdown.ts: link renderer uses hyperlink() when getCapabilities().hyperlinks is true; falls back to (url) text - Tests: new wrap-ansi tests for OSC 8 line-wrapping; terminal-image tests for hyperlink(); markdown tests covering both code paths; table-cell width test pinned to hyperlinks:false (checks raw columns) closes #3239 Co-authored-by: AI (Pi/Claude Sonnet 4.6) Co-authored-by: Mario Zechner --- packages/tui/src/components/markdown.ts | 28 ++++++---- packages/tui/src/index.ts | 2 + packages/tui/src/terminal-image.ts | 19 +++++++ packages/tui/src/utils.ts | 37 ++++++++---- packages/tui/test/markdown.test.ts | 71 ++++++++++++++++++++++-- packages/tui/test/terminal-image.test.ts | 28 +++++++++- packages/tui/test/wrap-ansi.test.ts | 55 ++++++++++++++++++ 7 files changed, 212 insertions(+), 28 deletions(-) diff --git a/packages/tui/src/components/markdown.ts b/packages/tui/src/components/markdown.ts index 4399ee53..7bdde68f 100644 --- a/packages/tui/src/components/markdown.ts +++ b/packages/tui/src/components/markdown.ts @@ -1,5 +1,5 @@ import { Marked, type Token, Tokenizer, type Tokens } from "marked"; -import { isImageLine } from "../terminal-image.js"; +import { getCapabilities, hyperlink, isImageLine } from "../terminal-image.js"; import type { Component } from "../tui.js"; import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils.js"; @@ -488,18 +488,22 @@ export class Markdown implements Component { case "link": { const linkText = this.renderInlineTokens(token.tokens || [], resolvedStyleContext); - // If link text matches href, only show the link once - // Compare raw text (token.text) not styled text (linkText) since linkText has ANSI codes - // For mailto: links, strip the prefix before comparing (autolinked emails have - // text="foo@bar.com" but href="mailto:foo@bar.com") - const hrefForComparison = token.href.startsWith("mailto:") ? token.href.slice(7) : token.href; - if (token.text === token.href || token.text === hrefForComparison) { - result += this.theme.link(this.theme.underline(linkText)) + stylePrefix; + const styledLink = this.theme.link(this.theme.underline(linkText)); + if (getCapabilities().hyperlinks) { + // OSC 8: render as a clickable hyperlink. The URL is not printed inline, + // so we always show only the link text regardless of whether it matches href. + result += hyperlink(styledLink, token.href) + stylePrefix; } else { - result += - this.theme.link(this.theme.underline(linkText)) + - this.theme.linkUrl(` (${token.href})`) + - stylePrefix; + // Fallback: print URL in parentheses when text differs from href. + // Compare raw token.text (not styled) against href for the equality check. + // For mailto: links strip the prefix (autolinked emails use text="foo@bar.com" + // but href="mailto:foo@bar.com"). + const hrefForComparison = token.href.startsWith("mailto:") ? token.href.slice(7) : token.href; + if (token.text === token.href || token.text === hrefForComparison) { + result += styledLink + stylePrefix; + } else { + result += styledLink + this.theme.linkUrl(` (${token.href})`) + stylePrefix; + } } break; } diff --git a/packages/tui/src/index.ts b/packages/tui/src/index.ts index 025ecde4..aa3dd68b 100644 --- a/packages/tui/src/index.ts +++ b/packages/tui/src/index.ts @@ -78,12 +78,14 @@ export { getJpegDimensions, getPngDimensions, getWebpDimensions, + hyperlink, type ImageDimensions, type ImageProtocol, type ImageRenderOptions, imageFallback, renderImage, resetCapabilitiesCache, + setCapabilities, setCellDimensions, type TerminalCapabilities, } from "./terminal-image.js"; diff --git a/packages/tui/src/terminal-image.ts b/packages/tui/src/terminal-image.ts index e706fedc..a8406a1c 100644 --- a/packages/tui/src/terminal-image.ts +++ b/packages/tui/src/terminal-image.ts @@ -81,6 +81,11 @@ export function resetCapabilitiesCache(): void { cachedCapabilities = null; } +/** Override the cached capabilities. Useful in tests to exercise both code paths. */ +export function setCapabilities(caps: TerminalCapabilities): void { + cachedCapabilities = caps; +} + const KITTY_PREFIX = "\x1b_G"; const ITERM2_PREFIX = "\x1b]1337;File="; @@ -372,6 +377,20 @@ export function renderImage( return null; } +/** + * Wrap text in an OSC 8 hyperlink sequence. + * The text is rendered as a clickable hyperlink in terminals that support OSC 8 + * (Ghostty, Kitty, WezTerm, iTerm2, VSCode, and others). + * In terminals that do not support OSC 8, the escape sequences are ignored + * and only the plain text is displayed. + * + * @param text - The visible text to display + * @param url - The URL to link to + */ +export function hyperlink(text: string, url: string): string { + return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`; +} + export function imageFallback(mimeType: string, dimensions?: ImageDimensions, filename?: string): string { const parts: string[] = []; if (filename) parts.push(filename); diff --git a/packages/tui/src/utils.ts b/packages/tui/src/utils.ts index ad67efdd..7c4ea7c1 100644 --- a/packages/tui/src/utils.ts +++ b/packages/tui/src/utils.ts @@ -311,8 +311,16 @@ class AnsiCodeTracker { private strikethrough = false; private fgColor: string | null = null; // Stores the full code like "31" or "38;5;240" private bgColor: string | null = null; // Stores the full code like "41" or "48;5;240" + private activeHyperlink: string | null = null; // Active OSC 8 hyperlink URL, or null process(ansiCode: string): void { + // OSC 8 hyperlink: \x1b]8;;\x1b\\ (open) or \x1b]8;;\x1b\\ (close) + if (ansiCode.startsWith("\x1b]8;")) { + const m = ansiCode.match(/^\x1b\]8;[^;]*;([^\x1b\x07]*)/); + this.activeHyperlink = m?.[1] ? m[1] : null; + return; + } + if (!ansiCode.endsWith("m")) { return; } @@ -447,11 +455,13 @@ class AnsiCodeTracker { this.strikethrough = false; this.fgColor = null; this.bgColor = null; + // SGR reset does not affect OSC 8 hyperlink state } /** Clear all state for reuse. */ clear(): void { this.reset(); + this.activeHyperlink = null; } getActiveCodes(): string { @@ -467,8 +477,11 @@ class AnsiCodeTracker { if (this.fgColor) codes.push(this.fgColor); if (this.bgColor) codes.push(this.bgColor); - if (codes.length === 0) return ""; - return `\x1b[${codes.join(";")}m`; + let result = codes.length > 0 ? `\x1b[${codes.join(";")}m` : ""; + if (this.activeHyperlink) { + result += `\x1b]8;;${this.activeHyperlink}\x1b\\`; + } + return result; } hasActiveCodes(): boolean { @@ -482,22 +495,26 @@ class AnsiCodeTracker { this.hidden || this.strikethrough || this.fgColor !== null || - this.bgColor !== null + this.bgColor !== null || + this.activeHyperlink !== null ); } /** - * Get reset codes for attributes that need to be turned off at line end, - * specifically underline which bleeds into padding. - * Returns empty string if no problematic attributes are active. + * Get reset codes for attributes that need to be turned off at line end. + * Underline must be closed to prevent bleeding into padding. + * Active OSC 8 hyperlinks must be closed and re-opened on the next line. + * Returns empty string if no attributes need closing. */ getLineEndReset(): string { - // Only underline causes visual bleeding into padding - // Other attributes like colors don't visually bleed to padding + let result = ""; if (this.underline) { - return "\x1b[24m"; // Underline off only + result += "\x1b[24m"; // Underline off only } - return ""; + if (this.activeHyperlink) { + result += "\x1b]8;;\x1b\\"; // Close hyperlink; re-opened at line start via getActiveCodes() + } + return result; } } diff --git a/packages/tui/test/markdown.test.ts b/packages/tui/test/markdown.test.ts index fd265187..029a614e 100644 --- a/packages/tui/test/markdown.test.ts +++ b/packages/tui/test/markdown.test.ts @@ -1,8 +1,9 @@ import assert from "node:assert"; -import { describe, it } from "node:test"; +import { afterEach, describe, it } from "node:test"; import type { Terminal as XtermTerminalType } from "@xterm/headless"; import { Chalk } from "chalk"; import { Markdown } from "../src/components/markdown.js"; +import { resetCapabilitiesCache, setCapabilities } from "../src/terminal-image.js"; import { type Component, TUI } from "../src/tui.js"; import { defaultMarkdownTheme } from "./test-themes.js"; import { VirtualTerminal } from "./virtual-terminal.js"; @@ -325,6 +326,8 @@ describe("Markdown component", () => { }); it("should wrap long unbroken tokens inside table cells (not only at line start)", () => { + // Pin to no-hyperlinks so width checks work on plain text without OSC 8 sequences. + setCapabilities({ images: null, trueColor: false, hyperlinks: false }); const url = "https://example.com/this/is/a/very/long/url/that/should/wrap"; const markdown = new Markdown( `| Value | @@ -337,6 +340,7 @@ describe("Markdown component", () => { const width = 30; const lines = markdown.render(width); + resetCapabilitiesCache(); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd()); for (const line of plainLines) { @@ -1087,7 +1091,13 @@ bar`, }); describe("Links", () => { + afterEach(() => { + resetCapabilitiesCache(); + }); + it("should not duplicate URL for autolinked emails", () => { + // Hyperlinks capability does not affect the mailto: display check. + setCapabilities({ images: null, trueColor: false, hyperlinks: false }); const markdown = new Markdown("Contact user@example.com for help", 0, 0, defaultMarkdownTheme); const lines = markdown.render(80); @@ -1100,6 +1110,7 @@ bar`, }); it("should not duplicate URL for bare URLs", () => { + setCapabilities({ images: null, trueColor: false, hyperlinks: false }); const markdown = new Markdown("Visit https://example.com for more", 0, 0, defaultMarkdownTheme); const lines = markdown.render(80); @@ -1111,29 +1122,79 @@ bar`, assert.strictEqual(urlCount, 1, "URL should appear exactly once"); }); - it("should show URL for explicit markdown links with different text", () => { + it("should show URL in parentheses when hyperlinks are not supported", () => { + setCapabilities({ images: null, trueColor: false, hyperlinks: false }); const markdown = new Markdown("[click here](https://example.com)", 0, 0, defaultMarkdownTheme); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); const joinedPlain = plainLines.join(" "); - // Should show both link text and URL assert.ok(joinedPlain.includes("click here"), "Should contain link text"); assert.ok(joinedPlain.includes("(https://example.com)"), "Should show URL in parentheses"); }); - it("should show URL for explicit mailto links with different text", () => { + it("should show mailto URL in parentheses when hyperlinks are not supported", () => { + setCapabilities({ images: null, trueColor: false, hyperlinks: false }); const markdown = new Markdown("[Email me](mailto:test@example.com)", 0, 0, defaultMarkdownTheme); const lines = markdown.render(80); const plainLines = lines.map((line) => line.replace(/\x1b\[[0-9;]*m/g, "")); const joinedPlain = plainLines.join(" "); - // Should show both link text and mailto URL assert.ok(joinedPlain.includes("Email me"), "Should contain link text"); assert.ok(joinedPlain.includes("(mailto:test@example.com)"), "Should show mailto URL in parentheses"); }); + + it("should emit OSC 8 hyperlink sequence when terminal supports hyperlinks", () => { + setCapabilities({ images: null, trueColor: false, hyperlinks: true }); + const markdown = new Markdown("[click here](https://example.com)", 0, 0, defaultMarkdownTheme); + + const lines = markdown.render(80); + const joined = lines.join(""); + + // OSC 8 open: ESC ] 8 ; ; ESC \ + assert.ok(joined.includes("\x1b]8;;https://example.com\x1b\\"), "Should contain OSC 8 open sequence"); + // OSC 8 close: ESC ] 8 ; ; ESC \ + assert.ok(joined.includes("\x1b]8;;\x1b\\"), "Should contain OSC 8 close sequence"); + // Visible text is present + const plainLines = lines.map((line) => line.replace(/\x1b[^a-zA-Z]*[a-zA-Z]|\x1b\].*?\x1b\\/g, "")); + assert.ok(plainLines.join("").includes("click here"), "Should contain link text"); + // URL is NOT printed inline as plain text + const rawPlain = lines.map((line) => + line.replace(/\x1b\]8;;[^\x1b]*\x1b\\/g, "").replace(/\x1b\[[0-9;]*m/g, ""), + ); + assert.ok(!rawPlain.join("").includes("(https://example.com)"), "URL should not appear inline in parentheses"); + }); + + it("should use OSC 8 for mailto links when terminal supports hyperlinks", () => { + setCapabilities({ images: null, trueColor: false, hyperlinks: true }); + const markdown = new Markdown("[Email me](mailto:test@example.com)", 0, 0, defaultMarkdownTheme); + + const lines = markdown.render(80); + const joined = lines.join(""); + + assert.ok( + joined.includes("\x1b]8;;mailto:test@example.com\x1b\\"), + "Should contain OSC 8 open with mailto URL", + ); + assert.ok(joined.includes("\x1b]8;;\x1b\\"), "Should contain OSC 8 close sequence"); + }); + + it("should use OSC 8 for bare URLs when terminal supports hyperlinks", () => { + setCapabilities({ images: null, trueColor: false, hyperlinks: true }); + const markdown = new Markdown("Visit https://example.com for more", 0, 0, defaultMarkdownTheme); + + const lines = markdown.render(80); + const joined = lines.join(""); + + assert.ok(joined.includes("\x1b]8;;https://example.com\x1b\\"), "Should contain OSC 8 hyperlink"); + // URL should not also appear as raw parenthetical text + const rawPlain = lines.map((line) => + line.replace(/\x1b\]8;;[^\x1b]*\x1b\\/g, "").replace(/\x1b\[[0-9;]*m/g, ""), + ); + assert.ok(!rawPlain.join("").includes("(https://example.com)"), "URL should not appear twice"); + }); }); describe("HTML-like tags in text", () => { diff --git a/packages/tui/test/terminal-image.test.ts b/packages/tui/test/terminal-image.test.ts index b573837c..feeebafe 100644 --- a/packages/tui/test/terminal-image.test.ts +++ b/packages/tui/test/terminal-image.test.ts @@ -4,7 +4,7 @@ import assert from "node:assert"; import { describe, it } from "node:test"; -import { isImageLine } from "../src/terminal-image.js"; +import { hyperlink, isImageLine } from "../src/terminal-image.js"; describe("isImageLine", () => { describe("iTerm2 image protocol", () => { @@ -151,3 +151,29 @@ describe("isImageLine", () => { }); }); }); + +describe("hyperlink", () => { + it("wraps text in OSC 8 open and close sequences", () => { + const result = hyperlink("click me", "https://example.com"); + assert.strictEqual(result, "\x1b]8;;https://example.com\x1b\\click me\x1b]8;;\x1b\\"); + }); + + it("preserves ANSI styling inside the hyperlink", () => { + const styled = "\x1b[4m\x1b[34mclick me\x1b[0m"; + const result = hyperlink(styled, "https://example.com"); + assert.ok(result.startsWith("\x1b]8;;https://example.com\x1b\\")); + assert.ok(result.includes(styled)); + assert.ok(result.endsWith("\x1b]8;;\x1b\\")); + }); + + it("works with empty text", () => { + const result = hyperlink("", "https://example.com"); + assert.strictEqual(result, "\x1b]8;;https://example.com\x1b\\\x1b]8;;\x1b\\"); + }); + + it("works with file:// URIs", () => { + const result = hyperlink("README.md", "file:///home/user/README.md"); + assert.ok(result.includes("file:///home/user/README.md")); + assert.ok(result.includes("README.md")); + }); +}); diff --git a/packages/tui/test/wrap-ansi.test.ts b/packages/tui/test/wrap-ansi.test.ts index 9fcc89ce..e0944ded 100644 --- a/packages/tui/test/wrap-ansi.test.ts +++ b/packages/tui/test/wrap-ansi.test.ts @@ -150,3 +150,58 @@ describe("wrapTextWithAnsi", () => { }); }); }); + +describe("wrapTextWithAnsi with OSC 8 hyperlinks", () => { + it("re-emits OSC 8 open at the start of continuation lines", () => { + // A hyperlink whose text is long enough to wrap + const url = "https://example.com"; + // OSC 8 open + text that is 10 visible chars + OSC 8 close + const input = `\x1b]8;;${url}\x1b\\0123456789\x1b]8;;\x1b\\`; + const lines = wrapTextWithAnsi(input, 6); + + // Every line that contains visible text from inside the hyperlink + // should start with the OSC 8 open sequence (or be preceded by it). + for (const line of lines) { + // If the line has visible content it must begin with the OSC 8 re-open + // OR it is the line where the close appeared with no following content. + const stripped = line.replace(/\x1b\]8;;[^\x1b\x07]*\x1b\\/g, "").replace(/\x1b\[[0-9;]*m/g, ""); + if (stripped.trim().length > 0) { + assert.ok( + line.startsWith(`\x1b]8;;${url}\x1b\\`) || line.includes(`\x1b]8;;${url}\x1b\\`), + `Line "${line}" has visible text but no OSC 8 re-open`, + ); + } + } + }); + + it("closes OSC 8 before each line break", () => { + const url = "https://example.com"; + const input = `\x1b]8;;${url}\x1b\\0123456789\x1b]8;;\x1b\\`; + const lines = wrapTextWithAnsi(input, 6); + + for (let i = 0; i < lines.length - 1; i++) { + const line = lines[i]; + // Every non-final line that is inside a hyperlink should end with the close + if (line.includes(`\x1b]8;;${url}\x1b\\`)) { + assert.ok( + line.endsWith("\x1b]8;;\x1b\\"), + `Non-final line "${line}" is inside a hyperlink but does not close it`, + ); + } + } + }); + + it("does not emit OSC 8 sequences on lines that are outside the hyperlink", () => { + const url = "https://example.com"; + const input = `before \x1b]8;;${url}\x1b\\link\x1b]8;;\x1b\\ after`; + const lines = wrapTextWithAnsi(input, 80); + + // With width 80 everything fits on one line; there should be exactly one + // OSC 8 open and one OSC 8 close. + assert.strictEqual(lines.length, 1); + const openCount = (lines[0].match(/\x1b\]8;;https:[^\x1b]+\x1b\\/g) ?? []).length; + const closeCount = (lines[0].match(/\x1b\]8;;\x1b\\/g) ?? []).length; + assert.strictEqual(openCount, 1); + assert.strictEqual(closeCount, 1); + }); +}); From c3ded498ad3063ab3323b689b671015a016eb2ca Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 22:16:34 +0200 Subject: [PATCH 22/38] docs(ai): drop incorrect anthropic sdk bump changelog entry The @anthropic-ai/sdk range was already ^0.90.0 in the previous commit. Only node_modules was stale; no dependency version actually changed. --- packages/ai/CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 241b7792..f062c605 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -7,10 +7,6 @@ - Added `onResponse` to `StreamOptions` so callers can inspect provider HTTP status and headers after each response arrives and before the response stream is consumed ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) - Added `thinkingDisplay` (`"summarized" | "omitted"`) to `AnthropicOptions` and `BedrockOptions`, wiring it through to the Anthropic/Bedrock `thinking` config. Defaults to `"summarized"` so Claude Opus 4.7 and Mythos Preview keep returning thinking text; set it to `"omitted"` to skip thinking streaming for faster time-to-first-text-token. -### Changed - -- Bumped `@anthropic-ai/sdk` to `0.90.0` so `ThinkingConfigAdaptive.display` and `ThinkingConfigEnabled.display` are typed by the SDK. - ## [0.67.5] - 2026-04-16 ### Fixed From 30a8a41fc3c8419f973a3e6bde95c41aabe14d75 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 22:19:38 +0200 Subject: [PATCH 23/38] fix(tui): default hyperlinks off for unknown terminals and tmux/screen OSC 8 hyperlinks landed in #3248, but detectCapabilities() returned hyperlinks: true in the unknown-terminal fallback. Terminals that silently swallow OSC 8 (most xterm-compatible hosts, tmux/screen without passthrough) end up dropping the URL from rendered markdown links entirely, since the fallback 'text (url)' rendering is skipped whenever hyperlinks is true. - Unknown terminals now default to hyperlinks: false. - tmux and screen (TMUX env, TERM starting with tmux/screen) force hyperlinks: false even when the outer terminal would otherwise advertise OSC 8 support. Image protocols also left disabled. - Added detectCapabilities tests covering the known-capable set and the tmux/screen/unknown cases. --- packages/tui/CHANGELOG.md | 8 ++ packages/tui/src/terminal-image.ts | 16 +++- packages/tui/test/terminal-image.test.ts | 102 ++++++++++++++++++++++- 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 0d46e4ba..57ddcd8e 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +### Added + +- Added OSC 8 hyperlink rendering for markdown links when the terminal advertises support. Introduces a public `hyperlink(text, url)` helper and a `setCapabilities()` test override in `packages/tui` ([#3248](https://github.com/badlogic/pi-mono/pull/3248) by [@ofa1](https://github.com/ofa1)). + +### Changed + +- Tightened `detectCapabilities()` to default `hyperlinks: false` for unknown terminals and to force `hyperlinks: false` under tmux/screen (including nested sessions where the outer terminal would otherwise advertise OSC 8). Prevents markdown link URLs from disappearing on terminals that silently swallow OSC 8 sequences ([#3248](https://github.com/badlogic/pi-mono/pull/3248)). + ## [0.67.5] - 2026-04-16 ### Fixed diff --git a/packages/tui/src/terminal-image.ts b/packages/tui/src/terminal-image.ts index a8406a1c..b300d8cf 100644 --- a/packages/tui/src/terminal-image.ts +++ b/packages/tui/src/terminal-image.ts @@ -42,6 +42,16 @@ export function detectCapabilities(): TerminalCapabilities { const term = process.env.TERM?.toLowerCase() || ""; const colorTerm = process.env.COLORTERM?.toLowerCase() || ""; + // tmux and screen swallow OSC 8 by default (passthrough is opt-in and wraps + // sequences differently). Force hyperlinks off whenever we detect them, even + // when the outer terminal would otherwise support OSC 8. Image protocols are + // also unreliable under tmux/screen, so leave `images: null` for safety. + const inTmuxOrScreen = !!process.env.TMUX || term.startsWith("tmux") || term.startsWith("screen"); + if (inTmuxOrScreen) { + const trueColor = colorTerm === "truecolor" || colorTerm === "24bit"; + return { images: null, trueColor, hyperlinks: false }; + } + if (process.env.KITTY_WINDOW_ID || termProgram === "kitty") { return { images: "kitty", trueColor: true, hyperlinks: true }; } @@ -66,8 +76,12 @@ export function detectCapabilities(): TerminalCapabilities { return { images: null, trueColor: true, hyperlinks: true }; } + // Unknown terminal: be conservative. OSC 8 is rendered invisibly as "just + // text" on terminals that swallow it, which means the URL disappears from + // the rendered output. Default to the legacy `text (url)` behavior unless we + // have positively identified a hyperlink-capable terminal above. const trueColor = colorTerm === "truecolor" || colorTerm === "24bit"; - return { images: null, trueColor, hyperlinks: true }; + return { images: null, trueColor, hyperlinks: false }; } export function getCapabilities(): TerminalCapabilities { diff --git a/packages/tui/test/terminal-image.test.ts b/packages/tui/test/terminal-image.test.ts index feeebafe..dd67e6bf 100644 --- a/packages/tui/test/terminal-image.test.ts +++ b/packages/tui/test/terminal-image.test.ts @@ -4,7 +4,38 @@ import assert from "node:assert"; import { describe, it } from "node:test"; -import { hyperlink, isImageLine } from "../src/terminal-image.js"; +import { detectCapabilities, hyperlink, isImageLine } from "../src/terminal-image.js"; + +const ENV_KEYS = [ + "TERM", + "TERM_PROGRAM", + "COLORTERM", + "TMUX", + "KITTY_WINDOW_ID", + "GHOSTTY_RESOURCES_DIR", + "WEZTERM_PANE", + "ITERM_SESSION_ID", +] as const; + +function withEnv(overrides: Record, fn: () => void): void { + const saved: Record = {}; + for (const key of ENV_KEYS) { + saved[key] = process.env[key]; + delete process.env[key]; + } + try { + for (const [k, v] of Object.entries(overrides)) { + if (v === undefined) delete process.env[k]; + else process.env[k] = v; + } + fn(); + } finally { + for (const key of ENV_KEYS) { + if (saved[key] === undefined) delete process.env[key]; + else process.env[key] = saved[key]; + } + } +} describe("isImageLine", () => { describe("iTerm2 image protocol", () => { @@ -152,6 +183,75 @@ describe("isImageLine", () => { }); }); +describe("detectCapabilities", () => { + it("defaults to hyperlinks: false for unknown terminals", () => { + withEnv({}, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, false); + assert.strictEqual(caps.images, null); + }); + }); + + it("forces hyperlinks: false under tmux even if outer terminal supports OSC 8", () => { + withEnv({ TMUX: "/tmp/tmux-1000/default,1234,0", TERM_PROGRAM: "ghostty" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, false); + assert.strictEqual(caps.images, null); + }); + }); + + it("forces hyperlinks: false when TERM starts with 'tmux'", () => { + withEnv({ TERM: "tmux-256color", TERM_PROGRAM: "iterm.app" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, false); + assert.strictEqual(caps.images, null); + }); + }); + + it("forces hyperlinks: false when TERM starts with 'screen'", () => { + withEnv({ TERM: "screen-256color" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, false); + assert.strictEqual(caps.images, null); + }); + }); + + it("enables hyperlinks for Ghostty", () => { + withEnv({ TERM_PROGRAM: "ghostty" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, true); + }); + }); + + it("enables hyperlinks for Kitty", () => { + withEnv({ KITTY_WINDOW_ID: "1" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, true); + }); + }); + + it("enables hyperlinks for WezTerm", () => { + withEnv({ WEZTERM_PANE: "0" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, true); + }); + }); + + it("enables hyperlinks for iTerm2", () => { + withEnv({ TERM_PROGRAM: "iterm.app" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, true); + }); + }); + + it("enables hyperlinks for VSCode", () => { + withEnv({ TERM_PROGRAM: "vscode" }, () => { + const caps = detectCapabilities(); + assert.strictEqual(caps.hyperlinks, true); + }); + }); +}); + describe("hyperlink", () => { it("wraps text in OSC 8 open and close sequences", () => { const result = hyperlink("click me", "https://example.com"); From 45f1a2cd00d1d843bbd6d835e55134dc6215b97b Mon Sep 17 00:00:00 2001 From: Vegard Stikbakke Date: Thu, 16 Apr 2026 22:38:40 +0200 Subject: [PATCH 24/38] fix(ai): set session id headers for all OpenAI compatible responses (#3264) --- packages/ai/src/providers/openai-responses.ts | 2 +- .../openai-responses-copilot-provider.test.ts | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/ai/src/providers/openai-responses.ts b/packages/ai/src/providers/openai-responses.ts index cc99bbb0..b8a991b6 100644 --- a/packages/ai/src/providers/openai-responses.ts +++ b/packages/ai/src/providers/openai-responses.ts @@ -179,7 +179,7 @@ function createClient( Object.assign(headers, copilotHeaders); } - if (sessionId && model.provider === "openai" && model.baseUrl.includes("api.openai.com")) { + if (sessionId) { headers.session_id = sessionId; headers["x-client-request-id"] = sessionId; } diff --git a/packages/ai/test/openai-responses-copilot-provider.test.ts b/packages/ai/test/openai-responses-copilot-provider.test.ts index df1ccb8a..ef55603b 100644 --- a/packages/ai/test/openai-responses-copilot-provider.test.ts +++ b/packages/ai/test/openai-responses-copilot-provider.test.ts @@ -1,6 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { getModel } from "../src/models.js"; import { streamOpenAIResponses } from "../src/providers/openai-responses.js"; +import type { Model } from "../src/types.js"; type CapturedHeaders = Headers | string[][] | Record | undefined; @@ -22,6 +23,7 @@ function getHeader(headers: CapturedHeaders, name: string): string | null { async function captureOpenAIResponseHeaders( options: Parameters[2], + model: Model<"openai-responses"> = getModel("openai", "gpt-5.4"), ): Promise<{ sessionId: string | null; clientRequestId: string | null }> { const captured = { sessionId: null as string | null, clientRequestId: null as string | null }; vi.spyOn(globalThis, "fetch").mockImplementation(async (_input, init) => { @@ -34,7 +36,7 @@ async function captureOpenAIResponseHeaders( }); const stream = streamOpenAIResponses( - getModel("openai", "gpt-5.4"), + model, { systemPrompt: "sys", messages: [{ role: "user", content: "hi", timestamp: Date.now() }], @@ -95,6 +97,17 @@ describe("openai-responses provider defaults", () => { expect(captured).toEqual({ sessionId: "session-123", clientRequestId: "session-123" }); }); + it("sets cache-affinity headers for proxy OpenAI Responses requests with a sessionId", async () => { + const proxyModel: Model<"openai-responses"> = { + ...getModel("openai", "gpt-5.4"), + provider: "opencode", + baseUrl: "https://proxy.example.com/v1", + }; + const captured = await captureOpenAIResponseHeaders({ sessionId: "session-123" }, proxyModel); + + expect(captured).toEqual({ sessionId: "session-123", clientRequestId: "session-123" }); + }); + it("lets explicit headers override the default OpenAI cache-affinity headers", async () => { const captured = await captureOpenAIResponseHeaders({ sessionId: "session-123", From ab518d8651bbe08585e4fa21a6e98292b9b144cd Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 22:42:40 +0200 Subject: [PATCH 25/38] docs(ai): changelog for openai-responses session header fix closes #3196 --- packages/ai/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index f062c605..59640f73 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -7,6 +7,10 @@ - Added `onResponse` to `StreamOptions` so callers can inspect provider HTTP status and headers after each response arrives and before the response stream is consumed ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) - Added `thinkingDisplay` (`"summarized" | "omitted"`) to `AnthropicOptions` and `BedrockOptions`, wiring it through to the Anthropic/Bedrock `thinking` config. Defaults to `"summarized"` so Claude Opus 4.7 and Mythos Preview keep returning thinking text; set it to `"omitted"` to skip thinking streaming for faster time-to-first-text-token. +### Fixed + +- Fixed OpenAI Responses prompt caching for non-`api.openai.com` base URLs (OpenAI-compatible proxies such as litellm, theclawbay) by sending the `session_id` and `x-client-request-id` cache-affinity headers unconditionally when a `sessionId` is provided, matching the official Codex CLI behavior ([#3264](https://github.com/badlogic/pi-mono/pull/3264) by [@vegarsti](https://github.com/vegarsti)) + ## [0.67.5] - 2026-04-16 ### Fixed From c5451af749126387f1d677baad8d7f3a09199411 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 22:53:45 +0200 Subject: [PATCH 26/38] fix(coding-agent): make find tool match path-based glob patterns fd --glob matches against the basename unless --full-path is set, so patterns containing '/' (e.g. 'src/**/*.spec.ts') silently returned no results. When the pattern contains '/', switch fd into --full-path mode and prepend '**/' unless the pattern already starts with '/', '**/', or is '**'. Basename patterns keep the default matcher. closes #3302 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/core/tools/find.ts | 13 +++- .../regressions/3302-find-path-glob.test.ts | 72 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packages/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index eea5c9e3..60183d2f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed `--verbose` startup output to begin with expanded startup help and loaded resource listings after the compact startup header change ([#3147](https://github.com/badlogic/pi-mono/issues/3147)) +- Fixed `find` tool returning no results for path-based glob patterns such as `src/**/*.spec.ts` or `some/parent/child/**` by switching fd into full-path mode and normalizing the pattern when it contains a `/` ([#3302](https://github.com/badlogic/pi-mono/issues/3302)) ## [0.67.5] - 2026-04-16 diff --git a/packages/coding-agent/src/core/tools/find.ts b/packages/coding-agent/src/core/tools/find.ts index 9e9e95b2..92afc275 100644 --- a/packages/coding-agent/src/core/tools/find.ts +++ b/packages/coding-agent/src/core/tools/find.ts @@ -258,7 +258,18 @@ export function createFindToolDefinition( return; } for (const gitignorePath of gitignoreFiles) args.push("--ignore-file", gitignorePath); - args.push(pattern, searchPath); + + // fd --glob matches against the basename unless --full-path is set; in --full-path + // mode it matches against the absolute candidate path, so a path-containing + // pattern like 'src/**/*.spec.ts' needs a leading '**/' to match anything. + let effectivePattern = pattern; + if (pattern.includes("/")) { + args.push("--full-path"); + if (!pattern.startsWith("/") && !pattern.startsWith("**/") && pattern !== "**") { + effectivePattern = `**/${pattern}`; + } + } + args.push(effectivePattern, searchPath); const child = spawn(fdPath, args, { stdio: ["ignore", "pipe", "pipe"] }); const rl = createInterface({ input: child.stdout }); diff --git a/packages/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts b/packages/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts new file mode 100644 index 00000000..0f19cb14 --- /dev/null +++ b/packages/coding-agent/test/suite/regressions/3302-find-path-glob.test.ts @@ -0,0 +1,72 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { createFindToolDefinition } from "../../../src/core/tools/find.js"; + +/** + * Regression test for https://github.com/badlogic/pi-mono/issues/3302 + * + * The `find` tool advertises glob patterns like `src/**\/*.spec.ts`, but the + * default fd-backed implementation used `fd --glob ` without + * `--full-path`, which makes fd match only against the basename. Any pattern + * containing a `/` therefore silently returned no matches. + * + * The fix switches fd into full-path mode when the pattern contains a `/` + * and prepends `**\/` so the pattern can match against the absolute candidate + * path that fd feeds to the matcher. + */ +describe("issue #3302 find returns no results for path-based glob patterns", () => { + let tempRoot: string; + + beforeEach(() => { + tempRoot = mkdtempSync(join(tmpdir(), "pi-3302-")); + mkdirSync(join(tempRoot, "some", "parent", "child"), { recursive: true }); + mkdirSync(join(tempRoot, "src", "foo", "bar"), { recursive: true }); + writeFileSync(join(tempRoot, "some", "parent", "child", "file.ext"), ""); + writeFileSync(join(tempRoot, "some", "parent", "child", "test.spec.ts"), ""); + writeFileSync(join(tempRoot, "src", "foo", "bar", "example.spec.ts"), ""); + }); + + afterEach(() => { + rmSync(tempRoot, { recursive: true, force: true }); + }); + + async function runFind(pattern: string): Promise { + const def = createFindToolDefinition(tempRoot); + // The find tool implementation does not touch ctx; pass a minimal stub. + const ctx = {} as Parameters[4]; + const result = (await def.execute("call-1", { pattern }, undefined, undefined, ctx)) as { + content: Array<{ type: string; text?: string }>; + }; + const text = result.content[0]?.text ?? ""; + if (text === "No files found matching pattern") return []; + return text + .split("\n") + .map((l) => l.trim()) + .filter((l) => l.length > 0 && !l.startsWith("[")); + } + + it("basename pattern still matches (regression-safe)", async () => { + const files = await runFind("*.spec.ts"); + expect(files.sort()).toEqual(["some/parent/child/test.spec.ts", "src/foo/bar/example.spec.ts"]); + }); + + it("directory-prefixed pattern with ** tail matches subtree", async () => { + const files = await runFind("some/parent/child/**"); + // Matches files (and possibly directories) under the subtree. Assert the two files are present. + expect(files).toContain("some/parent/child/file.ext"); + expect(files).toContain("some/parent/child/test.spec.ts"); + }); + + it("leading ** wildcard with path segments matches", async () => { + const files = await runFind("**/parent/child/*"); + expect(files.sort()).toContain("some/parent/child/file.ext"); + expect(files.sort()).toContain("some/parent/child/test.spec.ts"); + }); + + it("src/**/*.spec.ts matches nested spec file", async () => { + const files = await runFind("src/**/*.spec.ts"); + expect(files).toEqual(["src/foo/bar/example.spec.ts"]); + }); +}); From f84c4c89f5feba98f3797d9a9b04135cf1c0d810 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 22:57:30 +0200 Subject: [PATCH 27/38] fix(ai): restore FakeOpenAI mock .withResponse() so openai-completions tests pass The openai-completions provider now calls .withResponse() on the create() result to surface HTTP status/headers for the new onResponse hook (d131fcd4). The FakeOpenAI test mock only returned an async iterable, so 4 tests in openai-completions-tool-choice.test.ts that actually consume the stream failed with "withResponse is not a function". Updated the mock to return a Promise augmented with .withResponse() resolving to { data: , response: { status, headers } }. closes #3304 --- .../test/openai-completions-tool-choice.test.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index 62185d3e..485e973e 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -24,9 +24,9 @@ vi.mock("openai", () => { class FakeOpenAI { chat = { completions: { - create: async (params: unknown) => { + create: (params: unknown) => { mockState.lastParams = params; - return { + const stream = { async *[Symbol.asyncIterator]() { const chunks = mockState.chunks ?? [ { @@ -44,6 +44,17 @@ vi.mock("openai", () => { } }, }; + const promise = Promise.resolve(stream) as Promise & { + withResponse: () => Promise<{ + data: typeof stream; + response: { status: number; headers: Headers }; + }>; + }; + promise.withResponse = async () => ({ + data: stream, + response: { status: 200, headers: new Headers() }, + }); + return promise; }, }, }; From aa25726ebf2c104f80537a0327a5ba11de1665b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Vinueza?= Date: Thu, 16 Apr 2026 16:02:35 -0500 Subject: [PATCH 28/38] feat(coding-agent,tui): support argument-hint frontmatter in prompt templates (#2780) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(coding-agent,tui): support argument-hint frontmatter in prompt templates Parse argument-hint from prompt template frontmatter and display it in the autocomplete dropdown description, matching Claude Code's convention for custom commands. Frontmatter format: --- description: Code review argument-hint: "[file | #PR | PR-URL]" --- The hint renders in the description column of the autocomplete list: review [file | #PR | PR-URL] — Code review Closes #2761 * docs(coding-agent,tui): add argument-hint documentation, tests, and built-in hints - Document argument-hint frontmatter in prompt-templates.md with /[optional] convention - Add argument-hint to built-in prompts: pr, is, wr - Expand tests: required/optional hints, missing hints, empty hints, special characters - Add changelog entries for coding-agent and tui --- .pi/prompts/is.md | 1 + .pi/prompts/pr.md | 1 + .pi/prompts/wr.md | 1 + packages/coding-agent/CHANGELOG.md | 2 + .../coding-agent/docs/prompt-templates.md | 21 +++ .../coding-agent/src/core/prompt-templates.ts | 2 + .../src/modes/interactive/interactive-mode.ts | 1 + .../test/prompt-templates.test.ts | 127 +++++++++++++++++- packages/tui/CHANGELOG.md | 4 + packages/tui/src/autocomplete.ts | 17 ++- 10 files changed, 170 insertions(+), 7 deletions(-) diff --git a/.pi/prompts/is.md b/.pi/prompts/is.md index cf7da408..3dee06a5 100644 --- a/.pi/prompts/is.md +++ b/.pi/prompts/is.md @@ -1,5 +1,6 @@ --- description: Analyze GitHub issues (bugs or feature requests) +argument-hint: "" --- Analyze GitHub issue(s): $ARGUMENTS diff --git a/.pi/prompts/pr.md b/.pi/prompts/pr.md index cda6ad79..b8ce2da6 100644 --- a/.pi/prompts/pr.md +++ b/.pi/prompts/pr.md @@ -1,5 +1,6 @@ --- description: Review PRs from URLs with structured issue and code analysis +argument-hint: "" --- You are given one or more GitHub PR URLs: $@ diff --git a/.pi/prompts/wr.md b/.pi/prompts/wr.md index fa0250bc..65108e9c 100644 --- a/.pi/prompts/wr.md +++ b/.pi/prompts/wr.md @@ -1,5 +1,6 @@ --- description: Finish the current task end-to-end with changelog, commit, and push +argument-hint: "[instructions]" --- Wrap it. diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 60183d2f..f63d4f33 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -270,6 +270,8 @@ await runtime.fork("entry-id"); - Added label timestamps to the session tree with a `Shift+T` toggle in `/tree`, smart date formatting, and timestamp preservation through branching ([#2691](https://github.com/badlogic/pi-mono/pull/2691) by [@w-winter](https://github.com/w-winter)) +- Added `argument-hint` frontmatter field for prompt templates, displayed before the description in the autocomplete dropdown ([#2761](https://github.com/badlogic/pi-mono/issues/2761)) + ### Fixed - Fixed startup resource loading to reuse the initial `ResourceLoader` for the first runtime, so extensions are not loaded twice before session startup and `session_start` handlers still fire for singleton-style extensions ([#2766](https://github.com/badlogic/pi-mono/issues/2766)) diff --git a/packages/coding-agent/docs/prompt-templates.md b/packages/coding-agent/docs/prompt-templates.md index d6e540bb..056d2c9a 100644 --- a/packages/coding-agent/docs/prompt-templates.md +++ b/packages/coding-agent/docs/prompt-templates.md @@ -30,6 +30,27 @@ Review the staged changes (`git diff --cached`). Focus on: - The filename becomes the command name. `review.md` becomes `/review`. - `description` is optional. If missing, the first non-empty line is used. +- `argument-hint` is optional. When set, the hint is displayed before the description in the autocomplete dropdown. + +### Argument Hints + +Use `argument-hint` in frontmatter to show expected arguments in autocomplete. Use `` for required arguments and `[square brackets]` for optional ones: + +```markdown +--- +description: Review PRs from URLs with structured issue and code analysis +argument-hint: "" +--- +``` + +This renders in the autocomplete dropdown as: + +``` +→ pr — Review PRs from URLs with structured issue and code analysis + is — Analyze GitHub issues (bugs or feature requests) + wr [instructions] — Finish the current task end-to-end + cl — Audit changelog entries before release +``` ## Usage diff --git a/packages/coding-agent/src/core/prompt-templates.ts b/packages/coding-agent/src/core/prompt-templates.ts index 7c5dbc3f..4850351f 100644 --- a/packages/coding-agent/src/core/prompt-templates.ts +++ b/packages/coding-agent/src/core/prompt-templates.ts @@ -11,6 +11,7 @@ import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js"; export interface PromptTemplate { name: string; description: string; + argumentHint?: string; content: string; sourceInfo: SourceInfo; filePath: string; // Absolute path to the template file @@ -121,6 +122,7 @@ function loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptT return { name, description, + ...(frontmatter["argument-hint"] && { argumentHint: frontmatter["argument-hint"] }), content: body, sourceInfo, filePath, diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 4630c527..94d7b414 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -432,6 +432,7 @@ export class InteractiveMode { const templateCommands: SlashCommand[] = this.session.promptTemplates.map((cmd) => ({ name: cmd.name, description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo), + ...(cmd.argumentHint && { argumentHint: cmd.argumentHint }), })); // Convert extension commands to SlashCommand format diff --git a/packages/coding-agent/test/prompt-templates.test.ts b/packages/coding-agent/test/prompt-templates.test.ts index 168f7407..bef43f44 100644 --- a/packages/coding-agent/test/prompt-templates.test.ts +++ b/packages/coding-agent/test/prompt-templates.test.ts @@ -8,8 +8,11 @@ * - Edge cases and integration between parsing and substitution */ -import { describe, expect, test } from "vitest"; -import { parseCommandArgs, substituteArgs } from "../src/core/prompt-templates.js"; +import { mkdirSync, rmSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { afterAll, describe, expect, test } from "vitest"; +import { loadPromptTemplates, parseCommandArgs, substituteArgs } from "../src/core/prompt-templates.js"; // ============================================================================ // substituteArgs @@ -379,3 +382,123 @@ describe("parseCommandArgs + substituteArgs integration", () => { expect(substituteArgs(template1, args)).toBe(substituteArgs(template2, args)); }); }); + +// ============================================================================ +// loadPromptTemplates - argument-hint frontmatter +// ============================================================================ + +describe("loadPromptTemplates - argument-hint", () => { + const testDir = join(tmpdir(), `pi-test-prompts-${Date.now()}`); + + function writeTemplate(name: string, content: string) { + mkdirSync(testDir, { recursive: true }); + writeFileSync(join(testDir, `${name}.md`), content); + } + + test("should parse required argument-hint from frontmatter", () => { + writeTemplate( + "pr", + `--- +description: Review PRs from URLs with structured issue and code analysis +argument-hint: "" +--- +You are given one or more GitHub PR URLs: $@`, + ); + + const templates = loadPromptTemplates({ + promptPaths: [testDir], + includeDefaults: false, + }); + + const pr = templates.find((t) => t.name === "pr"); + expect(pr).toBeDefined(); + expect(pr!.argumentHint).toBe(""); + expect(pr!.description).toBe("Review PRs from URLs with structured issue and code analysis"); + }); + + test("should parse optional argument-hint from frontmatter", () => { + writeTemplate( + "wr", + `--- +description: Finish the current task end-to-end with changelog, commit, and push +argument-hint: "[instructions]" +--- +Wrap it. Additional instructions: $ARGUMENTS`, + ); + + const templates = loadPromptTemplates({ + promptPaths: [testDir], + includeDefaults: false, + }); + + const wr = templates.find((t) => t.name === "wr"); + expect(wr).toBeDefined(); + expect(wr!.argumentHint).toBe("[instructions]"); + expect(wr!.description).toBe("Finish the current task end-to-end with changelog, commit, and push"); + }); + + test("should leave argumentHint undefined when not specified", () => { + writeTemplate( + "cl", + `--- +description: Audit changelog entries before release +--- +Audit changelog entries for all commits since the last release.`, + ); + + const templates = loadPromptTemplates({ + promptPaths: [testDir], + includeDefaults: false, + }); + + const cl = templates.find((t) => t.name === "cl"); + expect(cl).toBeDefined(); + expect(cl!.argumentHint).toBeUndefined(); + }); + + test("should ignore empty argument-hint", () => { + writeTemplate( + "empty-hint", + `--- +description: A command with empty hint +argument-hint: "" +--- +Do something`, + ); + + const templates = loadPromptTemplates({ + promptPaths: [testDir], + includeDefaults: false, + }); + + const tmpl = templates.find((t) => t.name === "empty-hint"); + expect(tmpl).toBeDefined(); + expect(tmpl!.argumentHint).toBeUndefined(); + }); + + test("should preserve argument-hint with special characters", () => { + writeTemplate( + "is", + `--- +description: Analyze GitHub issues (bugs or feature requests) +argument-hint: "" +--- +Analyze GitHub issue(s): $ARGUMENTS`, + ); + + const templates = loadPromptTemplates({ + promptPaths: [testDir], + includeDefaults: false, + }); + + const is = templates.find((t) => t.name === "is"); + expect(is).toBeDefined(); + expect(is!.argumentHint).toBe(""); + }); + + afterAll(() => { + try { + rmSync(testDir, { recursive: true, force: true }); + } catch {} + }); +}); diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 57ddcd8e..628628a7 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -62,6 +62,10 @@ - Fixed slash-command argument autocomplete to await async `getArgumentCompletions()` results and ignore invalid return values, preventing crashes when extension commands provide asynchronous completions ([#2719](https://github.com/badlogic/pi-mono/issues/2719)) - Fixed non-capturing overlay padding from inflating scrollback and corrupting the viewport on terminal widen ([#2758](https://github.com/badlogic/pi-mono/pull/2758) by [@dotBeeps](https://github.com/dotBeeps)) +### Added + +- Added `argumentHint` to `SlashCommand` interface, displayed before the description in the autocomplete dropdown ([#2761](https://github.com/badlogic/pi-mono/issues/2761)) + ## [0.64.0] - 2026-03-29 ### Fixed diff --git a/packages/tui/src/autocomplete.ts b/packages/tui/src/autocomplete.ts index 792f119b..4ff1b6b7 100644 --- a/packages/tui/src/autocomplete.ts +++ b/packages/tui/src/autocomplete.ts @@ -223,6 +223,7 @@ type Awaitable = T | Promise; export interface SlashCommand { name: string; description?: string; + argumentHint?: string; // Function to get argument completions for this command // Returns null if no argument completion is available getArgumentCompletions?(argumentPrefix: string): Awaitable; @@ -303,11 +304,17 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider { if (spaceIndex === -1) { const prefix = textBeforeCursor.slice(1); - const commandItems = this.commands.map((cmd) => ({ - name: "name" in cmd ? cmd.name : cmd.value, - label: "name" in cmd ? cmd.name : cmd.label, - description: cmd.description, - })); + const commandItems = this.commands.map((cmd) => { + const name = "name" in cmd ? cmd.name : cmd.value; + const hint = "argumentHint" in cmd && cmd.argumentHint ? cmd.argumentHint : undefined; + const desc = cmd.description ?? ""; + const fullDesc = hint ? (desc ? `${hint} — ${desc}` : hint) : desc; + return { + name, + label: name, + description: fullDesc || undefined, + }; + }); const filtered = fuzzyFilter(commandItems, prefix, (item) => item.name).map((item) => ({ value: item.name, From 1d4fdbad2658bc81cbbe1afa05a1fa224a5ebc57 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 23:03:33 +0200 Subject: [PATCH 29/38] fix(coding-agent): scope nested .gitignore rules to their subtree in find The find tool previously collected every .gitignore under the search path and passed them to fd via --ignore-file. fd treats --ignore-file entries as a single global ignore source, so rules from a/.gitignore also filtered files under sibling b/. Drop the manual collection and pass --no-require-git instead, which makes fd apply hierarchical .gitignore semantics whether or not a git repo is present. closes #3303 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/core/tools/find.ts | 31 +------ .../3303-find-nested-gitignore.test.ts | 83 +++++++++++++++++++ 3 files changed, 88 insertions(+), 27 deletions(-) create mode 100644 packages/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f63d4f33..af115492 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -6,6 +6,7 @@ - Fixed `--verbose` startup output to begin with expanded startup help and loaded resource listings after the compact startup header change ([#3147](https://github.com/badlogic/pi-mono/issues/3147)) - Fixed `find` tool returning no results for path-based glob patterns such as `src/**/*.spec.ts` or `some/parent/child/**` by switching fd into full-path mode and normalizing the pattern when it contains a `/` ([#3302](https://github.com/badlogic/pi-mono/issues/3302)) +- Fixed `find` tool applying nested `.gitignore` rules across sibling directories (e.g. rules from `a/.gitignore` hiding matching files under `b/`) by dropping the manual `--ignore-file` collection and delegating to fd's hierarchical `.gitignore` handling via `--no-require-git` ([#3303](https://github.com/badlogic/pi-mono/issues/3303)) ## [0.67.5] - 2026-04-16 diff --git a/packages/coding-agent/src/core/tools/find.ts b/packages/coding-agent/src/core/tools/find.ts index 92afc275..0a8cbc2a 100644 --- a/packages/coding-agent/src/core/tools/find.ts +++ b/packages/coding-agent/src/core/tools/find.ts @@ -4,7 +4,6 @@ import { Text } from "@mariozechner/pi-tui"; import { type Static, Type } from "@sinclair/typebox"; import { spawn } from "child_process"; import { existsSync } from "fs"; -import { glob } from "glob"; import path from "path"; import { keyHint } from "../../modes/interactive/components/keybinding-hints.js"; import { ensureTool } from "../../utils/tools-manager.js"; @@ -225,39 +224,17 @@ export function createFindToolDefinition( return; } - // Build fd arguments. + // Build fd arguments. --no-require-git makes fd apply hierarchical .gitignore + // semantics whether or not the search path is inside a git repository, without + // leaking sibling-directory rules the way --ignore-file (a global source) would. const args: string[] = [ "--glob", "--color=never", "--hidden", + "--no-require-git", "--max-results", String(effectiveLimit), ]; - // Include .gitignore files from the search tree. - const gitignoreFiles = new Set(); - const rootGitignore = path.join(searchPath, ".gitignore"); - if (existsSync(rootGitignore)) gitignoreFiles.add(rootGitignore); - try { - const nestedGitignores = await glob("**/.gitignore", { - cwd: searchPath, - dot: true, - absolute: true, - ignore: ["**/node_modules/**", "**/.git/**"], - signal, - }); - for (const file of nestedGitignores) gitignoreFiles.add(file); - } catch { - if (signal?.aborted) { - settle(() => reject(new Error("Operation aborted"))); - return; - } - // ignore - } - if (signal?.aborted) { - settle(() => reject(new Error("Operation aborted"))); - return; - } - for (const gitignorePath of gitignoreFiles) args.push("--ignore-file", gitignorePath); // fd --glob matches against the basename unless --full-path is set; in --full-path // mode it matches against the absolute candidate path, so a path-containing diff --git a/packages/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts b/packages/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts new file mode 100644 index 00000000..15c8658a --- /dev/null +++ b/packages/coding-agent/test/suite/regressions/3303-find-nested-gitignore.test.ts @@ -0,0 +1,83 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { createFindToolDefinition } from "../../../src/core/tools/find.js"; + +/** + * Regression test for https://github.com/badlogic/pi-mono/issues/3303 + * + * The `find` tool previously collected every `.gitignore` under the search + * path and passed them to `fd` via `--ignore-file`. fd treats `--ignore-file` + * entries as a single global ignore source, so rules from `a/.gitignore` + * also filtered files under sibling `b/`. The fix switches to fd's + * hierarchical `.gitignore` handling via `--no-require-git` and drops the + * manual collection. + */ +describe("issue #3303 nested .gitignore rules leak into sibling directories", () => { + let tempRoot: string; + + async function runFind(pattern: string): Promise { + const def = createFindToolDefinition(tempRoot); + const ctx = {} as Parameters[4]; + const result = (await def.execute("call-1", { pattern }, undefined, undefined, ctx)) as { + content: Array<{ type: string; text?: string }>; + }; + const text = result.content[0]?.text ?? ""; + if (text === "No files found matching pattern") return []; + return text + .split("\n") + .map((l) => l.trim()) + .filter((l) => l.length > 0 && !l.startsWith("[")) + .sort(); + } + + afterEach(() => { + if (tempRoot) rmSync(tempRoot, { recursive: true, force: true }); + }); + + describe("flat sibling case", () => { + beforeEach(() => { + tempRoot = mkdtempSync(join(tmpdir(), "pi-3303-flat-")); + mkdirSync(join(tempRoot, "a"), { recursive: true }); + mkdirSync(join(tempRoot, "b"), { recursive: true }); + writeFileSync(join(tempRoot, "a", ".gitignore"), "ignored.txt\n"); + writeFileSync(join(tempRoot, "a", "ignored.txt"), ""); + writeFileSync(join(tempRoot, "a", "kept.txt"), ""); + writeFileSync(join(tempRoot, "b", "ignored.txt"), ""); + writeFileSync(join(tempRoot, "b", "kept.txt"), ""); + writeFileSync(join(tempRoot, "root.txt"), ""); + }); + + it("applies a/.gitignore only inside a/ and leaves b/ untouched", async () => { + const files = await runFind("**/*.txt"); + expect(files).toEqual(["a/kept.txt", "b/ignored.txt", "b/kept.txt", "root.txt"]); + }); + }); + + describe("deeply nested case", () => { + beforeEach(() => { + tempRoot = mkdtempSync(join(tmpdir(), "pi-3303-deep-")); + mkdirSync(join(tempRoot, "a", "deep"), { recursive: true }); + mkdirSync(join(tempRoot, "b"), { recursive: true }); + writeFileSync(join(tempRoot, "a", ".gitignore"), "ignored.txt\n"); + writeFileSync(join(tempRoot, "a", "deep", ".gitignore"), "secret.txt\n"); + writeFileSync(join(tempRoot, "a", "ignored.txt"), ""); + writeFileSync(join(tempRoot, "a", "kept.txt"), ""); + writeFileSync(join(tempRoot, "a", "deep", "ignored.txt"), ""); + writeFileSync(join(tempRoot, "a", "deep", "secret.txt"), ""); + writeFileSync(join(tempRoot, "a", "deep", "kept.txt"), ""); + writeFileSync(join(tempRoot, "b", "ignored.txt"), ""); + writeFileSync(join(tempRoot, "b", "kept.txt"), ""); + writeFileSync(join(tempRoot, "root.txt"), ""); + }); + + it("scopes each .gitignore to its own subtree", async () => { + const files = await runFind("**/*.txt"); + // a/.gitignore ignores 'ignored.txt' within a/ and a/deep/. + // a/deep/.gitignore additionally ignores 'secret.txt' within a/deep/. + // b/ is untouched by either. + expect(files).toEqual(["a/deep/kept.txt", "a/kept.txt", "b/ignored.txt", "b/kept.txt", "root.txt"]); + }); + }); +}); From 9f523babcda2607db90bd454c36bbaecce49beb5 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 23:16:20 +0200 Subject: [PATCH 30/38] docs(coding-agent,tui): move argument-hint changelog entries to [Unreleased] Move the argument-hint entries added in #2780 out of the already-released 0.65.0 sections and into [Unreleased], and credit the external contributor with a PR link per AGENTS.md changelog rules. --- packages/coding-agent/CHANGELOG.md | 6 ++++-- packages/tui/CHANGELOG.md | 5 +---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index af115492..7afb52b0 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added `argument-hint` frontmatter field for prompt templates, displayed before the description in the autocomplete dropdown ([#2780](https://github.com/badlogic/pi-mono/pull/2780) by [@andresvi94](https://github.com/andresvi94)) + ### Fixed - Fixed `--verbose` startup output to begin with expanded startup help and loaded resource listings after the compact startup header change ([#3147](https://github.com/badlogic/pi-mono/issues/3147)) @@ -271,8 +275,6 @@ await runtime.fork("entry-id"); - Added label timestamps to the session tree with a `Shift+T` toggle in `/tree`, smart date formatting, and timestamp preservation through branching ([#2691](https://github.com/badlogic/pi-mono/pull/2691) by [@w-winter](https://github.com/w-winter)) -- Added `argument-hint` frontmatter field for prompt templates, displayed before the description in the autocomplete dropdown ([#2761](https://github.com/badlogic/pi-mono/issues/2761)) - ### Fixed - Fixed startup resource loading to reuse the initial `ResourceLoader` for the first runtime, so extensions are not loaded twice before session startup and `session_start` handlers still fire for singleton-style extensions ([#2766](https://github.com/badlogic/pi-mono/issues/2766)) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 628628a7..d4e6e991 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Added OSC 8 hyperlink rendering for markdown links when the terminal advertises support. Introduces a public `hyperlink(text, url)` helper and a `setCapabilities()` test override in `packages/tui` ([#3248](https://github.com/badlogic/pi-mono/pull/3248) by [@ofa1](https://github.com/ofa1)). +- Added `argumentHint` to `SlashCommand` interface, displayed before the description in the autocomplete dropdown ([#2780](https://github.com/badlogic/pi-mono/pull/2780) by [@andresvi94](https://github.com/andresvi94)) ### Changed @@ -62,10 +63,6 @@ - Fixed slash-command argument autocomplete to await async `getArgumentCompletions()` results and ignore invalid return values, preventing crashes when extension commands provide asynchronous completions ([#2719](https://github.com/badlogic/pi-mono/issues/2719)) - Fixed non-capturing overlay padding from inflating scrollback and corrupting the viewport on terminal widen ([#2758](https://github.com/badlogic/pi-mono/pull/2758) by [@dotBeeps](https://github.com/dotBeeps)) -### Added - -- Added `argumentHint` to `SlashCommand` interface, displayed before the description in the autocomplete dropdown ([#2761](https://github.com/badlogic/pi-mono/issues/2761)) - ## [0.64.0] - 2026-03-29 ### Fixed From cab5f758e7c364970c8692012d8bf60e11abacf6 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 23:23:58 +0200 Subject: [PATCH 31/38] docs(coding-agent): audit [Unreleased] entries since v0.67.5 Add missing entries and cross-package duplications: - after_provider_response extension hook (#3128) - Compact startup header with Ctrl+O toggle (#3267) - preset example: restore original state on (none) (#3272) - OSC 8 hyperlinks in markdown (#3248) - Hyperlink capability detection tightening (#3248) - OpenAI Responses session_id headers for proxies (#3264) Add New Features summary at the top of [Unreleased]. --- packages/coding-agent/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 7afb52b0..7536e6c4 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,15 +2,31 @@ ## [Unreleased] +### New Features + +- Prompt templates support an `argument-hint` frontmatter field that renders before the description in the `/` autocomplete dropdown, using `` for required and `[square]` for optional arguments. See [docs/prompt-templates.md#argument-hints](docs/prompt-templates.md#argument-hints). +- New `after_provider_response` extension hook lets extensions inspect provider HTTP status codes and headers immediately after each response is received and before stream consumption begins. See [docs/extensions.md](docs/extensions.md). +- Compact interactive startup header with a comma-separated view of loaded AGENTS.md files, prompt templates, skills, and extensions. Press `Ctrl+O` to toggle the expanded listing. +- Markdown links in assistant output now render as OSC 8 hyperlinks on terminals that advertise support; unknown terminals and tmux/screen default to plain text so URLs are never silently dropped. + ### Added - Added `argument-hint` frontmatter field for prompt templates, displayed before the description in the autocomplete dropdown ([#2780](https://github.com/badlogic/pi-mono/pull/2780) by [@andresvi94](https://github.com/andresvi94)) +- Added `after_provider_response` extension hook so extensions can inspect provider HTTP status codes and headers after each provider response is received and before stream consumption begins ([#3128](https://github.com/badlogic/pi-mono/issues/3128)) +- Added OSC 8 hyperlink rendering for markdown links when the terminal advertises support ([#3248](https://github.com/badlogic/pi-mono/pull/3248) by [@ofa1](https://github.com/ofa1)) + +### Changed + +- Changed interactive startup header to a compact, comma-separated view of loaded AGENTS.md files, prompt templates, skills, and extensions, with `Ctrl+O` to toggle the expanded listing ([#3267](https://github.com/badlogic/pi-mono/pull/3267)) +- Tightened hyperlink capability detection to default `hyperlinks: false` for unknown terminals and force it off under tmux/screen (including nested sessions), preventing markdown link URLs from disappearing on terminals that silently swallow OSC 8 sequences ([#3248](https://github.com/badlogic/pi-mono/pull/3248)) ### Fixed - Fixed `--verbose` startup output to begin with expanded startup help and loaded resource listings after the compact startup header change ([#3147](https://github.com/badlogic/pi-mono/issues/3147)) - Fixed `find` tool returning no results for path-based glob patterns such as `src/**/*.spec.ts` or `some/parent/child/**` by switching fd into full-path mode and normalizing the pattern when it contains a `/` ([#3302](https://github.com/badlogic/pi-mono/issues/3302)) - Fixed `find` tool applying nested `.gitignore` rules across sibling directories (e.g. rules from `a/.gitignore` hiding matching files under `b/`) by dropping the manual `--ignore-file` collection and delegating to fd's hierarchical `.gitignore` handling via `--no-require-git` ([#3303](https://github.com/badlogic/pi-mono/issues/3303)) +- Fixed OpenAI Responses prompt caching for non-`api.openai.com` base URLs (OpenAI-compatible proxies such as litellm, theclawbay) by sending the `session_id` and `x-client-request-id` cache-affinity headers unconditionally when a `sessionId` is provided, matching the official Codex CLI behavior ([#3264](https://github.com/badlogic/pi-mono/pull/3264) by [@vegarsti](https://github.com/vegarsti)) +- Fixed the `preset` example extension to snapshot the active model, thinking level, and tool set on the first preset application and restore that state when cycling back to `(none)`, instead of falling back to a hardcoded default tool list ([#3272](https://github.com/badlogic/pi-mono/pull/3272) by [@stembi](https://github.com/stembi)) ## [0.67.5] - 2026-04-16 From 2a356dca4d949a28d0ede1b9586fadc948bf7f85 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 23:25:54 +0200 Subject: [PATCH 32/38] Release v0.67.6 --- package-lock.json | 735 +++++++----------- packages/agent/CHANGELOG.md | 2 +- packages/agent/package.json | 4 +- packages/ai/CHANGELOG.md | 2 +- packages/ai/package.json | 2 +- packages/coding-agent/CHANGELOG.md | 2 +- .../package-lock.json | 4 +- .../custom-provider-anthropic/package.json | 2 +- .../custom-provider-gitlab-duo/package.json | 2 +- .../custom-provider-qwen-cli/package.json | 2 +- .../extensions/with-deps/package-lock.json | 4 +- .../extensions/with-deps/package.json | 2 +- packages/coding-agent/package.json | 8 +- packages/mom/CHANGELOG.md | 2 +- packages/mom/package.json | 8 +- packages/pods/package.json | 4 +- packages/tui/CHANGELOG.md | 2 +- packages/tui/package.json | 2 +- packages/web-ui/CHANGELOG.md | 2 +- packages/web-ui/example/package.json | 2 +- packages/web-ui/package.json | 6 +- 21 files changed, 329 insertions(+), 470 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16022ac2..d6ef41ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -223,56 +223,56 @@ } }, "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1030.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1030.0.tgz", - "integrity": "sha512-5Lnyx6mQPsIdld5Xr9FJqu8Hi9RVY6SgE8Rysmn4r3lRY2vNohNEu+gCtdXRDkkv/PgK9OnbA0sUPFU9rBRMYA==", + "version": "3.1031.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1031.0.tgz", + "integrity": "sha512-ZgiSo2wslPXlv7wK4m2ULu2VfimbVRRlho0DqXhlvZGEqvtC209cMOxfPZWJ79Fz9sf0IzmWFkDtvMYjnwyLfw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/credential-provider-node": "^3.972.30", - "@aws-sdk/eventstream-handler-node": "^3.972.13", - "@aws-sdk/middleware-eventstream": "^3.972.9", - "@aws-sdk/middleware-host-header": "^3.972.9", - "@aws-sdk/middleware-logger": "^3.972.9", - "@aws-sdk/middleware-recursion-detection": "^3.972.10", - "@aws-sdk/middleware-user-agent": "^3.972.29", - "@aws-sdk/middleware-websocket": "^3.972.15", - "@aws-sdk/region-config-resolver": "^3.972.11", - "@aws-sdk/token-providers": "3.1030.0", - "@aws-sdk/types": "^3.973.7", - "@aws-sdk/util-endpoints": "^3.996.6", - "@aws-sdk/util-user-agent-browser": "^3.972.9", - "@aws-sdk/util-user-agent-node": "^3.973.15", - "@smithy/config-resolver": "^4.4.14", - "@smithy/core": "^3.23.14", - "@smithy/eventstream-serde-browser": "^4.2.13", - "@smithy/eventstream-serde-config-resolver": "^4.3.13", - "@smithy/eventstream-serde-node": "^4.2.13", - "@smithy/fetch-http-handler": "^5.3.16", - "@smithy/hash-node": "^4.2.13", - "@smithy/invalid-dependency": "^4.2.13", - "@smithy/middleware-content-length": "^4.2.13", - "@smithy/middleware-endpoint": "^4.4.29", - "@smithy/middleware-retry": "^4.5.0", - "@smithy/middleware-serde": "^4.2.17", - "@smithy/middleware-stack": "^4.2.13", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/node-http-handler": "^4.5.2", - "@smithy/protocol-http": "^5.3.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/credential-provider-node": "^3.972.31", + "@aws-sdk/eventstream-handler-node": "^3.972.14", + "@aws-sdk/middleware-eventstream": "^3.972.10", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.30", + "@aws-sdk/middleware-websocket": "^3.972.16", + "@aws-sdk/region-config-resolver": "^3.972.12", + "@aws-sdk/token-providers": "3.1031.0", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.7", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.16", + "@smithy/config-resolver": "^4.4.16", + "@smithy/core": "^3.23.15", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/eventstream-serde-config-resolver": "^4.3.14", + "@smithy/eventstream-serde-node": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.30", + "@smithy/middleware-retry": "^4.5.3", + "@smithy/middleware-serde": "^4.2.18", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.5.3", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.45", - "@smithy/util-defaults-mode-node": "^4.2.49", - "@smithy/util-endpoints": "^3.3.4", - "@smithy/util-middleware": "^4.2.13", - "@smithy/util-retry": "^4.3.0", - "@smithy/util-stream": "^4.5.22", + "@smithy/util-defaults-mode-browser": "^4.3.47", + "@smithy/util-defaults-mode-node": "^4.2.52", + "@smithy/util-endpoints": "^3.4.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.2", + "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -281,22 +281,22 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.973.27", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.27.tgz", - "integrity": "sha512-CUZ5m8hwMCH6OYI4Li/WgMfIEx10Q2PLI9Y3XOUTPGZJ53aZ0007jCv+X/ywsaERyKPdw5MRZWk877roQksQ4A==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.0.tgz", + "integrity": "sha512-8j+dMtyDqNXFmi09CBdz8TY6Ltf2jhfHuP6ZvG4zVjndRc6JF0aeBUbRwQLndbptFCsdctRQgdNWecy4TIfXAw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@aws-sdk/xml-builder": "^3.972.17", - "@smithy/core": "^3.23.14", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/property-provider": "^4.2.13", - "@smithy/protocol-http": "^5.3.13", - "@smithy/signature-v4": "^5.3.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/xml-builder": "^3.972.18", + "@smithy/core": "^3.23.15", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", - "@smithy/util-middleware": "^4.2.13", + "@smithy/util-middleware": "^4.2.14", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -305,15 +305,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.25.tgz", - "integrity": "sha512-6QfI0wv4jpG5CrdO/AO0JfZ2ux+tKwJPrUwmvxXF50vI5KIypKVGNF6b4vlkYEnKumDTI1NX2zUBi8JoU5QU3A==", + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.26.tgz", + "integrity": "sha512-WBHAMxyPdgeJY6ZGLvq9mJwzZ+GaNUROQbfdVshtMsDVBrZTj5ZuFjKclSjSHvKSHJ4Y4O2yvI/aA/hrJbYfng==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/types": "^3.973.7", - "@smithy/property-provider": "^4.2.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -321,20 +321,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.27", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.27.tgz", - "integrity": "sha512-3V3Usj9Gs93h865DqN4M2NWJhC5kXU9BvZskfN3+69omuYlE3TZxOEcVQtBGLOloJB7BVfJKXVLqeNhOzHqSlQ==", + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.28.tgz", + "integrity": "sha512-+1DwCjjpo1WoiZTN08yGitI3nUwZUSQWVWFrW4C46HqZwACjcUQ7C66tnKPBTVxrEYYDOP11A6Afmu1L6ylt3g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/types": "^3.973.7", - "@smithy/fetch-http-handler": "^5.3.16", - "@smithy/node-http-handler": "^4.5.2", - "@smithy/property-provider": "^4.2.13", - "@smithy/protocol-http": "^5.3.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", - "@smithy/util-stream": "^4.5.22", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.5.3", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" }, "engines": { @@ -342,24 +342,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.29.tgz", - "integrity": "sha512-SiBuAnXecCbT/OpAf3vqyI/AVE3mTaYr9ShXLybxZiPLBiPCCOIWSGAtYYGQWMRvobBTiqOewaB+wcgMMZI2Aw==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.30.tgz", + "integrity": "sha512-Fg1oJcoijwOZjTxdbx+ubqbQl8YEQ4Cwhjw6TWzQjuDEvQYNhnCXW2pN7eKtdTrdE4a6+5TVKGSm2I+i2BKIQg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/credential-provider-env": "^3.972.25", - "@aws-sdk/credential-provider-http": "^3.972.27", - "@aws-sdk/credential-provider-login": "^3.972.29", - "@aws-sdk/credential-provider-process": "^3.972.25", - "@aws-sdk/credential-provider-sso": "^3.972.29", - "@aws-sdk/credential-provider-web-identity": "^3.972.29", - "@aws-sdk/nested-clients": "^3.996.19", - "@aws-sdk/types": "^3.973.7", - "@smithy/credential-provider-imds": "^4.2.13", - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/credential-provider-env": "^3.972.26", + "@aws-sdk/credential-provider-http": "^3.972.28", + "@aws-sdk/credential-provider-login": "^3.972.30", + "@aws-sdk/credential-provider-process": "^3.972.26", + "@aws-sdk/credential-provider-sso": "^3.972.30", + "@aws-sdk/credential-provider-web-identity": "^3.972.30", + "@aws-sdk/nested-clients": "^3.996.20", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -367,18 +367,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.29.tgz", - "integrity": "sha512-OGOslTbOlxXexKMqhxCEbBQbUIfuhGxU5UXw3Fm56ypXHvrXH4aTt/xb5Y884LOoteP1QST1lVZzHfcTnWhiPQ==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.30.tgz", + "integrity": "sha512-nchIrrI/7dgjG1bW/DEWOJc00K9n+kkl6B8Mk0KO6d4GfWBOXlVr9uHp7CJR9FIrjmov5SGjHXG2q9XAtkRw6Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/nested-clients": "^3.996.19", - "@aws-sdk/types": "^3.973.7", - "@smithy/property-provider": "^4.2.13", - "@smithy/protocol-http": "^5.3.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/nested-clients": "^3.996.20", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -386,22 +386,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.30", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.30.tgz", - "integrity": "sha512-FMnAnWxc8PG+ZrZ2OBKzY4luCUJhe9CG0B9YwYr4pzrYGLXBS2rl+UoUvjGbAwiptxRL6hyA3lFn03Bv1TLqTw==", + "version": "3.972.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.31.tgz", + "integrity": "sha512-99OHVQ6eZ5DTxiOWgHdjBMvLqv7xoY4jLK6nZ1NcNSQbAnYZkQNIHi/VqInc9fnmg7of9si/z+waE6YL9OQIlw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.25", - "@aws-sdk/credential-provider-http": "^3.972.27", - "@aws-sdk/credential-provider-ini": "^3.972.29", - "@aws-sdk/credential-provider-process": "^3.972.25", - "@aws-sdk/credential-provider-sso": "^3.972.29", - "@aws-sdk/credential-provider-web-identity": "^3.972.29", - "@aws-sdk/types": "^3.973.7", - "@smithy/credential-provider-imds": "^4.2.13", - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@aws-sdk/credential-provider-env": "^3.972.26", + "@aws-sdk/credential-provider-http": "^3.972.28", + "@aws-sdk/credential-provider-ini": "^3.972.30", + "@aws-sdk/credential-provider-process": "^3.972.26", + "@aws-sdk/credential-provider-sso": "^3.972.30", + "@aws-sdk/credential-provider-web-identity": "^3.972.30", + "@aws-sdk/types": "^3.973.8", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -409,16 +409,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.25", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.25.tgz", - "integrity": "sha512-HR7ynNRdNhNsdVCOCegy1HsfsRzozCOPtD3RzzT1JouuaHobWyRfJzCBue/3jP7gECHt+kQyZUvwg/cYLWurNQ==", + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.26.tgz", + "integrity": "sha512-jibxNld3m+vbmQwn98hcQ+fLIVrx3cQuhZlSs1/hix48SjDS5/pjMLwpmtLD/lFnd6ve1AL4o1bZg3X1WRa2SQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/types": "^3.973.7", - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -426,36 +426,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.29.tgz", - "integrity": "sha512-HWv4SEq3jZDYPlwryZVef97+U8CxxRos5mK8sgGO1dQaFZpV5giZLzqGE5hkDmh2csYcBO2uf5XHjPTpZcJlig==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.30.tgz", + "integrity": "sha512-honYIM17F/+QSWJRE84T4u//ofqEi7rLbnwmIpu7fgFX5PML78wbtdSAy5Xwyve3TLpE9/f9zQx0aBVxSjAOPw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/nested-clients": "^3.996.19", - "@aws-sdk/token-providers": "3.1026.0", - "@aws-sdk/types": "^3.973.7", - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1026.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1026.0.tgz", - "integrity": "sha512-Ieq/HiRrbEtrYP387Nes0XlR7H1pJiJOZKv+QyQzMYpvTiDs0VKy2ZB3E2Zf+aFovWmeE7lRE4lXyF7dYM6GgA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/nested-clients": "^3.996.19", - "@aws-sdk/types": "^3.973.7", - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/nested-clients": "^3.996.20", + "@aws-sdk/token-providers": "3.1031.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -463,17 +445,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.29.tgz", - "integrity": "sha512-PdMBza1WEKEUPFEmMGCfnU2RYCz9MskU2e8JxjyUOsMKku7j9YaDKvbDi2dzC0ihFoM6ods2SbhfAAro+Gwlew==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.30.tgz", + "integrity": "sha512-CyL4oWUlONQRN2SsYMVrA9Z3i3QfLWTQctI8tuKbjNGCVVDCnJf/yMbSJCOZgpPFRtxh7dgQwvpqwmJm+iytmw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/nested-clients": "^3.996.19", - "@aws-sdk/types": "^3.973.7", - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/nested-clients": "^3.996.20", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -481,14 +463,14 @@ } }, "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.13", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.13.tgz", - "integrity": "sha512-2Pi1kD0MDkMAxDHqvpi/hKMs9hXUYbj2GLEjCwy+0jzfLChAsF50SUYnOeTI+RztA+Ic4pnLAdB03f1e8nggxQ==", + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.14.tgz", + "integrity": "sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/eventstream-codec": "^4.2.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -496,14 +478,14 @@ } }, "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.9.tgz", - "integrity": "sha512-ypgOvpWxQTCnQyDHGxnTviqqANE7FIIzII7VczJnTPCJcJlu17hMQXnvE47aKSKsawVJAaaRsyOEbHQuLJF9ng==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.10.tgz", + "integrity": "sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -511,14 +493,14 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.9.tgz", - "integrity": "sha512-je5vRdNw4SkuTnmRbFZLdye4sQ0faLt8kwka5wnnSU30q1mHO4X+idGEJOOE+Tn1ME7Oryn05xxkDvIb3UaLaQ==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.10.tgz", + "integrity": "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -526,13 +508,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.9.tgz", - "integrity": "sha512-HsVgDrruhqI28RkaXALm8grJ7Agc1wF6Et0xh6pom8NdO2VdO/SD9U/tPwUjewwK/pVoka+EShBxyCvgsPCtog==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.10.tgz", + "integrity": "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -540,15 +522,15 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.10.tgz", - "integrity": "sha512-RVQQbq5orQ/GHUnXvqEOj2HHPBJm+mM+ySwZKS5UaLBwra5ugRtiH09PLUoOZRl7a1YzaOzXSuGbn9iD5j60WQ==", + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.11.tgz", + "integrity": "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", + "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -556,18 +538,18 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.29.tgz", - "integrity": "sha512-f/sIRzuTfEjg6NsbMYvye2VsmnQoNgntntleQyx5uGacUYzszbfIlO3GcI6G6daWUmTm0IDZc11qMHWwF0o0mQ==", + "version": "3.972.30", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.30.tgz", + "integrity": "sha512-lCz6JfelhjD6Eco1urXM2rOYRaxROSqeoY6IEKx+soegFJOajmIBCMHTAWuJl25Wf9IAST+i0/yOk9G3rMV26A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/types": "^3.973.7", - "@aws-sdk/util-endpoints": "^3.996.6", - "@smithy/core": "^3.23.14", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", - "@smithy/util-retry": "^4.3.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.7", + "@smithy/core": "^3.23.15", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-retry": "^4.3.2", "tslib": "^2.6.2" }, "engines": { @@ -575,19 +557,19 @@ } }, "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.15.tgz", - "integrity": "sha512-hsZ35FORQsN5hwNdMD6zWmHCphbXkDxO6j+xwCUiuMb0O6gzS/PWgttQNl1OAn7h/uqZAMUG4yOS0wY/yhAieg==", + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.16.tgz", + "integrity": "sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@aws-sdk/util-format-url": "^3.972.9", - "@smithy/eventstream-codec": "^4.2.13", - "@smithy/eventstream-serde-browser": "^4.2.13", - "@smithy/fetch-http-handler": "^5.3.16", - "@smithy/protocol-http": "^5.3.13", - "@smithy/signature-v4": "^5.3.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-format-url": "^3.972.10", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/eventstream-serde-browser": "^4.2.14", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/protocol-http": "^5.3.14", + "@smithy/signature-v4": "^5.3.14", + "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", @@ -598,47 +580,47 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.996.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.19.tgz", - "integrity": "sha512-uFkmCDXvmQYLanlYdOFS0+MQWkrj9wPMt/ZCc/0J0fjPim6F5jBVBmEomvGY/j77ILW6GTPwN22Jc174Mhkw6Q==", + "version": "3.996.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.20.tgz", + "integrity": "sha512-bzPdsNQnCh6TvvUmTHLZlL8qgyME6mNiUErcRMyJPywIl1BEu2VZRShel3mUoSh89bOBEXEWtjocDMolFxd/9A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/middleware-host-header": "^3.972.9", - "@aws-sdk/middleware-logger": "^3.972.9", - "@aws-sdk/middleware-recursion-detection": "^3.972.10", - "@aws-sdk/middleware-user-agent": "^3.972.29", - "@aws-sdk/region-config-resolver": "^3.972.11", - "@aws-sdk/types": "^3.973.7", - "@aws-sdk/util-endpoints": "^3.996.6", - "@aws-sdk/util-user-agent-browser": "^3.972.9", - "@aws-sdk/util-user-agent-node": "^3.973.15", - "@smithy/config-resolver": "^4.4.14", - "@smithy/core": "^3.23.14", - "@smithy/fetch-http-handler": "^5.3.16", - "@smithy/hash-node": "^4.2.13", - "@smithy/invalid-dependency": "^4.2.13", - "@smithy/middleware-content-length": "^4.2.13", - "@smithy/middleware-endpoint": "^4.4.29", - "@smithy/middleware-retry": "^4.5.0", - "@smithy/middleware-serde": "^4.2.17", - "@smithy/middleware-stack": "^4.2.13", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/node-http-handler": "^4.5.2", - "@smithy/protocol-http": "^5.3.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/middleware-host-header": "^3.972.10", + "@aws-sdk/middleware-logger": "^3.972.10", + "@aws-sdk/middleware-recursion-detection": "^3.972.11", + "@aws-sdk/middleware-user-agent": "^3.972.30", + "@aws-sdk/region-config-resolver": "^3.972.12", + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/util-endpoints": "^3.996.7", + "@aws-sdk/util-user-agent-browser": "^3.972.10", + "@aws-sdk/util-user-agent-node": "^3.973.16", + "@smithy/config-resolver": "^4.4.16", + "@smithy/core": "^3.23.15", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/hash-node": "^4.2.14", + "@smithy/invalid-dependency": "^4.2.14", + "@smithy/middleware-content-length": "^4.2.14", + "@smithy/middleware-endpoint": "^4.4.30", + "@smithy/middleware-retry": "^4.5.3", + "@smithy/middleware-serde": "^4.2.18", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/node-http-handler": "^4.5.3", + "@smithy/protocol-http": "^5.3.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", - "@smithy/util-defaults-mode-browser": "^4.3.45", - "@smithy/util-defaults-mode-node": "^4.2.49", - "@smithy/util-endpoints": "^3.3.4", - "@smithy/util-middleware": "^4.2.13", - "@smithy/util-retry": "^4.3.0", + "@smithy/util-defaults-mode-browser": "^4.3.47", + "@smithy/util-defaults-mode-node": "^4.2.52", + "@smithy/util-endpoints": "^3.4.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -647,15 +629,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.11", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.11.tgz", - "integrity": "sha512-6Q8B1dcx6BBqUTY1Mc/eROKA0FImEEY5VPSd6AGPEUf0ErjExz4snVqa9kNJSoVDV1rKaNf3qrWojgcKW+SdDg==", + "version": "3.972.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.12.tgz", + "integrity": "sha512-QQI43Mxd53nBij0pm8HXC+t4IOC6gnhhZfzxE0OATQyO6QfPV4e+aTIRRuAJKA6Nig/cR8eLwPryqYTX9ZrjAQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/config-resolver": "^4.4.14", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/config-resolver": "^4.4.16", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -663,17 +645,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1030.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1030.0.tgz", - "integrity": "sha512-gUuCLTnEiUgpxHEnJSidxZZlQ+rQwc/mrijz6DxeMijTwS3/e3UfJvL8C1YDvcbt8MkkXj92h0MpYtfhR+EGeg==", + "version": "3.1031.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1031.0.tgz", + "integrity": "sha512-zj/PvnbQK/2KJNln5K2QRI9HSsy+B4emz2gbQyUHkk6l7Lidu83P/9tfmC2cJXkcC3vdmyKH2DP3Iw/FDfKQuQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.973.27", - "@aws-sdk/nested-clients": "^3.996.19", - "@aws-sdk/types": "^3.973.7", - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@aws-sdk/core": "^3.974.0", + "@aws-sdk/nested-clients": "^3.996.20", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -681,12 +663,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.7.tgz", - "integrity": "sha512-reXRwoJ6CfChoqAsBszUYajAF8Z2LRE+CRcKocvFSMpIiLOtYU3aJ9trmn6VVPAzbbY5LXF+FfmUslbXk1SYFg==", + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -694,15 +676,15 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.996.6", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.6.tgz", - "integrity": "sha512-2nUQ+2ih7CShuKHpGSIYvvAIOHy52dOZguYG36zptBukhw6iFwcvGfG0tes0oZFWQqEWvgZe9HLWaNlvXGdOrg==", + "version": "3.996.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.7.tgz", + "integrity": "sha512-ty4LQxN1QC+YhUP28NfEgZDEGXkyqOQy+BDriBozqHsrYO4JMgiPhfizqOGF7P+euBTZ5Ez6SKlLAMCLo8tzmw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", - "@smithy/util-endpoints": "^3.3.4", + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-endpoints": "^3.4.1", "tslib": "^2.6.2" }, "engines": { @@ -710,14 +692,14 @@ } }, "node_modules/@aws-sdk/util-format-url": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.9.tgz", - "integrity": "sha512-fNJXHrs0ZT7Wx0KGIqKv7zLxlDXt2vqjx9z6oKUQFmpE5o4xxnSryvVHfHpIifYHWKz94hFccIldJ0YSZjlCBw==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.972.10.tgz", + "integrity": "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/querystring-builder": "^4.2.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -737,27 +719,27 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.9", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.9.tgz", - "integrity": "sha512-sn/LMzTbGjYqCCF24390WxPd6hkpoSptiUn5DzVp4cD71yqw+yGEGm1YCxyEoPXyc8qciM8UzLJcZBFslxo5Uw==", + "version": "3.972.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.10.tgz", + "integrity": "sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.7", - "@smithy/types": "^4.14.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/types": "^4.14.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.15.tgz", - "integrity": "sha512-fYn3s9PtKdgQkczGZCFMgkNEe8aq1JCVbnRqjqN9RSVW43xn2RV9xdcZ3z01a48Jpkuh/xCmBKJxdLOo4Ozg7w==", + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.16.tgz", + "integrity": "sha512-ccvu0FNCI0C6OqmxI/tWn7BD8qGooWuURssiIM+6vbksFO8opXR4JOGtGYPj8QYzN/vfwNYrcK344PPbYuvzRg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.29", - "@aws-sdk/types": "^3.973.7", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/types": "^4.14.0", + "@aws-sdk/middleware-user-agent": "^3.972.30", + "@aws-sdk/types": "^3.973.8", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, @@ -774,12 +756,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.17.tgz", - "integrity": "sha512-Ra7hjqAZf1OXRRMueB13qex7mFJRDK/pgCvdSFemXBT8KCGnQDPoKzHY1SjN+TjJVmnpSF14W5tJ1vDamFu+Gg==", + "version": "3.972.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.18.tgz", + "integrity": "sha512-BMDNVG1ETXRhl1tnisQiYBef3RShJ1kfZA7x7afivTFMLirfHNTb6U71K569HNXhSXbQZsweHvSDZ6euBw8hPA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" }, @@ -874,9 +856,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -894,9 +873,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -914,9 +890,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -934,9 +907,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -1622,9 +1592,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1641,9 +1608,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1660,9 +1624,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1679,9 +1640,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1698,9 +1656,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1952,9 +1907,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1975,9 +1927,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1998,9 +1947,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2021,9 +1967,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2044,9 +1987,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2266,9 +2206,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2290,9 +2227,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2314,9 +2248,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2338,9 +2269,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2362,9 +2290,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2386,9 +2311,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2643,9 +2565,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2659,9 +2578,6 @@ "cpu": [ "arm" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2675,9 +2591,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2691,9 +2604,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2707,9 +2617,6 @@ "cpu": [ "loong64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2723,9 +2630,6 @@ "cpu": [ "loong64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2739,9 +2643,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2755,9 +2656,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2771,9 +2669,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2787,9 +2682,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2803,9 +2695,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2819,9 +2708,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2835,9 +2721,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3781,9 +3664,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3800,9 +3680,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3819,9 +3696,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3838,9 +3712,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6398,9 +6269,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6421,9 +6289,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6444,9 +6309,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6467,9 +6329,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -8678,10 +8537,10 @@ }, "packages/agent": { "name": "@mariozechner/pi-agent-core", - "version": "0.67.5", + "version": "0.67.6", "license": "MIT", "dependencies": { - "@mariozechner/pi-ai": "^0.67.5" + "@mariozechner/pi-ai": "^0.67.6" }, "devDependencies": { "@types/node": "^24.3.0", @@ -8711,7 +8570,7 @@ }, "packages/ai": { "name": "@mariozechner/pi-ai", - "version": "0.67.5", + "version": "0.67.6", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.90.0", @@ -8759,13 +8618,13 @@ }, "packages/coding-agent": { "name": "@mariozechner/pi-coding-agent", - "version": "0.67.5", + "version": "0.67.6", "license": "MIT", "dependencies": { "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.67.5", - "@mariozechner/pi-ai": "^0.67.5", - "@mariozechner/pi-tui": "^0.67.5", + "@mariozechner/pi-agent-core": "^0.67.6", + "@mariozechner/pi-ai": "^0.67.6", + "@mariozechner/pi-tui": "^0.67.6", "@silvia-odwyer/photon-node": "^0.3.4", "ajv": "^8.17.1", "chalk": "^5.5.0", @@ -8806,22 +8665,22 @@ }, "packages/coding-agent/examples/extensions/custom-provider-anthropic": { "name": "pi-extension-custom-provider-anthropic", - "version": "1.18.5", + "version": "1.18.6", "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } }, "packages/coding-agent/examples/extensions/custom-provider-gitlab-duo": { "name": "pi-extension-custom-provider-gitlab-duo", - "version": "1.18.5" + "version": "1.18.6" }, "packages/coding-agent/examples/extensions/custom-provider-qwen-cli": { "name": "pi-extension-custom-provider-qwen-cli", - "version": "1.17.5" + "version": "1.17.6" }, "packages/coding-agent/examples/extensions/with-deps": { "name": "pi-extension-with-deps", - "version": "1.31.5", + "version": "1.31.6", "dependencies": { "ms": "^2.1.3" }, @@ -8857,13 +8716,13 @@ }, "packages/mom": { "name": "@mariozechner/pi-mom", - "version": "0.67.5", + "version": "0.67.6", "license": "MIT", "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.67.5", - "@mariozechner/pi-ai": "^0.67.5", - "@mariozechner/pi-coding-agent": "^0.67.5", + "@mariozechner/pi-agent-core": "^0.67.6", + "@mariozechner/pi-ai": "^0.67.6", + "@mariozechner/pi-coding-agent": "^0.67.6", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", @@ -8902,10 +8761,10 @@ }, "packages/pods": { "name": "@mariozechner/pi", - "version": "0.67.5", + "version": "0.67.6", "license": "MIT", "dependencies": { - "@mariozechner/pi-agent-core": "^0.67.5", + "@mariozechner/pi-agent-core": "^0.67.6", "chalk": "^5.5.0" }, "bin": { @@ -8918,7 +8777,7 @@ }, "packages/tui": { "name": "@mariozechner/pi-tui", - "version": "0.67.5", + "version": "0.67.6", "license": "MIT", "dependencies": { "@types/mime-types": "^2.1.4", @@ -8965,12 +8824,12 @@ }, "packages/web-ui": { "name": "@mariozechner/pi-web-ui", - "version": "0.67.5", + "version": "0.67.6", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.67.5", - "@mariozechner/pi-tui": "^0.67.5", + "@mariozechner/pi-ai": "^0.67.6", + "@mariozechner/pi-tui": "^0.67.6", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", @@ -8991,7 +8850,7 @@ }, "packages/web-ui/example": { "name": "pi-web-ui-example", - "version": "1.55.5", + "version": "1.55.6", "dependencies": { "@mariozechner/mini-lit": "^0.2.0", "@mariozechner/pi-ai": "file:../../ai", diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index a95cea40..837b8eac 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.6] - 2026-04-16 ## [0.67.5] - 2026-04-16 diff --git a/packages/agent/package.json b/packages/agent/package.json index 9625f167..76ebbe8d 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-agent-core", - "version": "0.67.5", + "version": "0.67.6", "description": "General-purpose agent with transport abstraction, state management, and attachment support", "type": "module", "main": "./dist/index.js", @@ -17,7 +17,7 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@mariozechner/pi-ai": "^0.67.5" + "@mariozechner/pi-ai": "^0.67.6" }, "keywords": [ "ai", diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 59640f73..af8f6dc5 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.6] - 2026-04-16 ### Added diff --git a/packages/ai/package.json b/packages/ai/package.json index c68ca28b..7fddf4c3 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-ai", - "version": "0.67.5", + "version": "0.67.6", "description": "Unified LLM API with automatic model discovery and provider configuration", "type": "module", "main": "./dist/index.js", diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 7536e6c4..579abbe5 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.6] - 2026-04-16 ### New Features diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json index 4d3be44c..6637c89f 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-extension-custom-provider", - "version": "1.18.5", + "version": "1.18.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-extension-custom-provider", - "version": "1.18.5", + "version": "1.18.6", "dependencies": { "@anthropic-ai/sdk": "^0.52.0" } diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json index 60695b62..da6667c2 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-anthropic", "private": true, - "version": "1.18.5", + "version": "1.18.6", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json index e59edd43..2f6eda05 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-gitlab-duo", "private": true, - "version": "1.18.5", + "version": "1.18.6", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json b/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json index f2f82bca..249715e1 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json +++ b/packages/coding-agent/examples/extensions/custom-provider-qwen-cli/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-custom-provider-qwen-cli", "private": true, - "version": "1.17.5", + "version": "1.17.6", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/examples/extensions/with-deps/package-lock.json b/packages/coding-agent/examples/extensions/with-deps/package-lock.json index 48804236..22b9706a 100644 --- a/packages/coding-agent/examples/extensions/with-deps/package-lock.json +++ b/packages/coding-agent/examples/extensions/with-deps/package-lock.json @@ -1,12 +1,12 @@ { "name": "pi-extension-with-deps", - "version": "1.31.5", + "version": "1.31.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pi-extension-with-deps", - "version": "1.31.5", + "version": "1.31.6", "dependencies": { "ms": "^2.1.3" }, diff --git a/packages/coding-agent/examples/extensions/with-deps/package.json b/packages/coding-agent/examples/extensions/with-deps/package.json index 0280528c..e9c63bf1 100644 --- a/packages/coding-agent/examples/extensions/with-deps/package.json +++ b/packages/coding-agent/examples/extensions/with-deps/package.json @@ -1,7 +1,7 @@ { "name": "pi-extension-with-deps", "private": true, - "version": "1.31.5", + "version": "1.31.6", "type": "module", "scripts": { "clean": "echo 'nothing to clean'", diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json index 279bf1b6..ad21c8b8 100644 --- a/packages/coding-agent/package.json +++ b/packages/coding-agent/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-coding-agent", - "version": "0.67.5", + "version": "0.67.6", "description": "Coding agent CLI with read, bash, edit, write tools and session management", "type": "module", "piConfig": { @@ -40,9 +40,9 @@ }, "dependencies": { "@mariozechner/jiti": "^2.6.2", - "@mariozechner/pi-agent-core": "^0.67.5", - "@mariozechner/pi-ai": "^0.67.5", - "@mariozechner/pi-tui": "^0.67.5", + "@mariozechner/pi-agent-core": "^0.67.6", + "@mariozechner/pi-ai": "^0.67.6", + "@mariozechner/pi-tui": "^0.67.6", "@silvia-odwyer/photon-node": "^0.3.4", "ajv": "^8.17.1", "chalk": "^5.5.0", diff --git a/packages/mom/CHANGELOG.md b/packages/mom/CHANGELOG.md index 43c728e0..7ac3b67e 100644 --- a/packages/mom/CHANGELOG.md +++ b/packages/mom/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.6] - 2026-04-16 ## [0.67.5] - 2026-04-16 diff --git a/packages/mom/package.json b/packages/mom/package.json index 54c1843b..0e8c6013 100644 --- a/packages/mom/package.json +++ b/packages/mom/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-mom", - "version": "0.67.5", + "version": "0.67.6", "description": "Slack bot that delegates messages to the pi coding agent", "type": "module", "bin": { @@ -20,9 +20,9 @@ }, "dependencies": { "@anthropic-ai/sandbox-runtime": "^0.0.16", - "@mariozechner/pi-agent-core": "^0.67.5", - "@mariozechner/pi-ai": "^0.67.5", - "@mariozechner/pi-coding-agent": "^0.67.5", + "@mariozechner/pi-agent-core": "^0.67.6", + "@mariozechner/pi-ai": "^0.67.6", + "@mariozechner/pi-coding-agent": "^0.67.6", "@sinclair/typebox": "^0.34.0", "@slack/socket-mode": "^2.0.0", "@slack/web-api": "^7.0.0", diff --git a/packages/pods/package.json b/packages/pods/package.json index 8d45b394..e25d1ad9 100644 --- a/packages/pods/package.json +++ b/packages/pods/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi", - "version": "0.67.5", + "version": "0.67.6", "description": "CLI tool for managing vLLM deployments on GPU pods", "type": "module", "bin": { @@ -33,7 +33,7 @@ "node": ">=20.0.0" }, "dependencies": { - "@mariozechner/pi-agent-core": "^0.67.5", + "@mariozechner/pi-agent-core": "^0.67.6", "chalk": "^5.5.0" }, "devDependencies": {} diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index d4e6e991..85c26a72 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.6] - 2026-04-16 ### Added diff --git a/packages/tui/package.json b/packages/tui/package.json index 4764c886..8bc88115 100644 --- a/packages/tui/package.json +++ b/packages/tui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-tui", - "version": "0.67.5", + "version": "0.67.6", "description": "Terminal User Interface library with differential rendering for efficient text-based applications", "type": "module", "main": "dist/index.js", diff --git a/packages/web-ui/CHANGELOG.md b/packages/web-ui/CHANGELOG.md index e812f1e2..c21c2274 100644 --- a/packages/web-ui/CHANGELOG.md +++ b/packages/web-ui/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [0.67.6] - 2026-04-16 ## [0.67.5] - 2026-04-16 diff --git a/packages/web-ui/example/package.json b/packages/web-ui/example/package.json index ed460ed5..f3c97350 100644 --- a/packages/web-ui/example/package.json +++ b/packages/web-ui/example/package.json @@ -1,6 +1,6 @@ { "name": "pi-web-ui-example", - "version": "1.55.5", + "version": "1.55.6", "private": true, "type": "module", "scripts": { diff --git a/packages/web-ui/package.json b/packages/web-ui/package.json index f7f0f423..67591ede 100644 --- a/packages/web-ui/package.json +++ b/packages/web-ui/package.json @@ -1,6 +1,6 @@ { "name": "@mariozechner/pi-web-ui", - "version": "0.67.5", + "version": "0.67.6", "description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai", "type": "module", "main": "dist/index.js", @@ -18,8 +18,8 @@ }, "dependencies": { "@lmstudio/sdk": "^1.5.0", - "@mariozechner/pi-ai": "^0.67.5", - "@mariozechner/pi-tui": "^0.67.5", + "@mariozechner/pi-ai": "^0.67.6", + "@mariozechner/pi-tui": "^0.67.6", "docx-preview": "^0.3.7", "jszip": "^3.10.1", "lucide": "^0.544.0", From 5476b56e419c990c4fc669fab044fbf409fb61ba Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 23:26:44 +0200 Subject: [PATCH 33/38] Add [Unreleased] section for next cycle --- packages/agent/CHANGELOG.md | 2 ++ packages/ai/CHANGELOG.md | 2 ++ packages/ai/src/models.generated.ts | 10 +++++----- packages/coding-agent/CHANGELOG.md | 2 ++ packages/mom/CHANGELOG.md | 2 ++ packages/tui/CHANGELOG.md | 2 ++ packages/web-ui/CHANGELOG.md | 2 ++ 7 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 837b8eac..2d7226e0 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.6] - 2026-04-16 ## [0.67.5] - 2026-04-16 diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index af8f6dc5..15128307 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.6] - 2026-04-16 ### Added diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 84077d1a..15412260 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -10031,13 +10031,13 @@ export const MODELS = { reasoning: true, input: ["text"], cost: { - input: 0.14950000000000002, - output: 1.495, + input: 0.13, + output: 0.6, cacheRead: 0, cacheWrite: 0, }, - contextWindow: 131072, - maxTokens: 4096, + contextWindow: 262144, + maxTokens: 262144, } satisfies Model<"openai-completions">, "qwen/qwen3-30b-a3b": { id: "qwen/qwen3-30b-a3b", @@ -10492,7 +10492,7 @@ export const MODELS = { cost: { input: 0.39, output: 2.34, - cacheRead: 0, + cacheRead: 0.195, cacheWrite: 0, }, contextWindow: 262144, diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 579abbe5..7dd40ddb 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.6] - 2026-04-16 ### New Features diff --git a/packages/mom/CHANGELOG.md b/packages/mom/CHANGELOG.md index 7ac3b67e..a03de420 100644 --- a/packages/mom/CHANGELOG.md +++ b/packages/mom/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.6] - 2026-04-16 ## [0.67.5] - 2026-04-16 diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 85c26a72..e16ada79 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.6] - 2026-04-16 ### Added diff --git a/packages/web-ui/CHANGELOG.md b/packages/web-ui/CHANGELOG.md index c21c2274..8d9ada70 100644 --- a/packages/web-ui/CHANGELOG.md +++ b/packages/web-ui/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.67.6] - 2026-04-16 ## [0.67.5] - 2026-04-16 From 22085a9a1765d4894092d64c8baf2b3503547e21 Mon Sep 17 00:00:00 2001 From: wirjo Date: Fri, 17 Apr 2026 07:34:18 +1000 Subject: [PATCH 34/38] feat(bedrock): support Bearer token auth for Converse API (#3125) Adds bearer token authentication for the Bedrock Converse API, enabling users to authenticate with an API key instead of SigV4/IAM credentials. When a bearer token is available (via `options.bearerToken` or the `AWS_BEARER_TOKEN_BEDROCK` env var), the provider: 1. Sets dummy credentials to prevent SDK credential resolution errors 2. Injects middleware after SigV4 signing that replaces the Authorization header with `Bearer ` and removes SigV4-specific headers This uses the official `bedrock:CallWithBearerToken` IAM action, which is a documented AWS feature for API key auth on Bedrock endpoints. Use case: users who receive a Bedrock API key (bearer token) from the AWS console or their admin, without having IAM access keys or instance roles. Similar to how ANTHROPIC_API_KEY works for direct Anthropic API. Required IAM permission on the token's identity: bedrock:CallWithBearerToken Tested: Bearer token successfully authenticates against Bedrock Converse API (returns correct 403 for missing IAM permission, not auth format error). SigV4 path is unchanged when no bearer token is set. --- packages/ai/src/providers/amazon-bedrock.ts | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index 4b2e61f6..a1f328cb 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -74,6 +74,12 @@ export interface BedrockOptions extends StreamOptions { * Tags appear in AWS Cost Explorer split cost allocation data. * @see https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html */ requestMetadata?: Record; + /** Bearer token for Bedrock API key authentication. + * When set, bypasses SigV4 signing and sends Authorization: Bearer instead. + * Requires `bedrock:CallWithBearerToken` IAM permission on the token's identity. + * Set via AWS_BEARER_TOKEN_BEDROCK env var or pass directly. + * @see https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonbedrock.html */ + bearerToken?: string; } type Block = (TextContent | ThinkingContent | ToolCall) & { index?: number; partialJson?: string }; @@ -110,6 +116,9 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt profile: options.profile, }; + // Resolve bearer token for API key auth (bypasses SigV4) + const bearerToken = options.bearerToken || process.env.AWS_BEARER_TOKEN_BEDROCK || undefined; + // in Node.js/Bun environment only if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) { // Region resolution: explicit option > env vars > SDK default chain. @@ -130,6 +139,15 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt }; } + // Bearer token auth: use API key instead of SigV4 signing. + // Requires bedrock:CallWithBearerToken IAM permission. + if (bearerToken && process.env.AWS_BEDROCK_SKIP_AUTH !== "1") { + config.credentials = { + accessKeyId: "bearer-token-auth", + secretAccessKey: "bearer-token-auth", + }; + } + if ( process.env.HTTP_PROXY || process.env.HTTPS_PROXY || @@ -164,6 +182,22 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt try { const client = new BedrockRuntimeClient(config); + // Inject bearer token middleware after SigV4 signing + if (bearerToken) { + client.middlewareStack.addRelativeTo( + (next) => async (args: any) => { + if (args.request?.headers) { + args.request.headers["authorization"] = `Bearer ${bearerToken}`; + delete args.request.headers["x-amz-date"]; + delete args.request.headers["x-amz-security-token"]; + delete args.request.headers["x-amz-content-sha256"]; + } + return next(args); + }, + { relation: "after", toMiddleware: "awsAuthMiddleware", name: "bearerTokenAuth" }, + ); + } + const cacheRetention = resolveCacheRetention(options.cacheRetention); let commandInput = { modelId: model.id, From b7899005700a8e61d15e8561ceea92dc4db5df12 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 23:50:29 +0200 Subject: [PATCH 35/38] fix(ai): type bedrock bearer auth middleware --- packages/ai/src/providers/amazon-bedrock.ts | 35 +++++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/providers/amazon-bedrock.ts index a1f328cb..79c98038 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/providers/amazon-bedrock.ts @@ -15,11 +15,14 @@ import { type ConverseStreamMetadataEvent, ImageFormat, type Message, + type ServiceInputTypes, + type ServiceOutputTypes, type SystemContentBlock, type ToolChoice, type ToolConfiguration, ToolResultStatus, } from "@aws-sdk/client-bedrock-runtime"; +import type { FinalizeRequestMiddleware } from "@smithy/types"; import { calculateCost } from "../models.js"; import type { @@ -184,18 +187,30 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt // Inject bearer token middleware after SigV4 signing if (bearerToken) { - client.middlewareStack.addRelativeTo( - (next) => async (args: any) => { - if (args.request?.headers) { - args.request.headers["authorization"] = `Bearer ${bearerToken}`; - delete args.request.headers["x-amz-date"]; - delete args.request.headers["x-amz-security-token"]; - delete args.request.headers["x-amz-content-sha256"]; + const bearerTokenAuthMiddleware: FinalizeRequestMiddleware = + (next) => async (args) => { + const request = args.request; + if ( + typeof request === "object" && + request !== null && + "headers" in request && + typeof request.headers === "object" && + request.headers !== null + ) { + const headers = request.headers as Record; + headers.authorization = `Bearer ${bearerToken}`; + delete headers["x-amz-date"]; + delete headers["x-amz-security-token"]; + delete headers["x-amz-content-sha256"]; } return next(args); - }, - { relation: "after", toMiddleware: "awsAuthMiddleware", name: "bearerTokenAuth" }, - ); + }; + + client.middlewareStack.addRelativeTo(bearerTokenAuthMiddleware, { + relation: "after", + toMiddleware: "awsAuthMiddleware", + name: "bearerTokenAuth", + }); } const cacheRetention = resolveCacheRetention(options.cacheRetention); From b9cd557d1d6773abc4e1ddc22c4758b9ecda2c0c Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 23:51:26 +0200 Subject: [PATCH 36/38] fix(agent): guard afterToolCall hook errors in finalization closes #3084 --- packages/agent/CHANGELOG.md | 4 ++++ packages/agent/src/agent-loop.ts | 39 ++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 2d7226e0..5855835f 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed parallel tool-call finalization to convert `afterToolCall` hook throws into error tool results instead of aborting the batch ([#3084](https://github.com/badlogic/pi-mono/issues/3084)) + ## [0.67.6] - 2026-04-16 ## [0.67.5] - 2026-04-16 diff --git a/packages/agent/src/agent-loop.ts b/packages/agent/src/agent-loop.ts index 586a1b7d..8482c5ce 100644 --- a/packages/agent/src/agent-loop.ts +++ b/packages/agent/src/agent-loop.ts @@ -571,23 +571,28 @@ async function finalizeExecutedToolCall( let isError = executed.isError; if (config.afterToolCall) { - const afterResult = await config.afterToolCall( - { - assistantMessage, - toolCall: prepared.toolCall, - args: prepared.args, - result, - isError, - context: currentContext, - }, - signal, - ); - if (afterResult) { - result = { - content: afterResult.content ?? result.content, - details: afterResult.details ?? result.details, - }; - isError = afterResult.isError ?? isError; + try { + const afterResult = await config.afterToolCall( + { + assistantMessage, + toolCall: prepared.toolCall, + args: prepared.args, + result, + isError, + context: currentContext, + }, + signal, + ); + if (afterResult) { + result = { + content: afterResult.content ?? result.content, + details: afterResult.details ?? result.details, + }; + isError = afterResult.isError ?? isError; + } + } catch (error) { + result = createErrorToolResult(error instanceof Error ? error.message : String(error)); + isError = true; } } From 9078230b1abe76c973b207bf08bef99f5132b93f Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 16 Apr 2026 23:56:59 +0200 Subject: [PATCH 37/38] fix(coding-agent): export rpc client from root entrypoint closes #3275 --- packages/coding-agent/CHANGELOG.md | 4 ++++ packages/coding-agent/src/index.ts | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 7dd40ddb..1ccbba6e 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed missing root exports for `RpcClient` and RPC protocol types from `@mariozechner/pi-coding-agent`, so ESM consumers can import them from the main package entrypoint ([#3275](https://github.com/badlogic/pi-mono/issues/3275)) + ## [0.67.6] - 2026-04-16 ### New Features diff --git a/packages/coding-agent/src/index.ts b/packages/coding-agent/src/index.ts index 75720b2f..edf754a9 100644 --- a/packages/coding-agent/src/index.ts +++ b/packages/coding-agent/src/index.ts @@ -296,7 +296,14 @@ export { type MainOptions, main } from "./main.js"; export { InteractiveMode, type InteractiveModeOptions, + type ModelInfo, type PrintModeOptions, + RpcClient, + type RpcClientOptions, + type RpcCommand, + type RpcEventListener, + type RpcResponse, + type RpcSessionState, runPrintMode, runRpcMode, } from "./modes/index.js"; From 165603189b60231ed8b88274729471cee676a0c0 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 16 Apr 2026 23:55:52 +0200 Subject: [PATCH 38/38] fix(coding-agent): parse quoted import paths and missing files --- .../src/core/agent-session-runtime.ts | 12 +- .../src/modes/interactive/interactive-mode.ts | 47 +++++- .../interactive-mode-import-command.test.ts | 144 ++++++++++++++++++ 3 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 packages/coding-agent/test/interactive-mode-import-command.test.ts diff --git a/packages/coding-agent/src/core/agent-session-runtime.ts b/packages/coding-agent/src/core/agent-session-runtime.ts index 58e1fd57..327140bd 100644 --- a/packages/coding-agent/src/core/agent-session-runtime.ts +++ b/packages/coding-agent/src/core/agent-session-runtime.ts @@ -33,6 +33,16 @@ export type CreateAgentSessionRuntimeFactory = (options: { sessionStartEvent?: SessionStartEvent; }) => Promise; +export class SessionImportFileNotFoundError extends Error { + readonly filePath: string; + + constructor(filePath: string) { + super(`File not found: ${filePath}`); + this.name = "SessionImportFileNotFoundError"; + this.filePath = filePath; + } +} + function extractUserMessageText(content: string | Array<{ type: string; text?: string }>): string { if (typeof content === "string") { return content; @@ -251,7 +261,7 @@ export class AgentSessionRuntime { async importFromJsonl(inputPath: string, cwdOverride?: string): Promise<{ cancelled: boolean }> { const resolvedPath = resolve(inputPath); if (!existsSync(resolvedPath)) { - throw new Error(`File not found: ${resolvedPath}`); + throw new SessionImportFileNotFoundError(resolvedPath); } const sessionDir = this.session.sessionManager.getSessionDir(); diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 94d7b414..c088c7f3 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -47,7 +47,7 @@ import { VERSION, } from "../../config.js"; import { type AgentSession, type AgentSessionEvent, parseSkillBlock } from "../../core/agent-session.js"; -import type { AgentSessionRuntime } from "../../core/agent-session-runtime.js"; +import { type AgentSessionRuntime, SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js"; import type { ExtensionContext, ExtensionRunner, @@ -2278,12 +2278,12 @@ export class InteractiveMode { await this.handleModelCommand(searchTerm); return; } - if (text.startsWith("/export")) { + if (text === "/export" || text.startsWith("/export ")) { await this.handleExportCommand(text); this.editor.setText(""); return; } - if (text.startsWith("/import")) { + if (text === "/import" || text.startsWith("/import ")) { await this.handleImportCommand(text); this.editor.setText(""); return; @@ -4353,8 +4353,7 @@ export class InteractiveMode { } private async handleExportCommand(text: string): Promise { - const parts = text.split(/\s+/); - const outputPath = parts.length > 1 ? parts[1] : undefined; + const outputPath = this.getPathCommandArgument(text, "/export"); try { if (outputPath?.endsWith(".jsonl")) { @@ -4369,13 +4368,41 @@ export class InteractiveMode { } } + private getPathCommandArgument(text: string, command: "/export" | "/import"): string | undefined { + if (text === command) { + return undefined; + } + if (!text.startsWith(`${command} `)) { + return undefined; + } + + const argsString = text.slice(command.length + 1).trimStart(); + if (!argsString) { + return undefined; + } + + const firstChar = argsString[0]; + if (firstChar === '"' || firstChar === "'") { + const closingQuoteIndex = argsString.indexOf(firstChar, 1); + if (closingQuoteIndex < 0) { + return undefined; + } + return argsString.slice(1, closingQuoteIndex); + } + + const firstWhitespaceIndex = argsString.search(/\s/); + if (firstWhitespaceIndex < 0) { + return argsString; + } + return argsString.slice(0, firstWhitespaceIndex); + } + private async handleImportCommand(text: string): Promise { - const parts = text.split(/\s+/); - if (parts.length < 2 || !parts[1]) { + const inputPath = this.getPathCommandArgument(text, "/import"); + if (!inputPath) { this.showError("Usage: /import "); return; } - const inputPath = parts[1]; const confirmed = await this.showExtensionConfirm("Import session", `Replace current session with ${inputPath}?`); if (!confirmed) { @@ -4414,6 +4441,10 @@ export class InteractiveMode { this.showStatus(`Session imported from: ${inputPath}`); return; } + if (error instanceof SessionImportFileNotFoundError) { + this.showError(`Failed to import session: ${error.message}`); + return; + } await this.handleFatalRuntimeError("Failed to import session", error); } } diff --git a/packages/coding-agent/test/interactive-mode-import-command.test.ts b/packages/coding-agent/test/interactive-mode-import-command.test.ts new file mode 100644 index 00000000..85575ec0 --- /dev/null +++ b/packages/coding-agent/test/interactive-mode-import-command.test.ts @@ -0,0 +1,144 @@ +import { describe, expect, it, vi } from "vitest"; +import { SessionImportFileNotFoundError } from "../src/core/agent-session-runtime.js"; +import { InteractiveMode } from "../src/modes/interactive/interactive-mode.js"; + +type PathCommand = "/export" | "/import"; + +type InteractiveModePrototype = { + getPathCommandArgument(this: unknown, text: string, command: PathCommand): string | undefined; + handleImportCommand(this: ImportCommandContext, text: string): Promise; +}; + +type ImportCommandContext = { + loadingAnimation?: { stop: () => void }; + statusContainer: { clear: () => void }; + runtimeHost: { importFromJsonl: (inputPath: string, cwdOverride?: string) => Promise<{ cancelled: boolean }> }; + showError: (message: string) => void; + showStatus: (message: string) => void; + showExtensionConfirm: (title: string, message: string) => Promise; + handleRuntimeSessionChange: () => Promise; + renderCurrentSessionState: () => void; + handleFatalRuntimeError: (prefix: string, error: unknown) => Promise; + promptForMissingSessionCwd: (error: unknown) => Promise; + getPathCommandArgument: (text: string, command: PathCommand) => string | undefined; +}; + +const interactiveModePrototype = InteractiveMode.prototype as unknown as InteractiveModePrototype; + +describe("InteractiveMode /import parsing", () => { + it("strips quotes from /import path arguments", () => { + expect(interactiveModePrototype.getPathCommandArgument('/import "path/to/session.jsonl"', "/import")).toBe( + "path/to/session.jsonl", + ); + expect( + interactiveModePrototype.getPathCommandArgument('/import "path with spaces/session.jsonl"', "/import"), + ).toBe("path with spaces/session.jsonl"); + }); + + it("preserves apostrophes in unquoted /import path arguments", () => { + expect(interactiveModePrototype.getPathCommandArgument("/import john's/session.jsonl", "/import")).toBe( + "john's/session.jsonl", + ); + }); + + it("enforces command token boundaries", () => { + expect(interactiveModePrototype.getPathCommandArgument("/important /tmp/session.jsonl", "/import")).toBe( + undefined, + ); + expect(interactiveModePrototype.getPathCommandArgument("/exporter out.html", "/export")).toBe(undefined); + expect(interactiveModePrototype.getPathCommandArgument("/import /tmp/session.jsonl", "/import")).toBe( + "/tmp/session.jsonl", + ); + }); + + it("passes unquoted path to runtimeHost.importFromJsonl", async () => { + const importFromJsonl = vi.fn(async () => ({ cancelled: false })); + const showExtensionConfirm = vi.fn(async () => true); + const showStatus = vi.fn(); + const showError = vi.fn(); + + const context: ImportCommandContext = { + statusContainer: { clear: vi.fn() }, + runtimeHost: { importFromJsonl }, + showError, + showStatus, + showExtensionConfirm, + handleRuntimeSessionChange: vi.fn(async () => {}), + renderCurrentSessionState: vi.fn(), + handleFatalRuntimeError: vi.fn(async () => { + throw new Error("unexpected fatal error"); + }), + promptForMissingSessionCwd: vi.fn(async () => undefined), + getPathCommandArgument: interactiveModePrototype.getPathCommandArgument, + }; + + await interactiveModePrototype.handleImportCommand.call(context, '/import "path/to/session.jsonl"'); + + expect(showExtensionConfirm).toHaveBeenCalledWith( + "Import session", + "Replace current session with path/to/session.jsonl?", + ); + expect(importFromJsonl).toHaveBeenCalledWith("path/to/session.jsonl"); + expect(showError).not.toHaveBeenCalled(); + expect(showStatus).toHaveBeenCalledWith("Session imported from: path/to/session.jsonl"); + }); + + it("passes unquoted apostrophe path to runtimeHost.importFromJsonl unchanged", async () => { + const importFromJsonl = vi.fn(async () => ({ cancelled: false })); + const showExtensionConfirm = vi.fn(async () => true); + const showStatus = vi.fn(); + const showError = vi.fn(); + + const context: ImportCommandContext = { + statusContainer: { clear: vi.fn() }, + runtimeHost: { importFromJsonl }, + showError, + showStatus, + showExtensionConfirm, + handleRuntimeSessionChange: vi.fn(async () => {}), + renderCurrentSessionState: vi.fn(), + handleFatalRuntimeError: vi.fn(async () => { + throw new Error("unexpected fatal error"); + }), + promptForMissingSessionCwd: vi.fn(async () => undefined), + getPathCommandArgument: interactiveModePrototype.getPathCommandArgument, + }; + + await interactiveModePrototype.handleImportCommand.call(context, "/import john's/session.jsonl"); + + expect(importFromJsonl).toHaveBeenCalledWith("john's/session.jsonl"); + expect(showError).not.toHaveBeenCalled(); + expect(showStatus).toHaveBeenCalledWith("Session imported from: john's/session.jsonl"); + }); + + it("shows a non-fatal error when /import path does not exist", async () => { + const importFromJsonl = vi.fn(async () => { + throw new SessionImportFileNotFoundError("/tmp/missing-session.jsonl"); + }); + const showExtensionConfirm = vi.fn(async () => true); + const showStatus = vi.fn(); + const showError = vi.fn(); + const handleFatalRuntimeError = vi.fn(async () => { + throw new Error("unexpected fatal error"); + }); + + const context: ImportCommandContext = { + statusContainer: { clear: vi.fn() }, + runtimeHost: { importFromJsonl }, + showError, + showStatus, + showExtensionConfirm, + handleRuntimeSessionChange: vi.fn(async () => {}), + renderCurrentSessionState: vi.fn(), + handleFatalRuntimeError, + promptForMissingSessionCwd: vi.fn(async () => undefined), + getPathCommandArgument: interactiveModePrototype.getPathCommandArgument, + }; + + await interactiveModePrototype.handleImportCommand.call(context, "/import /tmp/missing-session.jsonl"); + + expect(showError).toHaveBeenCalledWith("Failed to import session: File not found: /tmp/missing-session.jsonl"); + expect(showStatus).not.toHaveBeenCalled(); + expect(handleFatalRuntimeError).not.toHaveBeenCalled(); + }); +});