@oh-my-pi/pi-coding-agent 15.5.4 → 15.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +48 -2
  2. package/dist/types/config/settings-schema.d.ts +50 -2
  3. package/dist/types/edit/hashline/diff.d.ts +6 -1
  4. package/dist/types/edit/hashline/execute.d.ts +1 -2
  5. package/dist/types/edit/hashline/params.d.ts +4 -5
  6. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +23 -0
  7. package/dist/types/lib/xai-http.d.ts +40 -0
  8. package/dist/types/session/agent-session.d.ts +1 -0
  9. package/dist/types/tools/fetch.d.ts +19 -0
  10. package/dist/types/tools/find.d.ts +7 -0
  11. package/dist/types/tools/image-gen.d.ts +6 -2
  12. package/dist/types/tools/index.d.ts +1 -0
  13. package/dist/types/tools/plan-mode-guard.d.ts +5 -6
  14. package/dist/types/tools/tts.d.ts +18 -0
  15. package/package.json +8 -8
  16. package/scripts/build-binary.ts +11 -0
  17. package/src/config/model-registry.ts +41 -9
  18. package/src/config/settings-schema.ts +43 -2
  19. package/src/edit/diff.ts +5 -3
  20. package/src/edit/hashline/diff.ts +11 -4
  21. package/src/edit/hashline/execute.ts +3 -10
  22. package/src/edit/hashline/params.ts +10 -3
  23. package/src/edit/index.ts +9 -12
  24. package/src/edit/renderer.ts +14 -7
  25. package/src/edit/streaming.ts +15 -128
  26. package/src/extensibility/legacy-pi-ai-shim.ts +24 -0
  27. package/src/extensibility/plugins/legacy-pi-compat.ts +47 -3
  28. package/src/lib/xai-http.ts +124 -0
  29. package/src/main.ts +2 -1
  30. package/src/modes/controllers/selector-controller.ts +7 -2
  31. package/src/modes/interactive-mode.ts +1 -1
  32. package/src/modes/rpc/rpc-client.ts +3 -1
  33. package/src/prompts/tools/find.md +3 -2
  34. package/src/sdk.ts +15 -9
  35. package/src/session/agent-session.ts +48 -5
  36. package/src/tools/fetch.ts +145 -74
  37. package/src/tools/find.ts +38 -6
  38. package/src/tools/image-gen.ts +205 -7
  39. package/src/tools/index.ts +1 -0
  40. package/src/tools/plan-mode-guard.ts +14 -6
  41. package/src/tools/read.ts +57 -3
  42. package/src/tools/search.ts +2 -2
  43. package/src/tools/tts.ts +133 -0
package/CHANGELOG.md CHANGED
@@ -2,10 +2,48 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.5.7] - 2026-05-27
6
+ ### Added
7
+ - `providers.openrouterVariant` setting (Settings → Providers → "OpenRouter Routing") to default OpenRouter requests to a routing-variant suffix (`:nitro`, `:floor`, `:online`, `:exacto`). Selectors that already name a variant (e.g. `openrouter/anthropic/claude-haiku:nitro`) keep precedence.
8
+
9
+ - `generate_image` supports xAI Grok Imagine via `providers.image=xai`. Supports `grok-imagine-image` (default) and `grok-imagine-image-quality` at aspect ratios `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`. Uses the xAI Grok OAuth credential when available, otherwise `XAI_API_KEY`.
10
+ - New `tts` tool synthesises speech via xAI Grok Voice behind the disabled-by-default `tts.enabled` setting. Built-in voices `ara`, `eve` (default), `leo`, `rex`, `sal`; custom voice IDs also accepted. Output codec inferred from the `output_path` suffix (`.wav` → `wav`, else `mp3`). Up to 15,000 characters per request.
11
+
12
+ ### Fixed
13
+
14
+ - Fixed plan-mode re-entry after approval reopening a fresh `local://PLAN.md` instead of the approved titled plan artifact, which could duplicate plan content and fail approval on an existing destination.
15
+ - Fixed `read` URL reader mode aborting after a stalled Jina request instead of falling back to trafilatura/lynx/native: Jina (and Parallel extract) now have their own per-attempt sub-budget capped at 10s, the catch handler honours only real user cancellation, and the in-process native renderer is always attempted on already-loaded HTML ([#1449](https://github.com/can1357/oh-my-pi/issues/1449))
16
+
17
+ ## [15.5.6] - 2026-05-27
18
+ ### Added
19
+
20
+ - Support for multi-range line selectors on URLs (e.g., `:5-10,20-30`) to fetch and display multiple non-contiguous sections
21
+ - Support for combining `:raw` mode with line range selectors on URLs (e.g., `:raw:1-120` or `:1-120:raw`)
22
+ - Support for line range selectors on directory listings (e.g., `:30-40` to view lines 30–40 of a directory tree)
23
+ - Clear error message when requesting a line offset beyond the end of a directory listing
24
+
25
+ ### Changed
26
+
27
+ - URL selector parsing now supports multiple trailing selector tokens (e.g., `:raw:N-M`), applying them left-to-right
28
+
29
+ ### Fixed
30
+
31
+ - Fixed `:raw` selector being ignored for JSON and feed URLs, causing them to be pretty-printed or converted to markdown instead of returning raw content
32
+ - Fixed directory listing line selectors silently dropping the offset parameter and only applying the limit
33
+
34
+ ## [15.5.5] - 2026-05-27
35
+
36
+ ### Changed
37
+
38
+ - Removed the model-facing `path` property from hashline edit tool parameters; hashline edit targets now come from `¶PATH` headers in `input`.
39
+
40
+ ### Fixed
41
+
42
+ - Fixed legacy pi-* extension loading regression where `import { Type } from "@(scope)/pi-ai"` (e.g. `@earendil-works/pi-ai` used by `@plannotator/pi-extension`) failed with `Export named 'Type' not found` after pi-ai 15.1.0 removed the root `Type` runtime export; the legacy-pi compat layer now redirects bare `@oh-my-pi/pi-ai` root imports through a sibling shim that re-exports the canonical pi-ai surface plus the Zod-backed `Type` runtime from the same TypeBox shim served to `@sinclair/typebox` imports ([#1437](https://github.com/can1357/oh-my-pi/issues/1437))
43
+
5
44
  ## [15.5.4] - 2026-05-27
6
45
 
7
46
  ### Breaking Changes
8
-
9
47
  - Removed the package root `hashline` export so imports from the top-level entrypoint can no longer access `hashline` helpers directly
10
48
 
11
49
  ### Added
@@ -31,6 +69,10 @@
31
69
 
32
70
  - Fixed `omp` startup and `/changelog` reading the host project's `CHANGELOG.md` as omp's — `getPackageDir()` no longer falls back to the user's `cwd` when no owning `package.json` is locatable, preventing spurious `lastChangelogVersion` writes ([#1423](https://github.com/can1357/oh-my-pi/issues/1423))
33
71
 
72
+ ### Fixed
73
+
74
+ - Fixed hashline session-chain replay silently overwriting in-session edits when the model re-targeted a previously rewritten line with a stale file hash; replay now refuses unless every edit's anchor line content matches between the snapshot and the current file ([#1422](https://github.com/can1357/oh-my-pi/pull/1422))
75
+
34
76
  ## [15.5.3] - 2026-05-27
35
77
  ### Breaking Changes
36
78
 
@@ -40,6 +82,10 @@
40
82
 
41
83
  - Warned when legacy inline `LINE:TEXT` lines are accepted as payload continuations only when inside a pending multi-line `A-B:` replacement
42
84
 
85
+ ### Fixed
86
+
87
+ - Fixed runtime model registry refresh and cache loading so providers with authoritative dynamic catalogs, including Synthetic, do not re-add deprecated bundled model IDs after discovery ([#1417](https://github.com/can1357/oh-my-pi/issues/1417)).
88
+
43
89
  ## [15.5.2] - 2026-05-26
44
90
  ### Breaking Changes
45
91
 
@@ -8841,4 +8887,4 @@ Initial public release.
8841
8887
  - Git branch display in footer
8842
8888
  - Message queueing during streaming responses
8843
8889
  - OAuth integration for Gmail and Google Calendar access
8844
- - HTML export with syntax highlighting and collapsible sections
8890
+ - HTML export with syntax highlighting and collapsible sections
@@ -2393,6 +2393,15 @@ export declare const SETTINGS_SCHEMA: {
2393
2393
  readonly description: "Enable the calculator tool for basic calculations";
2394
2394
  };
2395
2395
  };
2396
+ readonly "tts.enabled": {
2397
+ readonly type: "boolean";
2398
+ readonly default: false;
2399
+ readonly ui: {
2400
+ readonly tab: "tools";
2401
+ readonly label: "Text-to-Speech";
2402
+ readonly description: "Enable the tts tool for xAI Grok Voice speech synthesis";
2403
+ };
2404
+ };
2396
2405
  readonly "recipe.enabled": {
2397
2406
  readonly type: "boolean";
2398
2407
  readonly default: true;
@@ -3118,7 +3127,7 @@ export declare const SETTINGS_SCHEMA: {
3118
3127
  };
3119
3128
  readonly "providers.image": {
3120
3129
  readonly type: "enum";
3121
- readonly values: readonly ["auto", "openai", "gemini", "openrouter"];
3130
+ readonly values: readonly ["auto", "openai", "antigravity", "xai", "gemini", "openrouter"];
3122
3131
  readonly default: "auto";
3123
3132
  readonly ui: {
3124
3133
  readonly tab: "providers";
@@ -3127,11 +3136,19 @@ export declare const SETTINGS_SCHEMA: {
3127
3136
  readonly options: readonly [{
3128
3137
  readonly value: "auto";
3129
3138
  readonly label: "Auto";
3130
- readonly description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini";
3139
+ readonly description: "Priority: GPT model image tool > Antigravity > xAI > OpenRouter > Gemini";
3131
3140
  }, {
3132
3141
  readonly value: "openai";
3133
3142
  readonly label: "OpenAI";
3134
3143
  readonly description: "Uses the active GPT Responses/Codex model";
3144
+ }, {
3145
+ readonly value: "antigravity";
3146
+ readonly label: "Antigravity";
3147
+ readonly description: "Requires google-antigravity OAuth";
3148
+ }, {
3149
+ readonly value: "xai";
3150
+ readonly label: "xAI Grok Imagine";
3151
+ readonly description: "Requires xAI Grok OAuth or XAI_API_KEY";
3135
3152
  }, {
3136
3153
  readonly value: "gemini";
3137
3154
  readonly label: "Gemini";
@@ -3185,6 +3202,37 @@ export declare const SETTINGS_SCHEMA: {
3185
3202
  }];
3186
3203
  };
3187
3204
  };
3205
+ readonly "providers.openrouterVariant": {
3206
+ readonly type: "enum";
3207
+ readonly values: readonly ["default", "nitro", "floor", "online", "exacto"];
3208
+ readonly default: "default";
3209
+ readonly ui: {
3210
+ readonly tab: "providers";
3211
+ readonly label: "OpenRouter Routing";
3212
+ readonly description: "Default routing-variant suffix appended to OpenRouter model IDs (overridden when the selector already names a variant)";
3213
+ readonly options: readonly [{
3214
+ readonly value: "default";
3215
+ readonly label: "Default";
3216
+ readonly description: "No suffix; use OpenRouter's default routing";
3217
+ }, {
3218
+ readonly value: "nitro";
3219
+ readonly label: ":nitro";
3220
+ readonly description: "Prioritize throughput / lowest latency";
3221
+ }, {
3222
+ readonly value: "floor";
3223
+ readonly label: ":floor";
3224
+ readonly description: "Prioritize cheapest available provider";
3225
+ }, {
3226
+ readonly value: "online";
3227
+ readonly label: ":online";
3228
+ readonly description: "Enable OpenRouter's web-search plugin";
3229
+ }, {
3230
+ readonly value: "exacto";
3231
+ readonly label: ":exacto";
3232
+ readonly description: "Cherry-picked high-quality providers (only defined for select models)";
3233
+ }];
3234
+ };
3235
+ };
3188
3236
  readonly "providers.parallelFetch": {
3189
3237
  readonly type: "boolean";
3190
3238
  readonly default: true;
@@ -12,6 +12,12 @@
12
12
  import { type PatchSection } from "@oh-my-pi/hashline";
13
13
  export interface HashlineDiffOptions {
14
14
  autoDropPureInsertDuplicates?: boolean;
15
+ /**
16
+ * Use the streaming-tolerant applier ({@link PatchSection.applyPartialTo})
17
+ * so trailing in-flight ops do not throw or emit phantom edits. Streaming
18
+ * preview path only.
19
+ */
20
+ streaming?: boolean;
15
21
  }
16
22
  export declare function computeHashlineSectionDiff(section: PatchSection, cwd: string, options?: HashlineDiffOptions): Promise<{
17
23
  diff: string;
@@ -21,7 +27,6 @@ export declare function computeHashlineSectionDiff(section: PatchSection, cwd: s
21
27
  }>;
22
28
  export declare function computeHashlineDiff(input: {
23
29
  input: string;
24
- path?: string;
25
30
  }, cwd: string, options?: HashlineDiffOptions): Promise<{
26
31
  diff: string;
27
32
  firstChangedLine: number | undefined;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Coding-agent runner that drives the hashline {@link Patcher} on behalf of
3
- * the `edit` tool. Converts a `{input, path?}` tool-call payload into a
3
+ * the `edit` tool. Converts a `{input}` tool-call payload into a
4
4
  * fully-applied patch, wraps the result in the agent's
5
5
  * {@link AgentToolResult} shape, and attaches LSP diagnostics + `outputMeta`
6
6
  * for the renderer.
@@ -19,7 +19,6 @@ import { type HashlineParams, hashlineEditParamsSchema } from "./params";
19
19
  export interface ExecuteHashlineSingleOptions {
20
20
  session: ToolSession;
21
21
  input: string;
22
- path?: string;
23
22
  signal?: AbortSignal;
24
23
  batchRequest?: LspBatchRequest;
25
24
  writethrough: WritethroughCallback;
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Zod schema for the `edit` tool's hashline mode payload. The schema is
3
3
  * deliberately permissive (`.passthrough()`) so providers can attach extra
4
- * keys without rejection; only `input` is required and `path` is an
5
- * optional fallback used when the input lacks a `¶PATH#HASH` header.
4
+ * keys without rejection; only `input` is required. `_input` is accepted as a
5
+ * provider-emitted alias for `input`.
6
6
  */
7
7
  import * as z from "zod/v4";
8
- export declare const hashlineEditParamsSchema: z.ZodObject<{
8
+ export declare const hashlineEditParamsSchema: z.ZodPreprocess<z.ZodObject<{
9
9
  input: z.ZodString;
10
- path: z.ZodOptional<z.ZodString>;
11
- }, z.core.$loose>;
10
+ }, z.core.$loose>>;
12
11
  export type HashlineParams = z.infer<typeof hashlineEditParamsSchema>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Compatibility shim for legacy extensions importing the package root of
3
+ * `@oh-my-pi/pi-ai` (or one of its aliased scopes like `@earendil-works/pi-ai`
4
+ * or `@mariozechner/pi-ai`).
5
+ *
6
+ * pi-ai 15.1.0 removed the historical TypeBox root exports (`Type`, plus the
7
+ * runtime-relevant half of the `Static`/`TSchema` pair) from the package
8
+ * entrypoint. Legacy extensions still author parameter schemas as
9
+ * `Type.Object({ ... })`, so this file is served by `legacy-pi-compat.ts` in
10
+ * place of the real pi-ai entrypoint whenever a legacy extension imports the
11
+ * bare package root. Subpath imports (`@oh-my-pi/pi-ai/utils/oauth`, etc.)
12
+ * continue to resolve directly against the bundled pi-ai package.
13
+ *
14
+ * The `Type` runtime is borrowed from the Zod-backed TypeBox shim that
15
+ * already serves bare `@sinclair/typebox` imports for the same extension
16
+ * class, keeping the legacy-compat surface internally consistent.
17
+ *
18
+ * Type-level `Static` and `TSchema` continue to come from pi-ai's own
19
+ * `types.ts` via the `export *` below — pi-ai still exports both as types,
20
+ * only the runtime `Type` builder was removed.
21
+ */
22
+ export * from "@oh-my-pi/pi-ai";
23
+ export { Type } from "./typebox";
@@ -0,0 +1,40 @@
1
+ import type { ModelRegistry } from "../config/model-registry";
2
+ interface XAICredentials {
3
+ provider: "xai-oauth" | "xai";
4
+ apiKey: string;
5
+ baseURL: string;
6
+ }
7
+ export declare function ohMyPiXAIUserAgent(): string;
8
+ /**
9
+ * Resolve xAI credentials for HTTP tool calls.
10
+ *
11
+ * Credential priority:
12
+ * 1. xai-oauth — only when a *dedicated* xai-oauth source exists. Composed
13
+ * of two checks against the registry layer:
14
+ * a. `authStorage.hasNonEnvCredential("xai-oauth")` covers stored
15
+ * credentials (OAuth or api_key), runtime overrides (CLI
16
+ * `--api-key` for xai-oauth), config overrides (models.yml
17
+ * `providers.xai-oauth.apiKey`), and fallback resolvers.
18
+ * b. `$env.XAI_OAUTH_TOKEN` covers the xai-oauth-specific env var.
19
+ * `XAI_API_KEY` is intentionally NOT a signal here, even though the
20
+ * env-fallback map (`stream.ts: "xai-oauth"`) lets xai-oauth borrow it
21
+ * as a back-compat convenience: the borrow lets API-key-only setups
22
+ * satisfy the xai-oauth branch and then resolve baseUrl under
23
+ * xai-oauth instead of xai, silently bypassing `providers.xai.baseUrl`
24
+ * overrides for image/TTS traffic. The gate routes the borrow case to
25
+ * step 2 while preserving every dedicated xai-oauth path.
26
+ * 2. xai (plain API key). Delegates to ModelRegistry.getApiKeyForProvider
27
+ * which runs AuthStorage.getApiKey's full cascade: runtime override →
28
+ * models.yml config override → stored api_key credential → OAuth
29
+ * resolution → XAI_API_KEY env var → custom fallback resolver.
30
+ *
31
+ * baseURL: see `resolveXAIBaseURL` above. Resolved AFTER the credential
32
+ * decision so the scoped (provider, id) lookup is unambiguous. `modelId`
33
+ * is optional; probes / tool-availability checks pass `undefined` and fall
34
+ * through to env/default.
35
+ *
36
+ * Returns null when neither credential is available. Caller is responsible
37
+ * for surfacing an actionable error message in that case.
38
+ */
39
+ export declare function resolveXAIHttpCredentials(modelRegistry: ModelRegistry, modelId?: string): Promise<XAICredentials | null>;
40
+ export {};
@@ -454,6 +454,7 @@ export declare class AgentSession {
454
454
  get goalRuntime(): GoalRuntime;
455
455
  markPlanReferenceSent(): void;
456
456
  setPlanReferencePath(path: string): void;
457
+ getPlanReferencePath(): string;
457
458
  get clientBridge(): ClientBridge | undefined;
458
459
  setClientBridge(bridge: ClientBridge | undefined): void;
459
460
  getCheckpointState(): CheckpointState | undefined;
@@ -1,17 +1,36 @@
1
1
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
2
2
  import { type Component } from "@oh-my-pi/pi-tui";
3
+ import type { Settings } from "../config/settings";
3
4
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
4
5
  import { type Theme } from "../modes/theme/theme";
5
6
  import type { ToolSession } from "../sdk";
7
+ import type { AgentStorage } from "../session/agent-storage";
6
8
  import { type OutputMeta } from "./output-meta";
9
+ import { type LineRange } from "./path-utils";
7
10
  export declare function isReadableUrlPath(value: string): boolean;
8
11
  export interface ParsedReadUrlTarget {
9
12
  path: string;
10
13
  raw: boolean;
11
14
  offset?: number;
12
15
  limit?: number;
16
+ /** Populated only when the selector carries 2+ ranges. Single-range stays on offset/limit. */
17
+ ranges?: readonly LineRange[];
13
18
  }
14
19
  export declare function parseReadUrlTarget(readPath: string): ParsedReadUrlTarget | null;
20
+ /**
21
+ * Render HTML to markdown using Parallel, jina, trafilatura, lynx, then the
22
+ * in-process native converter. The overall `timeout` budget bounds the call,
23
+ * but remote reader requests are additionally capped at `REMOTE_READER_MAX_MS`
24
+ * so that a hung remote endpoint cannot prevent local fallbacks from running.
25
+ * Only a real `userSignal` cancellation aborts the chain — remote per-attempt
26
+ * timeouts and the overall reader-mode timeout still allow later renderers
27
+ * (especially the purely-local native converter) to be tried.
28
+ */
29
+ export declare function renderHtmlToText(url: string, html: string, timeout: number, settings: Settings, userSignal: AbortSignal | undefined, storage: AgentStorage | null): Promise<{
30
+ content: string;
31
+ ok: boolean;
32
+ method: string;
33
+ }>;
15
34
  interface FetchImagePayload {
16
35
  data: string;
17
36
  mimeType: string;
@@ -23,6 +23,13 @@ export type FindToolInput = z.infer<typeof findSchema>;
23
23
  * must pass through.
24
24
  */
25
25
  export declare function validateFindPathInputs(paths: readonly string[]): void;
26
+ /**
27
+ * Group find matches by their directory so the model doesn't pay repeated
28
+ * tokens for shared path prefixes. Preserves the input order: groups appear in
29
+ * the order their first member was emitted (mtime-desc for native glob), and
30
+ * within a group entries keep their relative order.
31
+ */
32
+ export declare function formatFindGroupedOutput(paths: readonly string[]): string;
26
33
  export interface FindToolDetails {
27
34
  truncation?: TruncationResult;
28
35
  resultLimitReached?: number;
@@ -2,7 +2,8 @@ import { type Model } from "@oh-my-pi/pi-ai";
2
2
  import * as z from "zod/v4";
3
3
  import { type ModelRegistry } from "../config/model-registry";
4
4
  import type { CustomTool } from "../extensibility/custom-tools/types";
5
- type ImageProvider = "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter";
5
+ export type ImageProvider = "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter" | "xai";
6
+ export type ImageProviderPreference = Exclude<ImageProvider, "openai-codex"> | "auto";
6
7
  declare const responseModalitySchema: z.ZodEnum<{
7
8
  IMAGE: "IMAGE";
8
9
  TEXT: "TEXT";
@@ -19,6 +20,8 @@ export declare const imageGenSchema: z.ZodObject<{
19
20
  aspect_ratio: z.ZodOptional<z.ZodEnum<{
20
21
  "16:9": "16:9";
21
22
  "1:1": "1:1";
23
+ "2:3": "2:3";
24
+ "3:2": "3:2";
22
25
  "3:4": "3:4";
23
26
  "4:3": "4:3";
24
27
  "9:16": "9:16";
@@ -70,8 +73,9 @@ interface InlineImageData {
70
73
  data: string;
71
74
  mimeType: string;
72
75
  }
76
+ export declare function isImageProviderPreference(value: unknown): value is ImageProviderPreference;
73
77
  /** Set the preferred image provider from settings */
74
- export declare function setPreferredImageProvider(provider: ImageProvider | "auto"): void;
78
+ export declare function setPreferredImageProvider(provider: ImageProviderPreference): void;
75
79
  export declare const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails>;
76
80
  export declare function getImageGenTools(modelRegistry?: ModelRegistry, activeModel?: Model): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>>;
77
81
  export declare function getImageGenToolsWithRegistry(modelRegistry: ModelRegistry, activeModel?: Model): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>>;
@@ -54,6 +54,7 @@ export * from "./search";
54
54
  export * from "./search-tool-bm25";
55
55
  export * from "./ssh";
56
56
  export * from "./todo-write";
57
+ export * from "./tts";
57
58
  export * from "./write";
58
59
  export * from "./yield";
59
60
  /** Tool type (AgentTool from pi-ai) */
@@ -2,12 +2,11 @@ import type { ToolSession } from ".";
2
2
  /**
3
3
  * Resolve a write/edit target to its absolute filesystem path.
4
4
  *
5
- * In plan mode, transparently redirects targets whose basename matches the
6
- * plan file's basename (e.g. a bare `PLAN.md` or `./PLAN.md`) to the canonical
7
- * plan file location at `state.planFilePath`. This lets `write` and `edit`
8
- * accept the unqualified plan filename and have the change land at the
9
- * session-scoped `local://PLAN.md` artifact instead of a stray cwd-relative
10
- * file the plan-mode guard would otherwise reject.
5
+ * In plan mode, transparently redirects `PLAN.md` aliases and targets whose
6
+ * basename matches the plan file's basename to the canonical plan file
7
+ * location at `state.planFilePath`. This lets `write` and `edit` accept the
8
+ * habitual plan filename after approval even when the active artifact has a
9
+ * titled path such as `local://APPROVED.md`.
11
10
  *
12
11
  * Outside plan mode (or when the basename does not match) this is a no-op.
13
12
  */
@@ -0,0 +1,18 @@
1
+ import * as z from "zod/v4";
2
+ import type { CustomTool } from "../extensibility/custom-tools/types";
3
+ type TtsCodec = "mp3" | "wav";
4
+ declare const ttsSchema: z.ZodObject<{
5
+ text: z.ZodString;
6
+ voice_id: z.ZodDefault<z.ZodString>;
7
+ language: z.ZodDefault<z.ZodString>;
8
+ output_path: z.ZodString;
9
+ sample_rate: z.ZodOptional<z.ZodNumber>;
10
+ bit_rate: z.ZodOptional<z.ZodNumber>;
11
+ }, z.core.$strip>;
12
+ interface TtsToolDetails {
13
+ bytes: number;
14
+ voiceId: string;
15
+ codec: TtsCodec;
16
+ }
17
+ export declare const ttsTool: CustomTool<typeof ttsSchema, TtsToolDetails>;
18
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.5.4",
4
+ "version": "15.5.7",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,13 +47,13 @@
47
47
  "@agentclientprotocol/sdk": "0.21.0",
48
48
  "@babel/parser": "^7.29.3",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.5.4",
51
- "@oh-my-pi/omp-stats": "15.5.4",
52
- "@oh-my-pi/pi-agent-core": "15.5.4",
53
- "@oh-my-pi/pi-ai": "15.5.4",
54
- "@oh-my-pi/pi-natives": "15.5.4",
55
- "@oh-my-pi/pi-tui": "15.5.4",
56
- "@oh-my-pi/pi-utils": "15.5.4",
50
+ "@oh-my-pi/hashline": "15.5.7",
51
+ "@oh-my-pi/omp-stats": "15.5.7",
52
+ "@oh-my-pi/pi-agent-core": "15.5.7",
53
+ "@oh-my-pi/pi-ai": "15.5.7",
54
+ "@oh-my-pi/pi-natives": "15.5.7",
55
+ "@oh-my-pi/pi-tui": "15.5.7",
56
+ "@oh-my-pi/pi-utils": "15.5.7",
57
57
  "@puppeteer/browsers": "^2.13.0",
58
58
  "@types/turndown": "5.0.6",
59
59
  "@xterm/headless": "^6.0.0",
@@ -56,6 +56,17 @@ async function main(): Promise<void> {
56
56
  "../stats/src/sync-worker.ts",
57
57
  "./src/tools/browser/tab-worker-entry.ts",
58
58
  "./src/eval/js/worker-entry.ts",
59
+ // Legacy pi-* extension compat shims served by `legacy-pi-compat.ts`.
60
+ // Both are reached only via the computed `TYPEBOX_SHIM_PATH` /
61
+ // `LEGACY_PI_AI_SHIM_PATH` constants (which `--compile`'s static
62
+ // analyzer cannot trace), so each shim must be listed here to land
63
+ // in bunfs alongside the workers above. The bunfs entry path is
64
+ // `--root`-relative with a `.js` extension, e.g.
65
+ // `/$bunfs/root/packages/coding-agent/src/extensibility/typebox.js`,
66
+ // which is what the `isCompiledBinary()` branch in
67
+ // `legacy-pi-compat.ts` resolves to at runtime.
68
+ "./src/extensibility/typebox.ts",
69
+ "./src/extensibility/legacy-pi-ai-shim.ts",
59
70
  "--outfile",
60
71
  "dist/omp",
61
72
  ],
@@ -291,6 +291,12 @@ export function mergeDiscoveredModel<TApi extends Api>(
291
291
  return model;
292
292
  }
293
293
 
294
+ const AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS = new Set<string>(
295
+ PROVIDER_DESCRIPTORS.filter(descriptor => descriptor.dynamicModelsAuthoritative).map(
296
+ descriptor => descriptor.providerId,
297
+ ),
298
+ );
299
+
294
300
  function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
295
301
  return (
296
302
  model.provider === "google-vertex" &&
@@ -323,6 +329,11 @@ interface DiscoveryProviderConfig {
323
329
  optional?: boolean;
324
330
  }
325
331
 
332
+ interface BuiltInDiscoveryResult {
333
+ models: Model<Api>[];
334
+ authoritativeProviders: Set<string>;
335
+ }
336
+
326
337
  export type ProviderDiscoveryStatus = "idle" | "ok" | "empty" | "cached" | "unavailable" | "unauthenticated";
327
338
 
328
339
  export interface ProviderDiscoveryState {
@@ -914,6 +925,11 @@ export class ModelRegistry {
914
925
  cachedAuthoritativeProviders.add(provider);
915
926
  }
916
927
  }
928
+ for (const provider of cachedStandardResult.authoritativeFreshProviders) {
929
+ if (AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS.has(provider)) {
930
+ cachedAuthoritativeProviders.add(provider);
931
+ }
932
+ }
917
933
  if (cachedAuthoritativeProviders.size > 0) {
918
934
  builtInModels = dropProviderModels(builtInModels, cachedAuthoritativeProviders);
919
935
  }
@@ -1253,12 +1269,12 @@ export class ModelRegistry {
1253
1269
  : Promise.all(
1254
1270
  selectedDiscoverableProviders.map(provider => this.#discoverProviderModels(provider, strategy)),
1255
1271
  ).then(results => results.flat());
1256
- const [configuredDiscovered, builtInDiscovered] = await Promise.all([
1272
+ const [configuredDiscovered, builtInDiscovery] = await Promise.all([
1257
1273
  configuredDiscoveriesPromise,
1258
1274
  this.#discoverBuiltInProviderModels(strategy, providerFilter),
1259
1275
  ]);
1260
- const discovered = [...configuredDiscovered, ...builtInDiscovered];
1261
- if (discovered.length === 0) {
1276
+ const discovered = [...configuredDiscovered, ...builtInDiscovery.models];
1277
+ if (discovered.length === 0 && builtInDiscovery.authoritativeProviders.size === 0) {
1262
1278
  return;
1263
1279
  }
1264
1280
  const discoveredModels = this.#applyHardcodedModelPolicies(
@@ -1271,6 +1287,9 @@ export class ModelRegistry {
1271
1287
  ),
1272
1288
  );
1273
1289
  const authoritativeProviders = providersWithAuthoritativeProjectCatalog(discoveredModels);
1290
+ for (const provider of builtInDiscovery.authoritativeProviders) {
1291
+ authoritativeProviders.add(provider);
1292
+ }
1274
1293
  const baseModels =
1275
1294
  authoritativeProviders.size > 0 ? dropProviderModels(this.#models, authoritativeProviders) : this.#models;
1276
1295
  const resolved = this.#mergeResolvedModels(baseModels, discoveredModels);
@@ -1385,7 +1404,7 @@ export class ModelRegistry {
1385
1404
  async #discoverBuiltInProviderModels(
1386
1405
  strategy: ModelRefreshStrategy,
1387
1406
  providerFilter?: ReadonlySet<string>,
1388
- ): Promise<Model<Api>[]> {
1407
+ ): Promise<BuiltInDiscoveryResult> {
1389
1408
  // Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
1390
1409
  const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
1391
1410
  const managerOptions = (await this.#collectBuiltInModelManagerOptions()).filter(opts => {
@@ -1395,12 +1414,20 @@ export class ModelRegistry {
1395
1414
  return providerFilter ? providerFilter.has(opts.providerId) : true;
1396
1415
  });
1397
1416
  if (managerOptions.length === 0) {
1398
- return [];
1417
+ return { models: [], authoritativeProviders: new Set() };
1399
1418
  }
1400
1419
  const discoveries = await Promise.all(
1401
1420
  managerOptions.map(options => this.#discoverWithModelManager(options, strategy)),
1402
1421
  );
1403
- return discoveries.flat();
1422
+ const authoritativeProviders = new Set<string>();
1423
+ const models: Model<Api>[] = [];
1424
+ for (const discovery of discoveries) {
1425
+ models.push(...discovery.models);
1426
+ for (const provider of discovery.authoritativeProviders) {
1427
+ authoritativeProviders.add(provider);
1428
+ }
1429
+ }
1430
+ return { models, authoritativeProviders };
1404
1431
  }
1405
1432
 
1406
1433
  async #collectBuiltInModelManagerOptions(): Promise<ModelManagerOptions<Api>[]> {
@@ -1482,19 +1509,24 @@ export class ModelRegistry {
1482
1509
  async #discoverWithModelManager(
1483
1510
  options: ModelManagerOptions<Api>,
1484
1511
  strategy: ModelRefreshStrategy,
1485
- ): Promise<Model<Api>[]> {
1512
+ ): Promise<BuiltInDiscoveryResult> {
1486
1513
  try {
1487
1514
  const manager = createModelManager({ ...options, cacheDbPath: this.#cacheDbPath });
1488
1515
  const result = await manager.refresh(strategy);
1489
- return result.models.map(model =>
1516
+ const models = result.models.map(model =>
1490
1517
  model.provider === options.providerId ? model : { ...model, provider: options.providerId },
1491
1518
  );
1519
+ const authoritativeProviders = new Set<string>();
1520
+ if (options.dynamicModelsAuthoritative && !result.stale) {
1521
+ authoritativeProviders.add(options.providerId);
1522
+ }
1523
+ return { models, authoritativeProviders };
1492
1524
  } catch (error) {
1493
1525
  logger.warn("model discovery failed for provider", {
1494
1526
  provider: options.providerId,
1495
1527
  error: error instanceof Error ? error.message : String(error),
1496
1528
  });
1497
- return [];
1529
+ return { models: [], authoritativeProviders: new Set() };
1498
1530
  }
1499
1531
  }
1500
1532