fix(coding-agent,tui): drop typebox compiler shim and fix progress

This commit is contained in:
Mario Zechner
2026-04-22 21:12:20 +02:00
parent 35ff2689ee
commit 3dacaa8ed9
18 changed files with 130 additions and 60 deletions

View File

@@ -2,6 +2,10 @@
## [Unreleased]
### Breaking Changes
- Migrated public TypeBox-facing types and examples from `@sinclair/typebox` 0.34.x to `typebox` 1.x. Install and import from `typebox` instead of relying on `@sinclair/typebox` transitively ([#3112](https://github.com/badlogic/pi-mono/issues/3112))
### Added
- Added `terminate: true` tool-result hints to skip the automatic follow-up LLM call when every finalized tool result in the current batch opts into early termination ([#3525](https://github.com/badlogic/pi-mono/issues/3525))

View File

@@ -367,7 +367,7 @@ const agent = new Agent({
Define tools using `AgentTool`:
```typescript
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
const readFileTool: AgentTool = {
name: "read_file",

View File

@@ -2,6 +2,10 @@
## [Unreleased]
### Breaking Changes
- Migrated TypeBox support from `@sinclair/typebox` 0.34.x plus AJV to `typebox` 1.x plus TypeBox's built-in validator and value-conversion APIs. Tool argument validation now runs in eval-restricted JavaScript runtimes such as Cloudflare Workers and other environments that disallow `eval` / `new Function`, instead of being silently skipped. Migration: install and import from `typebox` instead of `@sinclair/typebox`, and retest any coercion-sensitive tool paths that serialize schemas to plain JSON because those now go through the new TypeBox-based validation and coercion path rather than AJV ([#3112](https://github.com/badlogic/pi-mono/issues/3112))
### Fixed
- Fixed `google-gemini-cli` built-in model discovery to include `gemini-3.1-flash-lite-preview`, so Cloud Code Assist model lists expose it without requiring manual `--model` fallback selection ([#3545](https://github.com/badlogic/pi-mono/issues/3545))

View File

@@ -7234,7 +7234,7 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 64000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"mimo-v2-pro": {
id: "mimo-v2-pro",
@@ -7251,7 +7251,41 @@ export const MODELS = {
cacheWrite: 0,
},
contextWindow: 1048576,
maxTokens: 64000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"mimo-v2.5": {
id: "mimo-v2.5",
name: "MiMo V2.5",
api: "openai-completions",
provider: "opencode-go",
baseUrl: "https://opencode.ai/zen/go/v1",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.4,
output: 2,
cacheRead: 0.08,
cacheWrite: 0,
},
contextWindow: 262144,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"mimo-v2.5-pro": {
id: "mimo-v2.5-pro",
name: "MiMo V2.5 Pro",
api: "openai-completions",
provider: "opencode-go",
baseUrl: "https://opencode.ai/zen/go/v1",
reasoning: true,
input: ["text"],
cost: {
input: 1,
output: 3,
cacheRead: 0.2,
cacheWrite: 0,
},
contextWindow: 1048576,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"minimax-m2.5": {
id: "minimax-m2.5",
@@ -7697,6 +7731,23 @@ export const MODELS = {
contextWindow: 1000000,
maxTokens: 128000,
} satisfies Model<"openai-completions">,
"arcee-ai/trinity-large-preview": {
id: "arcee-ai/trinity-large-preview",
name: "Arcee AI: Trinity Large Preview",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: false,
input: ["text"],
cost: {
input: 0.15,
output: 0.44999999999999996,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 131000,
maxTokens: 4096,
} satisfies Model<"openai-completions">,
"arcee-ai/trinity-large-preview:free": {
id: "arcee-ai/trinity-large-preview:free",
name: "Arcee AI: Trinity Large Preview (free)",
@@ -8085,7 +8136,7 @@ export const MODELS = {
cacheRead: 0.024999999999999998,
cacheWrite: 0.08333333333333334,
},
contextWindow: 1000000,
contextWindow: 1048576,
maxTokens: 8192,
} satisfies Model<"openai-completions">,
"google/gemini-2.0-flash-lite-001": {
@@ -11301,6 +11352,40 @@ export const MODELS = {
contextWindow: 1048576,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"xiaomi/mimo-v2.5": {
id: "xiaomi/mimo-v2.5",
name: "Xiaomi: MiMo-V2.5",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true,
input: ["text", "image"],
cost: {
input: 0.39999999999999997,
output: 2,
cacheRead: 0.08,
cacheWrite: 0,
},
contextWindow: 1048576,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"xiaomi/mimo-v2.5-pro": {
id: "xiaomi/mimo-v2.5-pro",
name: "Xiaomi: MiMo-V2.5-Pro",
api: "openai-completions",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
reasoning: true,
input: ["text"],
cost: {
input: 1,
output: 3,
cacheRead: 0.19999999999999998,
cacheWrite: 0,
},
contextWindow: 1048576,
maxTokens: 131072,
} satisfies Model<"openai-completions">,
"z-ai/glm-4-32b": {
id: "z-ai/glm-4-32b",
name: "Z.ai: GLM 4 32B ",

View File

@@ -1,5 +1,5 @@
import type Anthropic from "@anthropic-ai/sdk";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { describe, expect, it } from "vitest";
import { getModel } from "../src/models.js";
import { streamAnthropic } from "../src/providers/anthropic.js";

View File

@@ -2,18 +2,20 @@
## [Unreleased]
### Breaking Changes
- Migrated first-party coding-agent code, SDK/examples/docs, and package metadata from `@sinclair/typebox` 0.34.x to `typebox` 1.x. New extensions, SDK integrations, and pi packages should depend on and import from `typebox`. Legacy extension loading still aliases the root `@sinclair/typebox` package, but `@sinclair/typebox/compiler` is no longer shimmed. This migration also picks up the new `@mariozechner/pi-ai` TypeBox-native validator path, so tool argument validation now works in eval-restricted runtimes such as Cloudflare Workers instead of being skipped ([#3112](https://github.com/badlogic/pi-mono/issues/3112))
- Session-replacement commands now invalidate captured pre-replacement session-bound extension objects after `ctx.newSession()`, `ctx.fork()`, and `ctx.switchSession()`. Old `pi` and command `ctx` references now throw instead of silently targeting the replaced session. Migration: if code needs to keep working in the replacement session after one of those calls, pass `withSession` to that same method and do the post-switch work there. In practice, move post-switch `pi.sendUserMessage()`, `pi.sendMessage()`, and command-ctx/session-manager access into `withSession`, and use only the `ReplacedSessionContext` passed to that callback for session-bound operations. Footguns: `withSession` runs after the old extension instance has already received `session_shutdown`, old cleanup may already have invalidated captured state, captured old `pi` / old command `ctx` are stale, and previously extracted raw objects such as `const sm = ctx.sessionManager` remain the caller's responsibility and must not be reused after the switch.
### Added
- Added `structured-output.ts` extension example plus extension docs for terminating tool results, showing how a custom tool can return `terminate: true` so the agent ends on the tool call without an extra follow-up LLM turn ([#3525](https://github.com/badlogic/pi-mono/issues/3525))
- Added support for terminating tool results via `terminate: true`, allowing custom tools to end the current tool batch without an automatic follow-up LLM call, plus a `structured-output.ts` extension example and extension docs showing the pattern ([#3525](https://github.com/badlogic/pi-mono/issues/3525))
- Added OSC 9;4 terminal progress indicators during agent streaming and compaction, so terminals like iTerm2, WezTerm, Windows Terminal, and Kitty show activity in their tab bar
- Added `ctx.ui.addAutocompleteProvider(...)` for stacking extension autocomplete providers on top of the built-in slash/path provider, plus a `github-issue-autocomplete.ts` example and extension docs ([#2983](https://github.com/badlogic/pi-mono/issues/2983))
### Breaking Changes
- Session-replacement commands now invalidate captured pre-replacement session-bound extension objects after `ctx.newSession()`, `ctx.fork()`, and `ctx.switchSession()`. Old `pi` and command `ctx` references now throw instead of silently targeting the replaced session. Migration: if code needs to keep working in the replacement session after one of those calls, pass `withSession` to that same method and do the post-switch work there. In practice, move post-switch `pi.sendUserMessage()`, `pi.sendMessage()`, and command-ctx/session-manager access into `withSession`, and use only the `ReplacedSessionContext` passed to that callback for session-bound operations. Footguns: `withSession` runs after the old extension instance has already received `session_shutdown`, old cleanup may already have invalidated captured state, captured old `pi` / old command `ctx` are stale, and previously extracted raw objects such as `const sm = ctx.sessionManager` remain the caller's responsibility and must not be reused after the switch.
### Fixed
- Fixed exported session HTML to sanitize markdown link URLs before rendering them into anchor tags, blocking `javascript:`-style payloads while preserving safe links in shared/exported sessions ([#3532](https://github.com/badlogic/pi-mono/issues/3532))
- Fixed `ctx.getSystemPrompt()` inside `before_agent_start` to reflect chained system-prompt changes made by earlier `before_agent_start` handlers, and clarified the extension docs around provider-payload rewrites and what `ctx.getSystemPrompt()` does and does not report ([#3539](https://github.com/badlogic/pi-mono/issues/3539))
- Fixed built-in `google-gemini-cli` model lists and selector entries to include `gemini-3.1-flash-lite-preview`, so Cloud Code Assist users no longer need manual `--model` fallback selection to use it ([#3545](https://github.com/badlogic/pi-mono/issues/3545))
- Fixed extension session-replacement flows so `ctx.newSession()`, `ctx.fork()`, `ctx.switchSession()`, and imported-session replacements fully rebind before post-switch work runs, added `withSession` replacement callbacks with fresh `ReplacedSessionContext` helpers, and make stale pre-replacement `pi` / `ctx` session-bound accesses throw instead of silently targeting the wrong session ([#2860](https://github.com/badlogic/pi-mono/issues/2860))

View File

@@ -57,7 +57,7 @@ Create `~/.pi/agent/extensions/my-extension.ts`:
```typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// React to events
@@ -139,7 +139,7 @@ To share extensions via npm or git as pi packages, see [packages.md](packages.md
| Package | Purpose |
|---------|---------|
| `@mariozechner/pi-coding-agent` | Extension types (`ExtensionAPI`, `ExtensionContext`, events) |
| `@sinclair/typebox` | Schema definitions for tool parameters |
| `typebox` | Schema definitions for tool parameters |
| `@mariozechner/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
| `@mariozechner/pi-tui` | TUI components for custom rendering |
@@ -1139,7 +1139,7 @@ Example tool the LLM can call to trigger reload:
```typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
pi.registerCommand("reload-runtime", {
@@ -1186,7 +1186,7 @@ Use `promptSnippet` to opt a custom tool into a one-line entry in `Available too
See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full example.
```typescript
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { StringEnum } from "@mariozechner/pi-ai";
pi.registerTool({
@@ -1657,7 +1657,7 @@ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
### Tool Definition
```typescript
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { StringEnum } from "@mariozechner/pi-ai";
import { Text } from "@mariozechner/pi-tui";

View File

@@ -158,7 +158,7 @@ If no `pi` manifest is present, pi auto-discovers resources from these directori
Third party runtime dependencies belong in `dependencies` in `package.json`. Dependencies that do not register extensions, skills, prompt templates, or themes also belong in `dependencies`. When pi installs a package from npm or git, it runs `npm install`, so those dependencies are installed automatically.
Pi bundles core packages for extensions and skills. If you import any of these, list them in `peerDependencies` with a `"*"` range and do not bundle them: `@mariozechner/pi-ai`, `@mariozechner/pi-agent-core`, `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, `@sinclair/typebox`.
Pi bundles core packages for extensions and skills. If you import any of these, list them in `peerDependencies` with a `"*"` range and do not bundle them: `@mariozechner/pi-ai`, `@mariozechner/pi-agent-core`, `@mariozechner/pi-coding-agent`, `@mariozechner/pi-tui`, `typebox`.
Other pi packages must be bundled in your tarball. Add them to `dependencies` and `bundledDependencies`, then reference their resources through `node_modules/` paths. Pi loads packages with separate module roots, so separate installs do not collide or share modules.

View File

@@ -529,7 +529,7 @@ const { session } = await createAgentSession({
### Custom Tools
```typescript
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { createAgentSession, defineTool } from "@mariozechner/pi-coding-agent";
// Inline custom tool
@@ -882,7 +882,7 @@ interface LoadExtensionsResult {
```typescript
import { getModel } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import {
AuthStorage,
bashTool,

View File

@@ -140,7 +140,7 @@ See [docs/extensions.md](../../docs/extensions.md) for full documentation.
```typescript
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// Subscribe to lifecycle events

View File

@@ -7,7 +7,7 @@
import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Text } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
interface StructuredOutputDetails {
headline: string;

View File

@@ -29,7 +29,6 @@ import { createEventBus, type EventBus } from "../event-bus.js";
import type { ExecOptions } from "../exec.js";
import { execCommand } from "../exec.js";
import { createSyntheticSourceInfo } from "../source-info.js";
import * as _bundledTypeboxCompilerCompat from "./typebox-compiler-compat.js";
import type {
Extension,
ExtensionAPI,
@@ -47,11 +46,9 @@ const VIRTUAL_MODULES: Record<string, unknown> = {
typebox: _bundledTypebox,
"typebox/compile": _bundledTypeboxCompile,
"typebox/value": _bundledTypeboxValue,
"typebox/compiler": _bundledTypeboxCompilerCompat,
"@sinclair/typebox": _bundledTypebox,
"@sinclair/typebox/compile": _bundledTypeboxCompile,
"@sinclair/typebox/value": _bundledTypeboxValue,
"@sinclair/typebox/compiler": _bundledTypeboxCompilerCompat,
"@mariozechner/pi-agent-core": _bundledPiAgentCore,
"@mariozechner/pi-tui": _bundledPiTui,
"@mariozechner/pi-ai": _bundledPiAi,
@@ -67,14 +64,6 @@ const require = createRequire(import.meta.url);
*/
let _aliases: Record<string, string> | null = null;
function resolveTypeboxCompilerCompatPath(baseDir: string): string {
const jsPath = path.resolve(baseDir, "typebox-compiler-compat.js");
if (fs.existsSync(jsPath)) {
return jsPath;
}
return path.resolve(baseDir, "typebox-compiler-compat.ts");
}
function getAliases(): Record<string, string> {
if (_aliases) return _aliases;
@@ -84,7 +73,6 @@ function getAliases(): Record<string, string> {
const typeboxEntry = require.resolve("typebox");
const typeboxCompileEntry = require.resolve("typebox/compile");
const typeboxValueEntry = require.resolve("typebox/value");
const typeboxCompilerCompat = resolveTypeboxCompilerCompatPath(__dirname);
const packagesRoot = path.resolve(__dirname, "../../../../");
const resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {
@@ -104,11 +92,9 @@ function getAliases(): Record<string, string> {
typebox: typeboxEntry,
"typebox/compile": typeboxCompileEntry,
"typebox/value": typeboxValueEntry,
"typebox/compiler": typeboxCompilerCompat,
"@sinclair/typebox": typeboxEntry,
"@sinclair/typebox/compile": typeboxCompileEntry,
"@sinclair/typebox/value": typeboxValueEntry,
"@sinclair/typebox/compiler": typeboxCompilerCompat,
};
return _aliases;

View File

@@ -1,19 +0,0 @@
import { Code, Compile, Validator } from "typebox/compile";
export { Code, Compile, Validator };
// Legacy @sinclair/typebox/compiler compatibility for extensions.
export const TypeCompiler = {
Compile,
};
// In TypeBox 0.x this was a named export. Map it to the v1 Validator class.
export const TypeCheck = Validator;
export default {
Code,
Compile,
Validator,
TypeCompiler,
TypeCheck,
};

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,10 @@
## [Unreleased]
### Breaking Changes
- Migrated Mom's TypeBox-based tool examples and runtime dependency from `@sinclair/typebox` 0.34.x to `typebox` 1.x. Install and import from `typebox` instead of `@sinclair/typebox` when authoring Mom tools and integrations ([#3112](https://github.com/badlogic/pi-mono/issues/3112))
## [0.68.1] - 2026-04-22
## [0.68.0] - 2026-04-20

View File

@@ -646,7 +646,7 @@ Mom runs bash commands inside a sandbox (Docker container), but sometimes you ne
```typescript
// data/tools/gmail/index.ts
import type { MomCustomTool, ToolAPI } from "@mariozechner/pi-mom";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { StringEnum } from "@mariozechner/pi-ai";
const tool: MomCustomTool = {
@@ -691,7 +691,7 @@ export default tool;
### MomCustomTool Type
```typescript
import type { TSchema, Static } from "@sinclair/typebox";
import type { TSchema, Static } from "typebox";
export interface MomToolResult<TDetails = any> {
content: Array<{ type: "text"; text: string } | { type: "image"; data: string; mimeType: string }>;
@@ -798,7 +798,7 @@ async function loadCustomTools(dataDir: string): Promise<LoadedTool[]> {
Mom has a single `invoke_tool` tool that dispatches to custom tools:
```typescript
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
function createInvokeToolTool(loadedTools: LoadedTool[]): AgentTool {
const toolMap = new Map(loadedTools.map(t => [t.tool.name, t.tool]));
@@ -889,7 +889,7 @@ function schemaToSimpleJson(schema: TSchema): object {
```typescript
// data/tools/gmail/index.ts
import type { MomCustomTool, ToolAPI } from "@mariozechner/pi-mom";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { StringEnum } from "@mariozechner/pi-ai";
import Imap from "imap";
import nodemailer from "nodemailer";

View File

@@ -363,8 +363,8 @@ export class ProcessTerminal implements Terminal {
setProgress(active: boolean): void {
if (active) {
// OSC 9;4;1;0 - indeterminate progress
process.stdout.write("\x1b]9;4;1;0\x07");
// OSC 9;4;3 - indeterminate progress
process.stdout.write("\x1b]9;4;3\x07");
} else {
// OSC 9;4;0 - clear progress
process.stdout.write("\x1b]9;4;0;\x07");

View File

@@ -2,6 +2,10 @@
## [Unreleased]
### Breaking Changes
- Migrated the web UI's TypeBox-based tool definitions and runtime dependency from `@sinclair/typebox` 0.34.x to `typebox` 1.x. Install and import from `typebox` instead of `@sinclair/typebox` when embedding or extending `@mariozechner/pi-web-ui` with shared TypeBox schemas ([#3112](https://github.com/badlogic/pi-mono/issues/3112))
### Fixed
- Render SVG artifact previews through a blob-backed image instead of injecting untrusted SVG markup into the page DOM ([#3552](https://github.com/badlogic/pi-mono/issues/3552))