@oh-my-pi/pi-ai 15.13.3 → 16.0.1

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 (85) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +3 -0
  3. package/dist/types/{grammar → dialect}/anthropic.d.ts +3 -3
  4. package/dist/types/{grammar → dialect}/catalog.d.ts +2 -2
  5. package/dist/types/{grammar → dialect}/deepseek.d.ts +3 -3
  6. package/dist/types/dialect/examples.d.ts +2 -0
  7. package/dist/types/dialect/factory.d.ts +3 -0
  8. package/dist/types/{grammar → dialect}/gemini.d.ts +4 -3
  9. package/dist/types/{grammar → dialect}/gemma.d.ts +4 -3
  10. package/dist/types/{grammar → dialect}/glm.d.ts +3 -3
  11. package/dist/types/{grammar → dialect}/harmony.d.ts +3 -3
  12. package/dist/types/{grammar → dialect}/hermes.d.ts +3 -3
  13. package/dist/types/dialect/history.d.ts +3 -0
  14. package/dist/types/{grammar → dialect}/inventory.d.ts +3 -3
  15. package/dist/types/{grammar → dialect}/kimi.d.ts +4 -3
  16. package/dist/types/dialect/owned-stream.d.ts +4 -0
  17. package/dist/types/{grammar → dialect}/pi.d.ts +3 -3
  18. package/dist/types/{grammar → dialect}/qwen3.d.ts +3 -3
  19. package/dist/types/dialect/rendering.d.ts +45 -0
  20. package/dist/types/{grammar → dialect}/types.d.ts +16 -14
  21. package/dist/types/{grammar → dialect}/xml.d.ts +3 -3
  22. package/dist/types/registry/registry.d.ts +4 -0
  23. package/dist/types/registry/umans.d.ts +7 -0
  24. package/dist/types/utils/stream-markup-healing.d.ts +6 -5
  25. package/package.json +6 -6
  26. package/src/{grammar → dialect}/anthropic.ts +75 -11
  27. package/src/{grammar → dialect}/catalog.ts +8 -6
  28. package/src/{grammar → dialect}/deepseek.ts +76 -16
  29. package/src/{grammar → dialect}/examples.ts +5 -5
  30. package/src/dialect/factory.ts +34 -0
  31. package/src/{grammar → dialect}/gemini.md +9 -1
  32. package/src/{grammar → dialect}/gemini.ts +174 -18
  33. package/src/{grammar → dialect}/gemma.md +9 -0
  34. package/src/{grammar → dialect}/gemma.ts +168 -18
  35. package/src/{grammar → dialect}/glm.ts +82 -10
  36. package/src/{grammar → dialect}/harmony.ts +84 -10
  37. package/src/{grammar → dialect}/hermes.ts +45 -10
  38. package/src/{grammar → dialect}/history.ts +12 -12
  39. package/src/dialect/inventory.ts +73 -0
  40. package/src/{grammar → dialect}/kimi.md +1 -1
  41. package/src/dialect/kimi.ts +340 -0
  42. package/src/{grammar → dialect}/owned-stream.ts +10 -10
  43. package/src/{grammar → dialect}/pi.md +9 -1
  44. package/src/{grammar → dialect}/pi.ts +145 -17
  45. package/src/{grammar → dialect}/prompt-template.md +1 -1
  46. package/src/{grammar → dialect}/qwen3.ts +47 -10
  47. package/src/dialect/rendering.ts +194 -0
  48. package/src/{grammar → dialect}/types.ts +16 -14
  49. package/src/dialect/xml.ts +90 -0
  50. package/src/providers/anthropic.ts +19 -4
  51. package/src/providers/azure-openai-responses.ts +5 -2
  52. package/src/providers/cursor.ts +4 -2
  53. package/src/providers/ollama.ts +5 -6
  54. package/src/providers/openai-codex-responses.ts +20 -5
  55. package/src/providers/openai-completions.ts +114 -35
  56. package/src/providers/openai-responses.ts +14 -5
  57. package/src/registry/registry.ts +2 -0
  58. package/src/registry/umans.ts +23 -0
  59. package/src/utils/schema/normalize.ts +40 -3
  60. package/src/utils/schema/wire.ts +18 -3
  61. package/src/utils/stream-markup-healing.ts +19 -12
  62. package/src/utils/validation.ts +159 -0
  63. package/dist/types/grammar/examples.d.ts +0 -2
  64. package/dist/types/grammar/factory.d.ts +0 -3
  65. package/dist/types/grammar/history.d.ts +0 -3
  66. package/dist/types/grammar/owned-stream.d.ts +0 -4
  67. package/dist/types/grammar/rendering.d.ts +0 -30
  68. package/src/grammar/factory.ts +0 -34
  69. package/src/grammar/inventory.ts +0 -28
  70. package/src/grammar/kimi.ts +0 -198
  71. package/src/grammar/rendering.ts +0 -313
  72. package/src/grammar/xml.ts +0 -33
  73. /package/dist/types/{grammar → dialect}/coercion.d.ts +0 -0
  74. /package/dist/types/{grammar → dialect}/index.d.ts +0 -0
  75. /package/dist/types/{grammar → dialect}/thinking.d.ts +0 -0
  76. /package/src/{grammar → dialect}/anthropic.md +0 -0
  77. /package/src/{grammar → dialect}/coercion.ts +0 -0
  78. /package/src/{grammar → dialect}/deepseek.md +0 -0
  79. /package/src/{grammar → dialect}/glm.md +0 -0
  80. /package/src/{grammar → dialect}/harmony.md +0 -0
  81. /package/src/{grammar → dialect}/hermes.md +0 -0
  82. /package/src/{grammar → dialect}/index.ts +0 -0
  83. /package/src/{grammar → dialect}/qwen3.md +0 -0
  84. /package/src/{grammar → dialect}/thinking.ts +0 -0
  85. /package/src/{grammar → dialect}/xml.md +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,63 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.0.1] - 2026-06-15
6
+
7
+ ### Added
8
+
9
+ - Added Umans AI Coding Plan API-key login support and `UMANS_AI_CODING_PLAN_API_KEY` environment fallback ([#2636](https://github.com/can1357/oh-my-pi/pull/2636) by [@oldschoola](https://github.com/oldschoola)).
10
+
11
+ ### Fixed
12
+
13
+ - Fixed OpenAI Responses, Azure OpenAI Responses, and Codex Responses providers ignoring async `onPayload` replacement bodies. Provider payload hooks can now transform the actual request body sent upstream, matching the Anthropic/Gemini replacement contract.
14
+ - Fixed OpenAI-compatible chat-completions streams that send object-shaped tool arguments in fragments by deep-merging nested objects and task arrays instead of replacing earlier chunks. ([#2617](https://github.com/can1357/oh-my-pi/issues/2617))
15
+ - Fixed OpenAI Responses strict-mode tool schema normalization for nullable enum MCP parameters so enum constraints are distributed to matching `anyOf` branches instead of being copied onto the `null` branch. ([#1835](https://github.com/can1357/oh-my-pi/issues/1835))
16
+ - Fixed Cursor provider formatting tool errors with the same `[Tool Result]` prefix as successful results, causing Composer models to misinterpret error messages (e.g. "Pattern must not be empty") as directives over long conversations. Errors now use a `[Tool Error]` prefix so the model can distinguish failures from successes in the prompt history. ([#1853](https://github.com/can1357/oh-my-pi/pull/1853))
17
+ - Fixed `validateToolArguments` silently accepting JSON-encoded array strings (e.g. `'["a","b"]'`) against `union(string, array<string>)` schemas — providers that double-serialize tool-call arguments (Z.AI / GLM) caused tools like `search` to receive the literal `["a","b"]` as a single path, producing zero matches (single element) or glob parse errors (multi-element). A new pre-validation pass parses JSON-array-shaped strings when the schema explicitly accepts both shapes. ([#1788](https://github.com/can1357/oh-my-pi/issues/1788))
18
+ - Fixed Anthropic thinking summaries that arrive wrapped in literal `<thinking>` tags so advisor/raw transcript dumps do not render nested thinking tags ([#2695](https://github.com/can1357/oh-my-pi/issues/2695)).
19
+
20
+ ## [16.0.0] - 2026-06-15
21
+
22
+ ### Breaking Changes
23
+
24
+ - Renamed the public dialect entrypoint from `@oh-my-pi/pi-ai/grammar` to `@oh-my-pi/pi-ai/dialect`.
25
+ - Renamed grammar dialect identifiers from `ToolCallSyntax` to `Dialect`, renamed the `Grammar` interface to `DialectDefinition`, and renamed `Grammar.syntax` to `DialectDefinition.dialect`.
26
+ - Added `DialectDefinition.renderThinking` and `DialectDefinition.renderTranscript` so dialect implementations serialize complete native chat transcripts, not just tool call/result blocks.
27
+
28
+ ### Added
29
+
30
+ - Added `renderTranscript` method to dialect definitions for serializing complete native chat transcripts
31
+ - Added `renderThinking` method to dialect definitions for rendering thinking/reasoning blocks
32
+ - Added support for 11 dialect implementations: Anthropic, DeepSeek, Gemini, Gemma, GLM, Harmony, Hermes, Kimi, Pi-native, Qwen3, and XML
33
+ - Added `createInbandScanner` factory function to instantiate dialect-specific scanners
34
+ - Added `getDialectDefinition` function to retrieve dialect implementations by name
35
+ - Added `renderToolCatalog` and `renderInbandToolPrompt` functions for tool catalog rendering
36
+ - Added `renderToolInventory` function to generate human-readable per-tool documentation with examples
37
+ - Added `renderToolExamples` function to render tool usage examples in the model's native dialect
38
+ - Added `encodeInbandToolHistory` function to encode tool call history in dialect-specific format
39
+ - Added `wrapInbandToolStream` function to process streaming responses with in-band tool call parsing
40
+ - Added `ThinkingInbandScanner` for parsing thinking/reasoning blocks across dialects
41
+ - Added `OwnedStream` class for managing dialect-aware streaming with tool call events
42
+ - Added in-band thinking channels to every dialect that was missing one: `gemini` (a ```` ```thinking ```` fence mirroring ```` ```tool_code ````), `gemma` (its native `<|channel>thought…<channel|>` reasoning channel), `kimi` (`<think>…</think>`), and `pi` (`<thinking>…</thinking>`). Each scanner now parses reasoning into thinking events instead of leaking chain-of-thought into the visible reply, and every dialect's `renderThinking` is a real channel that round-trips back through its scanner (no passthrough renderers).
43
+
44
+ ### Changed
45
+
46
+ - Moved public dialect entrypoint from `@oh-my-pi/pi-ai/grammar` to `@oh-my-pi/pi-ai/dialect` in package exports
47
+ - Updated internal imports in `stream-markup-healing.ts` to use new dialect module path
48
+ - Changed `renderToolInventory` to demote a tool description's own markdown headers by one level when it contains a top-level `# ` header, so they nest under the wrapping `# Tool: <name>` heading instead of reading as sibling sections. Descriptions that already start at `##` and headers inside fenced code blocks are left untouched.
49
+
50
+ ### Fixed
51
+
52
+ - Fixed Gemini, Gemma, Kimi, and Pi in-band scanners to respect `parseThinking: false`, leaving private reasoning markers in visible text when parsing is disabled
53
+ - Fixed thinking-channel parsing for streaming Gemini, Gemma, Kimi, and Pi outputs so split or partial `<thinking>` blocks no longer leak into visible replies
54
+ - Fixed in-band thinking finalization and Kimi stream-healing interactions so leaked `<think>` blocks are preserved when structured tool calls are present, not duplicated when explicit reasoning is present, and closed on stream flush.
55
+
56
+ ### Removed
57
+
58
+ - Removed `src/grammar/factory.ts` (replaced by `src/dialect/factory.ts`)
59
+ - Removed `src/grammar/rendering.ts` (functionality moved to `src/dialect/rendering.ts`)
60
+ - Removed `src/grammar/xml.ts` (replaced by `src/dialect/xml.ts`)
61
+
5
62
  ## [15.13.3] - 2026-06-15
6
63
 
7
64
  ### Added
package/README.md CHANGED
@@ -68,6 +68,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
68
68
  - **Kilo Gateway** (supports OAuth `/login kilo` or `KILO_API_KEY`)
69
69
  - **LiteLLM** (requires `LITELLM_API_KEY`)
70
70
  - **zAI** (requires `ZAI_API_KEY`)
71
+ - **Umans AI Coding Plan** (supports `/login umans` or `UMANS_AI_CODING_PLAN_API_KEY`)
71
72
  - **MiniMax Token Plan** (requires `MINIMAX_CODE_API_KEY` or `MINIMAX_CODE_CN_API_KEY`)
72
73
  - **Xiaomi MiMo** (requires `XIAOMI_API_KEY`)
73
74
  - **ZenMux** (requires `ZENMUX_API_KEY`)
@@ -952,6 +953,7 @@ In Node.js environments, you can set environment variables to avoid passing API
952
953
  | Ollama Cloud | `OLLAMA_CLOUD_API_KEY` |
953
954
  | Qwen Portal | `QWEN_OAUTH_TOKEN` or `QWEN_PORTAL_API_KEY` |
954
955
  | zAI | `ZAI_API_KEY` |
956
+ | Umans AI Coding Plan | `UMANS_AI_CODING_PLAN_API_KEY` |
955
957
  | MiniMax Code | `MINIMAX_CODE_API_KEY` (international) or `MINIMAX_CODE_CN_API_KEY` (China) |
956
958
  | Xiaomi MiMo | `XIAOMI_API_KEY` |
957
959
  | ZenMux | `ZENMUX_API_KEY` |
@@ -978,6 +980,7 @@ Provider endpoint defaults for the current OpenAI-compatible integrations:
978
980
  - Xiaomi MiMo: `https://api.xiaomimimo.com/anthropic`
979
981
  - ZenMux (OpenAI): `https://zenmux.ai/api/v1`
980
982
  - ZenMux (Anthropic models): `https://zenmux.ai/api/anthropic`
983
+ - Umans AI Coding Plan: `https://api.code.umans.ai`
981
984
  - vLLM: `http://127.0.0.1:8000/v1`
982
985
  - Ollama: local OpenAI-compatible runtime (`http://127.0.0.1:11434/v1`)
983
986
  - Ollama Cloud: native Ollama API host (`https://ollama.com/api`, configured here as base URL `https://ollama.com`)
@@ -1,9 +1,9 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare class AnthropicInbandScanner implements InbandScanner {
3
3
  #private;
4
4
  constructor(options?: InbandScannerOptions);
5
5
  feed(text: string): InbandScanEvent[];
6
6
  flush(): InbandScanEvent[];
7
7
  }
8
- declare const grammar: Grammar;
9
- export default grammar;
8
+ declare const definition: DialectDefinition;
9
+ export default definition;
@@ -1,3 +1,3 @@
1
- import type { InbandTool, ToolCallSyntax } from "./types";
1
+ import type { Dialect, InbandTool } from "./types";
2
2
  export declare function renderToolCatalog(tools: readonly InbandTool[]): string;
3
- export declare function renderInbandToolPrompt(tools: readonly InbandTool[], syntax: ToolCallSyntax): string;
3
+ export declare function renderInbandToolPrompt(tools: readonly InbandTool[], dialect: Dialect): string;
@@ -1,4 +1,4 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare const DEEPSEEK_TOOL_CALLS_BEGIN = "<\uFF5Ctool\u2581calls\u2581begin\uFF5C>";
3
3
  export declare const DEEPSEEK_TOOL_CALLS_END = "<\uFF5Ctool\u2581calls\u2581end\uFF5C>";
4
4
  export declare const DEEPSEEK_TOOL_CALL_BEGIN = "<\uFF5Ctool\u2581call\u2581begin\uFF5C>";
@@ -10,5 +10,5 @@ export declare class DeepSeekInbandScanner implements InbandScanner {
10
10
  feed(text: string): InbandScanEvent[];
11
11
  flush(): InbandScanEvent[];
12
12
  }
13
- declare const grammar: Grammar;
14
- export default grammar;
13
+ declare const definition: DialectDefinition;
14
+ export default definition;
@@ -0,0 +1,2 @@
1
+ import type { Dialect, InbandTool } from "./types";
2
+ export declare function renderToolExamples(tool: InbandTool, dialect: Dialect, intentField?: string): string;
@@ -0,0 +1,3 @@
1
+ import type { Dialect, DialectDefinition, InbandScanner, InbandScannerOptions } from "./types";
2
+ export declare function getDialectDefinition(dialect: Dialect): DialectDefinition;
3
+ export declare function createInbandScanner(dialect: Dialect, options?: InbandScannerOptions): InbandScanner;
@@ -1,4 +1,4 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  /**
3
3
  * Scanner for the hosted-Gemini / Gemma 3 Pythonic tool-calling convention
4
4
  * (see `docs/toolconv/gemini.md`). Tool calls arrive as a ```` ```tool_code ````
@@ -9,8 +9,9 @@ import type { Grammar, InbandScanEvent, InbandScanner } from "./types";
9
9
  */
10
10
  export declare class GeminiInbandScanner implements InbandScanner {
11
11
  #private;
12
+ constructor(options?: InbandScannerOptions);
12
13
  feed(text: string): InbandScanEvent[];
13
14
  flush(): InbandScanEvent[];
14
15
  }
15
- declare const grammar: Grammar;
16
- export default grammar;
16
+ declare const definition: DialectDefinition;
17
+ export default definition;
@@ -1,4 +1,4 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  /**
3
3
  * Scanner for the Gemma 4 token-delimited tool-calling convention (see
4
4
  * `docs/toolconv/gemma.md`). Each call is one `<|tool_call>call:NAME{…}<tool_call|>`
@@ -7,8 +7,9 @@ import type { Grammar, InbandScanEvent, InbandScanner } from "./types";
7
7
  */
8
8
  export declare class GemmaInbandScanner implements InbandScanner {
9
9
  #private;
10
+ constructor(options?: InbandScannerOptions);
10
11
  feed(text: string): InbandScanEvent[];
11
12
  flush(): InbandScanEvent[];
12
13
  }
13
- declare const grammar: Grammar;
14
- export default grammar;
14
+ declare const definition: DialectDefinition;
15
+ export default definition;
@@ -1,9 +1,9 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare class GLMInbandScanner implements InbandScanner {
3
3
  #private;
4
4
  constructor(options?: InbandScannerOptions);
5
5
  feed(text: string): InbandScanEvent[];
6
6
  flush(): InbandScanEvent[];
7
7
  }
8
- declare const grammar: Grammar;
9
- export default grammar;
8
+ declare const definition: DialectDefinition;
9
+ export default definition;
@@ -1,8 +1,8 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner } from "./types";
2
2
  export declare class HarmonyInbandScanner implements InbandScanner {
3
3
  #private;
4
4
  feed(text: string): InbandScanEvent[];
5
5
  flush(): InbandScanEvent[];
6
6
  }
7
- declare const grammar: Grammar;
8
- export default grammar;
7
+ declare const definition: DialectDefinition;
8
+ export default definition;
@@ -1,9 +1,9 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare class HermesInbandScanner implements InbandScanner {
3
3
  #private;
4
4
  constructor(options?: InbandScannerOptions);
5
5
  feed(text: string): InbandScanEvent[];
6
6
  flush(): InbandScanEvent[];
7
7
  }
8
- declare const grammar: Grammar;
9
- export default grammar;
8
+ declare const definition: DialectDefinition;
9
+ export default definition;
@@ -0,0 +1,3 @@
1
+ import type { Context } from "../types";
2
+ import type { Dialect, InbandTool } from "./types";
3
+ export declare function encodeInbandToolHistory(messages: Context["messages"], dialect: Dialect, tools?: readonly InbandTool[]): Context["messages"];
@@ -3,10 +3,10 @@ import type { InbandTool } from "./types";
3
3
  * Human-readable per-tool inventory: each tool renders as a `# Tool: <name>`
4
4
  * section with its description, a simplified TypeScript-style parameter
5
5
  * signature (derived from the wire JSON Schema), and examples in the model's
6
- * native tool-call syntax. Shared by the verbose system-prompt inventory and
6
+ * native dialect. Shared by the verbose system-prompt inventory and
7
7
  * `/dump` so both render the catalog the same way.
8
8
  *
9
- * `model` is a model id; the native example syntax is resolved from it
10
- * (`preferredToolSyntax`, which falls back to XML for empty/unknown ids).
9
+ * `model` is a model id; the native example dialect is resolved from it
10
+ * (`preferredDialect`, which falls back to XML for empty/unknown ids).
11
11
  */
12
12
  export declare function renderToolInventory(tools: readonly InbandTool[], model: string): string;
@@ -1,4 +1,4 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare const KIMI_SECTION_BEGIN = "<|tool_calls_section_begin|>";
3
3
  export declare const KIMI_SECTION_END = "<|tool_calls_section_end|>";
4
4
  export declare const KIMI_CALL_BEGIN = "<|tool_call_begin|>";
@@ -6,8 +6,9 @@ export declare const KIMI_CALL_END = "<|tool_call_end|>";
6
6
  export declare const KIMI_ARG_BEGIN = "<|tool_call_argument_begin|>";
7
7
  export declare class KimiInbandScanner implements InbandScanner {
8
8
  #private;
9
+ constructor(options?: InbandScannerOptions);
9
10
  feed(text: string): InbandScanEvent[];
10
11
  flush(): InbandScanEvent[];
11
12
  }
12
- declare const grammar: Grammar;
13
- export default grammar;
13
+ declare const definition: DialectDefinition;
14
+ export default definition;
@@ -0,0 +1,4 @@
1
+ import type { AssistantMessage, AssistantMessageEventStream as AssistantMessageEventStreamType } from "../types";
2
+ import type { Dialect, InbandTool } from "./types";
3
+ export declare function parseInbandToolMessage(message: AssistantMessage, dialect: Dialect, tools: readonly InbandTool[]): AssistantMessage;
4
+ export declare function wrapInbandToolStream(inner: AssistantMessageEventStreamType, tools: readonly InbandTool[], dialect: Dialect, onAbort?: () => void, abortOnFabrication?: boolean): AssistantMessageEventStreamType;
@@ -1,9 +1,9 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare class PiNativeInbandScanner implements InbandScanner {
3
3
  #private;
4
4
  constructor(options?: InbandScannerOptions);
5
5
  feed(text: string): InbandScanEvent[];
6
6
  flush(): InbandScanEvent[];
7
7
  }
8
- declare const grammar: Grammar;
9
- export default grammar;
8
+ declare const definition: DialectDefinition;
9
+ export default definition;
@@ -1,9 +1,9 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare class Qwen3InbandScanner implements InbandScanner {
3
3
  #private;
4
4
  constructor(options?: InbandScannerOptions);
5
5
  feed(text: string): InbandScanEvent[];
6
6
  flush(): InbandScanEvent[];
7
7
  }
8
- declare const grammar: Grammar;
9
- export default grammar;
8
+ declare const definition: DialectDefinition;
9
+ export default definition;
@@ -0,0 +1,45 @@
1
+ import type { AssistantMessage, Message, ToolCall } from "../types";
2
+ import type { DialectRenderOptions, DialectToolResult } from "./types";
3
+ export declare function renderToolResponseResults(results: readonly DialectToolResult[]): string;
4
+ export declare function kimiCallId(name: string, id: string, index: number): string;
5
+ export declare function harmonyRecipient(name: string): string;
6
+ export declare function stringifyJson(value: unknown): string;
7
+ export declare function escapeXmlAttr(value: string): string;
8
+ export declare function escapeXmlText(value: string): string;
9
+ export type AssistantTranscriptParts = {
10
+ readonly text: string;
11
+ readonly thinking: string;
12
+ readonly toolCalls: readonly ToolCall[];
13
+ };
14
+ export type ToolCallRenderer = (calls: readonly ToolCall[], options?: DialectRenderOptions) => string;
15
+ export type ToolResultRenderer = (results: readonly DialectToolResult[], options?: DialectRenderOptions) => string;
16
+ export type ChatMlTranscriptConfig = {
17
+ readonly bos?: string;
18
+ readonly toolResultRole: "tool" | "user";
19
+ readonly renderThinking: (text: string) => string;
20
+ readonly renderCalls: ToolCallRenderer;
21
+ readonly renderResultsBody: ToolResultRenderer;
22
+ };
23
+ export type LegacyTextTranscriptConfig = {
24
+ readonly renderThinking: (text: string) => string;
25
+ readonly renderCalls: ToolCallRenderer;
26
+ readonly renderResults: ToolResultRenderer;
27
+ };
28
+ export declare function renderChatMlTranscript(messages: readonly Message[], options: DialectRenderOptions, config: ChatMlTranscriptConfig): string;
29
+ export declare function renderLegacyTextTranscript(messages: readonly Message[], options: DialectRenderOptions, config: LegacyTextTranscriptConfig): string;
30
+ export declare function assistantTranscriptParts(message: AssistantMessage): AssistantTranscriptParts;
31
+ export declare function collectToolResultRun(messages: readonly Message[], start: number): {
32
+ readonly results: readonly DialectToolResult[];
33
+ readonly next: number;
34
+ };
35
+ export declare function messageContentText(content: string | readonly {
36
+ readonly type: string;
37
+ readonly text?: string;
38
+ readonly mimeType?: string;
39
+ }[]): string;
40
+ export declare function renderDelimitedThinking(open: string, close: string, text: string): string;
41
+ export declare function chatMlTurn(role: "assistant" | "system" | "tool" | "user", body: string): string;
42
+ export declare function kimiTurn(role: "assistant" | "system" | "user", name: string, body: string): string;
43
+ export declare function gemmaTurn(role: "model" | "system" | "user", body: string): string;
44
+ export declare function geminiTurn(role: "model" | "user", body: string): string;
45
+ export declare function joinUserBodies(left: string, right: string): string;
@@ -1,6 +1,6 @@
1
- import type { ToolCallSyntax } from "@oh-my-pi/pi-catalog/identity";
2
- import type { Context, ToolCall } from "../types";
3
- export type { ToolCallSyntax };
1
+ import type { Dialect as CatalogDialect } from "@oh-my-pi/pi-catalog/identity";
2
+ import type { Context, Message, ToolCall } from "../types";
3
+ export type { Dialect } from "@oh-my-pi/pi-catalog/identity";
4
4
  export type InbandScanEvent = {
5
5
  type: "text";
6
6
  text: string;
@@ -33,35 +33,37 @@ export interface InbandScanner {
33
33
  feed(text: string): InbandScanEvent[];
34
34
  flush(): InbandScanEvent[];
35
35
  }
36
- export interface GrammarToolResult {
36
+ export interface DialectToolResult {
37
37
  readonly id: string;
38
38
  readonly name: string;
39
39
  readonly index: number;
40
40
  readonly text: string;
41
41
  readonly isError: boolean;
42
42
  }
43
- export interface GrammarRenderOptions {
43
+ export interface DialectRenderOptions {
44
44
  readonly tools?: readonly InbandTool[];
45
45
  readonly example?: boolean;
46
46
  }
47
- export interface Grammar {
48
- readonly syntax: ToolCallSyntax;
47
+ export interface DialectDefinition {
48
+ readonly dialect: CatalogDialect;
49
49
  readonly prompt: string;
50
50
  createScanner(options?: InbandScannerOptions): InbandScanner;
51
51
  /** Render a single tool-call invocation — the inner element only, WITHOUT any parallel-call block envelope (e.g. anthropic's `<function_calls>` / kimi's section wrapper). */
52
- renderToolCall(call: ToolCall, options?: GrammarRenderOptions): string;
53
- /** Render a batch of (parallel) tool calls as one complete block, including whatever envelope the syntax wraps multiple calls in. */
54
- renderAssistantToolCalls(calls: readonly ToolCall[], options?: GrammarRenderOptions): string;
55
- renderToolResults(results: readonly GrammarToolResult[], options?: GrammarRenderOptions): string;
52
+ renderToolCall(call: ToolCall, options?: DialectRenderOptions): string;
53
+ /** Render a batch of (parallel) tool calls as one complete block, including whatever envelope the dialect wraps multiple calls in. */
54
+ renderAssistantToolCalls(calls: readonly ToolCall[], options?: DialectRenderOptions): string;
55
+ renderToolResults(results: readonly DialectToolResult[], options?: DialectRenderOptions): string;
56
+ renderThinking(text: string): string;
57
+ renderTranscript(messages: readonly Message[], options?: DialectRenderOptions): string;
56
58
  }
57
59
  export interface InbandScannerOptions {
58
- /** string-typed arg names for a tool → read verbatim. Ignored by JSON-carrying syntaxes. */
60
+ /** string-typed arg names for a tool → read verbatim. Ignored by JSON-carrying dialects. */
59
61
  stringArgs?: (toolName: string) => ReadonlySet<string>;
60
- /** Full tool schemas for schema-driven syntaxes such as GLM XML and pi-native. */
62
+ /** Full tool schemas for schema-driven dialects such as GLM XML and pi-native. */
61
63
  tools?: readonly InbandTool[];
62
64
  /** XML only: parse pipe-wrapped DeepSeek DSML tags vs plain Anthropic invoke/parameter tags. */
63
65
  xmlTagset?: "anthropic" | "dsml";
64
- /** Emit thinking markers as thinking events instead of visible text when the syntax defines them. */
66
+ /** Emit thinking markers as thinking events instead of visible text when the dialect defines them. */
65
67
  parseThinking?: boolean;
66
68
  }
67
69
  export type InbandTool = NonNullable<Context["tools"]>[number];
@@ -1,9 +1,9 @@
1
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
1
+ import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
2
2
  export declare class XmlInbandScanner implements InbandScanner {
3
3
  #private;
4
4
  constructor(options?: InbandScannerOptions);
5
5
  feed(text: string): InbandScanEvent[];
6
6
  flush(): InbandScanEvent[];
7
7
  }
8
- declare const grammar: Grammar;
9
- export default grammar;
8
+ declare const definition: DialectDefinition;
9
+ export default definition;
@@ -208,6 +208,10 @@ declare const ALL: ({
208
208
  readonly id: "together";
209
209
  readonly name: "Together";
210
210
  readonly login: (cb: Parameters<typeof import("./together").loginTogether>[0]) => Promise<string>;
211
+ } | {
212
+ readonly id: "umans";
213
+ readonly name: "Umans AI Coding Plan";
214
+ readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
211
215
  } | {
212
216
  readonly id: "venice";
213
217
  readonly name: "Venice";
@@ -0,0 +1,7 @@
1
+ import type { OAuthLoginCallbacks } from "./oauth/types";
2
+ export declare const loginUmans: (options: import("./oauth").OAuthController) => Promise<string>;
3
+ export declare const umansProvider: {
4
+ readonly id: "umans";
5
+ readonly name: "Umans AI Coding Plan";
6
+ readonly login: (cb: OAuthLoginCallbacks) => Promise<string>;
7
+ };
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Hosted models sometimes leak raw template markup into visible `content` instead
5
5
  * of returning structured events. Tool-call healing delegates to the same
6
- * grammar scanners used by owned in-band tool calling; this file keeps the
6
+ * dialect scanners used by owned in-band tool calling; this file keeps the
7
7
  * provider-facing compatibility wrapper and model/provider gating.
8
8
  */
9
9
  export interface HealedToolCall {
@@ -47,11 +47,12 @@ export declare class StreamMarkupHealing {
47
47
  /** Feed a chunk and return cleaned text/thinking/tool-call events in stream order. */
48
48
  feedEvents(text: string): StreamMarkupHealingEvent[];
49
49
  /**
50
- * Like {@link feed}, but discards completed calls. Used when the upstream
51
- * chunk also carries structured `tool_calls`, keeping that structured payload
52
- * as the single source of truth.
50
+ * Feed a chunk and return cleaned events, excluding synthesized tool calls.
51
+ * Used when the upstream chunk also carries structured `tool_calls`, keeping
52
+ * that structured payload as the single source of truth while preserving
53
+ * adjacent text and thinking events.
53
54
  */
54
- consumeWithoutCalls(text: string): string;
55
+ feedEventsWithoutCalls(text: string): StreamMarkupHealingEvent[];
55
56
  /** Drain accumulated tool calls from calls to {@link feed}. */
56
57
  drainCompleted(): HealedToolCall[];
57
58
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "15.13.3",
4
+ "version": "16.0.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,8 +38,8 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "@oh-my-pi/pi-catalog": "15.13.3",
42
- "@oh-my-pi/pi-utils": "15.13.3",
41
+ "@oh-my-pi/pi-catalog": "16.0.1",
42
+ "@oh-my-pi/pi-utils": "16.0.1",
43
43
  "partial-json": "^0.1.7",
44
44
  "zod": "^4"
45
45
  },
@@ -96,9 +96,9 @@
96
96
  "types": "./dist/types/utils/harmony-leak.d.ts",
97
97
  "import": "./src/utils/harmony-leak.ts"
98
98
  },
99
- "./grammar": {
100
- "types": "./dist/types/grammar/index.d.ts",
101
- "import": "./src/grammar/index.ts"
99
+ "./dialect": {
100
+ "types": "./dist/types/dialect/index.d.ts",
101
+ "import": "./src/dialect/index.ts"
102
102
  },
103
103
  "./utils/*": {
104
104
  "types": "./dist/types/utils/*.d.ts",
@@ -1,8 +1,22 @@
1
+ import type { Message, ToolCall } from "../types";
1
2
  import { parseJsonWithRepair } from "../utils/json-parse";
2
- import grammarPrompt from "./anthropic.md" with { type: "text" };
3
- import { buildStringArgsResolver, mintToolCallId } from "./coercion";
4
- import { renderAnthropicInvocation, renderAnthropicToolCalls, renderAnthropicToolResults } from "./rendering";
5
- import type { Grammar, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
3
+ import dialectPrompt from "./anthropic.md" with { type: "text" };
4
+ import { buildArgShapes, buildStringArgsResolver, mintToolCallId, type ToolArgShape } from "./coercion";
5
+ import {
6
+ escapeXmlAttr,
7
+ escapeXmlText,
8
+ renderDelimitedThinking,
9
+ renderLegacyTextTranscript,
10
+ stringifyJson,
11
+ } from "./rendering";
12
+ import type {
13
+ DialectDefinition,
14
+ DialectRenderOptions,
15
+ DialectToolResult,
16
+ InbandScanEvent,
17
+ InbandScanner,
18
+ InbandScannerOptions,
19
+ } from "./types";
6
20
 
7
21
  const MAX_PARTIAL_TAG_LENGTH = 256;
8
22
  const MAX_PARAMETER_VALUE_LENGTH = 1_000_000;
@@ -509,13 +523,63 @@ function couldBeTagPrefix(buffer: string, prefixes: readonly string[]): boolean
509
523
  return false;
510
524
  }
511
525
 
512
- const grammar: Grammar = {
513
- syntax: "anthropic",
514
- prompt: grammarPrompt,
526
+ function renderToolCall(call: ToolCall, options: DialectRenderOptions = {}): string {
527
+ return renderInvoke(call, buildArgShapes(options.tools).get(call.name));
528
+ }
529
+
530
+ function renderAssistantToolCalls(calls: readonly ToolCall[], options: DialectRenderOptions = {}): string {
531
+ if (calls.length === 0) return "";
532
+ return `<function_calls>\n${renderInvokes(calls, options.tools ?? [])}\n</function_calls>`;
533
+ }
534
+
535
+ function renderToolResults(results: readonly DialectToolResult[]): string {
536
+ const body = results
537
+ .map(result => {
538
+ const tag = result.isError ? "error" : "result";
539
+ const streamTag = result.isError ? "stderr" : "stdout";
540
+ return `<${tag}>\n<tool_name>${escapeXmlText(result.name)}</tool_name>\n<${streamTag}>${result.text}</${streamTag}>\n</${tag}>`;
541
+ })
542
+ .join("\n");
543
+ return `<function_results>\n${body}\n</function_results>`;
544
+ }
545
+
546
+ function renderThinking(text: string): string {
547
+ return renderDelimitedThinking("<thinking>", "</thinking>", text);
548
+ }
549
+
550
+ function renderTranscript(messages: readonly Message[], options: DialectRenderOptions = {}): string {
551
+ return renderLegacyTextTranscript(messages, options, {
552
+ renderThinking,
553
+ renderCalls: renderAssistantToolCalls,
554
+ renderResults: renderToolResults,
555
+ });
556
+ }
557
+
558
+ function renderInvoke(call: ToolCall, shape: ToolArgShape | undefined): string {
559
+ let body = `<invoke name="${escapeXmlAttr(call.name)}">`;
560
+ for (const key in call.arguments) {
561
+ const value = call.arguments[key];
562
+ const isString = shape?.stringArgs.has(key) === true;
563
+ const rendered = isString && typeof value === "string" ? value : stringifyJson(value);
564
+ body += `<parameter name="${escapeXmlAttr(key)}">${rendered}</parameter>`;
565
+ }
566
+ return `${body}</invoke>`;
567
+ }
568
+
569
+ function renderInvokes(calls: readonly ToolCall[], tools: NonNullable<DialectRenderOptions["tools"]>): string {
570
+ const shapes = buildArgShapes(tools);
571
+ return calls.map(call => renderInvoke(call, shapes.get(call.name))).join("\n");
572
+ }
573
+
574
+ const definition: DialectDefinition = {
575
+ dialect: "anthropic",
576
+ prompt: dialectPrompt,
515
577
  createScanner: options => new AnthropicInbandScanner(options),
516
- renderToolCall: renderAnthropicInvocation,
517
- renderAssistantToolCalls: renderAnthropicToolCalls,
518
- renderToolResults: renderAnthropicToolResults,
578
+ renderToolCall,
579
+ renderAssistantToolCalls,
580
+ renderToolResults,
581
+ renderThinking,
582
+ renderTranscript,
519
583
  };
520
584
 
521
- export default grammar;
585
+ export default definition;
@@ -1,10 +1,10 @@
1
1
  import { toolWireSchema } from "../utils/schema";
2
- import { getInbandGrammar } from "./factory";
2
+ import { getDialectDefinition } from "./factory";
3
3
  import promptTemplate from "./prompt-template.md" with { type: "text" };
4
- import type { InbandTool, ToolCallSyntax } from "./types";
4
+ import type { Dialect, InbandTool } from "./types";
5
5
 
6
6
  const TOOLS_TOKEN = "{{TOOLS}}";
7
- const GRAMMAR_TOKEN = "{{GRAMMAR}}";
7
+ const DIALECT_PROMPT_TOKEN = "{{DIALECT}}";
8
8
 
9
9
  export function renderToolCatalog(tools: readonly InbandTool[]): string {
10
10
  return tools
@@ -21,7 +21,9 @@ export function renderToolCatalog(tools: readonly InbandTool[]): string {
21
21
  .join("\n");
22
22
  }
23
23
 
24
- export function renderInbandToolPrompt(tools: readonly InbandTool[], syntax: ToolCallSyntax): string {
25
- const prompt = getInbandGrammar(syntax).prompt.trim();
26
- return promptTemplate.replace(TOOLS_TOKEN, () => renderToolCatalog(tools)).replace(GRAMMAR_TOKEN, () => prompt);
24
+ export function renderInbandToolPrompt(tools: readonly InbandTool[], dialect: Dialect): string {
25
+ const prompt = getDialectDefinition(dialect).prompt.trim();
26
+ return promptTemplate
27
+ .replace(TOOLS_TOKEN, () => renderToolCatalog(tools))
28
+ .replace(DIALECT_PROMPT_TOKEN, () => prompt);
27
29
  }