draft: add lightweight TUI localization foundation#1230
Draft
jiwangyihao wants to merge 3857 commits into
Draft
Conversation
fix(agent): calculate costs in proxy stream path
…-drive-letter fix(extensibility): treat Windows drive-letter paths as filesystem in legacy-pi mirror
…arkers - Added a Kimi ToolCallHealer to strip leaked token markers while buffering partial stream chunks. - Routed OpenAI completion deltas through healing and emitted cleaned text plus parsed toolCall blocks. - Implemented healer finalization to normalize IDs, repair JSON arguments, and flush pending calls at stream end. - Added regression SSE tests and CHANGELOG notes for marker leaks, split calls, multi-calls, and unknown tokens.
- Added opt-in telemetry configuration to Agent and session APIs, including Agent#setTelemetry mutator. - Implemented OpenTelemetry spans for invoke_agent, chat, execute_tool, and handoff paths with metadata and step tracking. - Added a telemetry helper module, OpenTelemetry request/usage types, and dependency wiring with no-op behavior when tracer SDK is absent. - Added OTEL end-to-end tests and fixed coding-agent OutputSink realignment and artifact-link newline output issues.
- Adjusted OutputSink to disable head retention after replace(), resetting counters so later pushes append to the tail and do not trigger stale middle-elision in dump(). - Refined artifact link emission to insert a newline separator only when the minimized output lacked one. - Added a regression test for replace-plus-push ordering that verifies no elision marker and aligned byte counts.
…k handoffs - Task tool sessions now expose and forward parent OpenTelemetry config when creating subagent tasks. - Subprocess execution now derives child telemetry from the parent config with the subagent identity and child session conversation handling. - Subagent creation now records a handoff span using the resolved parent telemetry handle before running the child loop.
- Replaced `GenAIAttr` with an `export const enum` in telemetry while preserving all GenAI attribute constants. - Updated the OTEL stream test fixture to emit an `error` event with an `error` payload instead of a `done` event. - Added runSubprocess telemetry propagation tests for inheriting parent telemetry and handling missing parent telemetry.
- Added OpenAI remote-compaction API support with provider-specific endpoint gating. - Added buildOpenAiNativeHistory, token-budget estimation, and message trimming for remote-compaction. - Added helpers to preserve and validate remote-compaction metadata in request/response handling. - Refactored coding-agent compaction to consume pi-ai remote-compaction helpers with converted message history. - Exported remote-compaction from ai index and documented the new APIs in CHANGELOG.
…ray helpers - Updated plan-mode and hindsight session state lookups to use Array.findLast for selecting the latest assistant or user message. - Updated postmortem callback iteration to use Array.toReversed before mapping cleanup callbacks.
- Added `run-collector` exports, `agentLoopDetailed`, and `agentLoopContinueDetailed` APIs. - Expanded `agent_end` event payloads with optional `telemetry` and `coverage` fields. - Added `AgentRunCollector` span tracking with typed chat/tool records and summary/coverage builders. - Fixed telemetry totals to include interrupted, skipped, and failed tool/chat paths via `failChatSpan` and skip recording.
- Added `runEnded`/`markRunEnded()` tracking and only fired `telemetry.onRunEnd` once per run. - Added `agent_end` telemetry support for per-run `telemetry`/`coverage` and `agentLoopDetailed()` with `detailed()`. - Added `aggregateAgentRunSummaries`/`aggregateAgentRunCoverage` and mapped `execute_tool` outcomes to `blocked` and `skipped`. - Updated `finishInvokeAgentSpan` to derive failure `error.type`/status text from run status and exception state. - Added run-summary test helpers covering `agent_end`, aggregation, and `onRunEnd` warning/compatibility scenarios.
- Added createMockModel() returning model, calls, stream, push, and reset helpers for mock streams. - Added registerMockApi() and streamMock() to register MOCK_API, validate model ownership, and resolve handlers by responses, extras, then fallback. - Implemented runMock() to normalize outputs, emit block stream events, and handle delays, aborts, errors, stop reasons, usage, and tool calls. - Added mock-provider entrypoint export, changelog notes, and broader test coverage for cleanup, sequencing, async/sync scripts, and failures.
- Restored default stream idle timeout from 30s back to 120s (regression from 15.0.0). - Fixed `iterateWithIdleTimeout` to only flip `awaitingFirstItem` on real progress items, not synthetic `start` events. - Marked provider `start` events as non-progress keepalives in the lazy-stream wrapper.
- Added resolveAttributes, normalizeProvider, normalizeAgentName, onCostDelta, onTelemetryWarning, and contentSerializer hooks to AgentTelemetryConfig. - Added "summary" capture mode emitting bounded dashboard-friendly span attributes without full payloads. - Added recordManualChatTelemetry for instrumenting non-loop model calls. - Introduced TelemetryAttributeContext as a base for TelemetryHookContext.
- Added `onChatUsage` telemetry configuration and `ChatUsageEvent` payload so each chat step with usage now emits a usage event without requiring a cost estimator. - Updated chat span completion paths to await async chat usage emission, and emitted `on_chat_usage_failed` warnings when callbacks or hooks reject. - Awaited `finishChatSpan` in assistant-stream completion and abort flows so telemetry callbacks run before returning from chat completion.
- Replaced custom MockAssistantStream helpers with createMockModel streams across agent tests. - Removed manual queueMicrotask stream-event scripting in favor of scripted mock responses. - Consolidated helper fixtures by deleting local aliases and reusing shared user-message/model helpers. - Updated test assertions to use mock.calls and mock.model metadata for call and context validation.
- Preserved description at the top level when wrapping optional properties with anyOf/null. - Substituted schema-supplied defaults when a required field arrives as null or "null", cloning to prevent cross-call mutation. - Added coercion tests for default substitution, isolation, optional null stripping, and nested JSON deserialization.
…out to OpenAI client - Added OpenAI completions progress detection to treat usage, finish reason, and real content deltas as progress while ignoring keepalive and role-only preamble chunks. - Applied the new detector through the stream idle watchdog via `isProgressItem` so the timeout only advances on meaningful model output. - Derived an SDK request timeout from the stream first-event watchdog window and passed it to the OpenAI client to avoid long pre-header stalls.
…indow progress reporting - Added a task.maxRuntimeMs setting with a default disabled state for per-subagent runtime limits. - Updated subprocess execution to enforce the configured wall-clock timeout, mark timeouts as aborts, and include timeout-specific abort messaging. - Tracked per-turn context size and context window through task progress/results and updated UI renderers to display current context against window with cumulative tokens rendered separately.
- Added bounded SIGINT escalation with `requestCancel()` to force kernel shutdown after 2s. - Removed executor idle-timeout, max-session, heartbeat, and deferred disposal-retry logic. - Added per-session `runQueued()` execution and dead-kernel restart retry flow for owned sessions. - Replaced kernel session tracking with reusable `sessions` map and `acquireSession()` ownership registration. - Removed obsolete owner-cleanup session-capacity tests and updated dead-session mocks to resolver-based flow.
- Swapped deprecated telemetry keys for OpenAIAttr/PiGenAIAttr constants, including tool intent keys. - Normalized provider names via mapProviderNameToOtel and emitted OTEL pi.gen_ai fields on chat/request/response spans. - Revised usage and aggregate telemetry to include cache-token totals plus failed and skipped step counts in run summaries. - Updated OTEL and run-summary tests to use z.object schemas and renamed GenAI/PiGenAI attribute assertions.
- Added canonical `pi.zod` schema API exports and removed TypeBox package exports/imports. - Migrated Tool schema typing from TypeBox to shared `TSchema`/Zod flow with legacy TypeBox compatibility. - Updated AI provider adapters and MCP/agent builders to convert tool params through `toolWireSchema()`. - Reworked schema validation from AJV to Zod-safe parsing with `fromTypeBox`, `toolWireSchema`, and meta schema checks.
- Replaced fromTypeBox conversion with a JSON-schema validator flow in ai tool handling and execution paths. - Added recursive schema validation and expanded TypeBox checks for refs, enums, uniqueItems, and constraint keywords. - Sanitized Azure/CCA tool schemas by dropping unsupported fields and rewriting oneOf tool branches as anyOf. - Tightened argument and model-config validation, preserving unknown tool fields and adding apiKey plus compatibility flags.
…ox imports to shim - Added a dedicated `@sinclair/typebox` specifier remap path and routed bare legacy imports to the in-repo shim during extension rewriting and module resolution. - Added resolve hooks for both `file` and legacy-file namespaces so legacy extensions load the shim consistently at runtime.
`SessionManager.close()` queues `#closePersistWriterInternal()` on the
persist chain. The task awaits `#persistWriter.close()`, which flips
`#closing = true` synchronously before yielding on its inner writer
`close()`. A concurrent `appendMessage()` landing in that yield window
hit the hot path, got the still-cached (but closing) writer back from
`#ensurePersistWriter()`, and threw `Error("Writer closed")` from
`writeSync`. The throw was stashed into `#persistError`; the next async
caller (`flush()` or a later `appendMessage()`) re-threw it as an
unhandled rejection with the original line-1282 stack.
Expose `NdjsonFileWriter.isOpen()` and treat a mid-close cached writer
as a miss in `#ensurePersistWriter()`. `_persist` now falls back to the
async `#rewriteFile()` cold path so the entry — already in
`#fileEntries` — still lands on disk once the close drains.
- Added note to read tool prompt that the pipe after the hashline anchor is a separator, not part of the file content. - Added note to search tool prompt that the pipe before content is a separator.
…K timeout override - Promotion of healed stop→toolUse now gated on prior stopReason === "stop" so error/length/aborted finishes are preserved. - When a chunk carries both leaked markers and a structured delta.tool_calls, the healer strips visible markers but discards its synthesized calls so structured payloads remain the single source of truth. - Healer no longer finalizes tool calls outside an active <|tool_calls_section_begin|>…<|tool_calls_section_end|> section; bare tokens in assistant prose survive as text. - OpenAI SDK client now honors per-request streamFirstEventTimeoutMs so slow-before-headers providers are not aborted at the env default before the wrapping watchdog arms.
…serve Zod root extras - JSON Schema validator now enforces propertyNames, patternProperties, dependentRequired, dependencies, if/then/else, contains, and prefixItems instead of silently accepting values that violate them; unevaluated* still permissive but warns once. - Recursive $ref no longer short-circuits to true on revisit: cycle detection keys on (ref, value-identity) with a primitive depth cap, so nested sub-schema violations are caught. - Meta-validator now structurally validates if/then/else/dependencies sub-schemas and accepts draft-07 dependencies as either schemas or string-array dependent keys. - Wire-schema null normalization no longer strips null-valued unknown root fields before preserveUnknownRootFields snapshots them, so task.simple and similar callers still see disallowed null arguments.
…ote compaction - buildOpenAiNativeHistory now emits custom_tool_call / custom_tool_call_output for blocks with customWireName (apply_patch and other freeform tools), matching the normal Responses replay path; previously demoted to function_call which broke remote-compaction replay or mismatched the original call. - requestOpenAiRemoteCompaction and requestRemoteCompaction accept an optional AbortSignal; the coding-agent compaction caller forwards the existing signal so cancellation now terminates the in-flight fetch instead of stranding the session in the compacting state until the server replies.
The CLI's login() switch only covered a subset of OAuth providers, so 'omp auth-broker login <provider>' (which shells out to 'pi-ai login') crashed with 'Unknown provider' for perplexity, alibaba-coding-plan, gitlab-duo, huggingface, opencode, ollama, cerebras, fireworks, qianfan, synthetic, venice, litellm, moonshot, together, vercel/cloudflare ai-gateway, vllm, qwen-portal, nvidia, xiaomi, and every custom OAuth provider. The full dispatch already lives in AuthStorage.login(). Delegate to it and drop the duplicated switch.
…hashline-mode-produces-line-du fix(coding-agent): drop hashline anchor echo inserts
…crashes-modulenotfound-resolvi fix(release): list worker --compile entrypoints in CI release script
…rained models
z.unknown() emits {} (empty JSON Schema) as the value for additionalProperties,
items, and other schema-valued positions. This is semantically equivalent to
boolean true per JSON Schema draft 2020-12 §4.3.1, but grammar-constrained
samplers (llama.cpp, etc.) interpret the object form as "generate an empty
object" rather than "any JSON value". Models using such samplers therefore
emit {} for every open-typed field, including extra.title in plan-mode resolve
calls (issue can1357#1179). This affected all tools with open schemas, not just the
resolve tool -- MCP tools and any tool using z.unknown() in value positions
have the same problem.
Fix: normalize any {} in schema-valued positions to true in two places:
- zodToWireSchema postProcess/walk: covers Zod-based tools across all providers
- normalizeOpenAIResponsesSchemaNode: covers all tools (Zod, TypeBox, MCP)
going to openai-responses-compatible endpoints (llama.cpp, Azure Responses)
Adds isJsonObjectEmpty() helper to schema/types.ts (for...in based, no allocation).
Fixes can1357#1179
…title
The schema-level fix (normalizing {} to true in open schema positions)
makes the prompts correct-by-construction: models that follow the schema
grammar now emit string values for extra.title without needing softer
prompt language. Revert the SHOULD/optional hedging to the original MUST
wording per maintainer review.
Fixes can1357#1179
…ust openai
Move {}→true normalization out of zodToWireSchema/postProcess (Zod-only)
into a dedicated, exported normalizeEmptySchemas helper that toolWireSchema
calls for both the Zod and TypeBox/raw-JSON-Schema branches. Every provider
that calls toolWireSchema (OpenAI, Anthropic, Google, Ollama, Bedrock,
Cursor) now receives the normalized schema regardless of how the tool was
authored.
Verified strict-mode opt-out across providers:
- OpenAI: hasUnrepresentableStrictObjectMap hits === true branch (was
isJsonObject({}) branch), same result -> strict: false.
- Anthropic: normalizeAnthropicStrictSchemaNode opts out via
additionalProperties !== false (still true for true) -> strict: false.
- Google: normalizeSchemaForGoogle strips additionalProperties regardless
(UNSUPPORTED_SCHEMA_FIELDS, pre-existing behavior).
The normalizeOpenAIResponsesSchemaNode guard stays as a safety net for
callers that invoke sanitizeSchemaForOpenAIResponses directly on schemas
that bypass the wire-schema pipeline (e.g. fixtures, debug paths).
Adds cross-provider tests covering both Zod and TypeBox inputs and verifying
downstream provider behavior with the normalized form.
Fixes can1357#1179
…b-a3b-mtp-still-not-able-to-en fix(plan): derive plan title when resolve.extra.title is not a string
…d path resolution - Added splitInternalUrlSel to iteratively peel internal-URL selector chunks while preserving unsupported schemes like mcp://. - Updated ReadTool to use the internal splitter before routing so selector parsing is handled via parseSel. - Added unit tests covering malformed selectors, namespaced skill hosts, and unchanged behavior for non-URLs or unsupported schemes.
- Preserved launch and attach request failures when configurationDone also fails. - Handled initial stop-outcome watcher rejections for failed launch and attach attempts. - Rejected directory-valued debug launch programs before adapter selection and documented the debugpy launch shape. Fixes can1357#1187
…-schema normalization Following fcb0f99 ({}→true normalization applied to all providers via toolWireSchema), the explicit additionalProperties: {} input now arrives at the Anthropic normalizer as additionalProperties: true. Both forms are semantically equivalent open-map per JSON Schema 2020-12 §4.3.1.
- Set `GITHUB_ACTIONS` to an empty value for the `Test workspace (TS)` step so Bun's `::group::` markers are not emitted as log text. - Kept the override scoped to that step to avoid impacting other workflow steps while preserving normal annotation behavior.
Replaces the parallel `speed` knob with the existing `serviceTier`
concept. The anthropic-messages provider now realizes
`serviceTier: "priority"` by setting `speed: "fast"` on the wire and
appending the `fast-mode-2026-02-01` beta header; other providers
continue to pass `service_tier` through natively or ignore it.
User-facing impact:
- `/fast` no longer dispatches on model.api. It just toggles
`serviceTier: "priority"`. Anthropic-specific translation lives
entirely in the provider.
- Anthropic auto-fallback marker is now the generic `"priority"`
identifier in `AssistantMessage.disabledFeatures` instead of
`"anthropic.fast_mode"`.
- New `clearAnthropicFastModeFallback(providerSessionState)` export is
invoked from `AgentSession.setServiceTier` when transitioning into
`"priority"`, so re-running `/fast on` after the provider
auto-disabled fast mode actually re-arms the next request instead of
silently no-oping.
Provider-side cleanups:
- Tightened cast site (`ParamsWithSpeed` alias) for the typed
`speed: "fast"` injection.
- Widened the rejection matcher (`\bspeed\b` + `not support`) so
phrasing drift ("is not supported" vs "does not support", quoted vs
backticked) doesn't break the fallback.
Dropped from the PR:
- `Agent.speed` / `AgentOptions.speed` / `SimpleStreamOptions.speed`
fields.
- `SpeedChangeEntry` and `appendSpeedChange` from the session entry
schema; service-tier change entries already cover this.
- `AgentSession.setSpeed` / `.speed` and the previousSpeed
capture/restore in `switchSession` — collapsed back into
`setServiceTier` + previousServiceTier, which now covers the rollback
too.
…only) Two new `ServiceTier` values let users target priority/fast mode at one provider family without paying premium costs on the other when switching models mid-session: - `"openai-only"` → resolves to `"priority"` on `openai` and `openai-codex`; `undefined` everywhere else. - `"claude-only"` → resolves to `"priority"` on direct `anthropic`; `undefined` on Bedrock/Vertex Claude and elsewhere. Implementation centers on a new `resolveServiceTier(serviceTier, provider)` helper exported from `@oh-my-pi/pi-ai`. The three OpenAI providers and the Anthropic provider all route through it, replacing the previous `shouldSendServiceTier` type-guard pattern (which couldn't survive scoped values — the input variable's literal type stops matching the wire type once scopes are introduced). `shouldSendServiceTier` is kept as a plain boolean for external callers but no longer narrows the input. `getPriorityPremiumRequests` is reworked: it now counts Anthropic + `"priority"` (fast mode) as one premium request — the original PR introduced the realization but didn't update billing — and continues to ignore providers that silently drop the field on the wire. User-facing: - `serviceTier` setting enum gains `"openai-only"` and `"claude-only"` with clear UI descriptions. - `/fast on` still sets the unscoped `"priority"`, but `/fast status` and `isFastModeEnabled()` now report `on` for any priority-granting tier (including scoped values). `/fast off` clears to `undefined` regardless of scope. - The Anthropic auto-fallback listener and re-arm clearing both cover `"priority"` and `"claude-only"` (the two values that grant priority on Anthropic). `"openai-only"` doesn't trigger the anthropic fallback even if the user is on an Anthropic model — by design. Tests cover all four resolver branches (unscoped passthrough, openai-only match/miss, claude-only match/miss), Anthropic provider's wire `speed` field under each scope, and updated premium accounting.
feat(ai,coding-agent): added Anthropic fast mode with auto-fallback
…ion-shape fix(acp): include execute metadata in bash permission requests
…t-mode icon - Expanded `isAnthropicFastModeUnsupportedError` to treat 429 `rate_limit_error` responses mentioning fast mode as unsupported alongside 400 `invalid_request_error` speed-rejection cases. - Added tests for unsupported-fast-mode detection covering 400, 429, and unrelated error payloads. - Added `AgentSession.isFastModeActive()` with provider-scoped resolution and switched status-line rendering to use it for the fast-mode icon.
…seems-to-prepend-a-space-on-ea fix(edit): stripped hashline separator padding
…launch-failures-can-crash-omp- fix(debug): preserve DAP launch failures
- Added `DirtyState` and `inspect_dirty_state` in `git_ops` to report uncommitted, unpushed, and summary state. - Updated `_drive_turn` to recheck completion each cycle and emit dirty-state reminders or exit when clean. - Wired `dirty_state_reminder` into persona rendering with `uncommitted`, `unpushed`, and `summary` context. - Added `dirty_state_reminder.md` guidance for fixing/restoring changes and pushing only after `bun run fix` completes. - Added worker tests for dirty, clean, and persistent-dirty flows and prompt-count assertions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
i18nhelper for TUI/display strings.en-USresources for a small initial key set.Why
Refs #1229
This draft PR is a small implementation sketch for lightweight TUI localization support. English remains the default, only a few TUI strings are migrated, and the goal is to validate the API shape before broader localization work.
Non-goals:
Testing
bun test packages/coding-agent/test/i18n.test.tsbun --cwd=packages/coding-agent run checkNote: I also attempted workspace
bun run test:ts; it failed/timed out in unrelated packages/platform-sensitive tests (pi-natives,pi-ai,pi-tui) and did not isolate a failure in thiscoding-agentchange.Checklist
bun check/ package check run locally