@oh-my-pi/pi-ai 15.13.2 → 16.0.0
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.
- package/CHANGELOG.md +63 -0
- package/dist/types/{grammar → dialect}/anthropic.d.ts +3 -3
- package/dist/types/{grammar → dialect}/catalog.d.ts +2 -2
- package/dist/types/{grammar → dialect}/deepseek.d.ts +3 -3
- package/dist/types/dialect/examples.d.ts +2 -0
- package/dist/types/dialect/factory.d.ts +3 -0
- package/dist/types/dialect/gemini.d.ts +17 -0
- package/dist/types/dialect/gemma.d.ts +15 -0
- package/dist/types/{grammar → dialect}/glm.d.ts +3 -3
- package/dist/types/{grammar → dialect}/harmony.d.ts +3 -3
- package/dist/types/{grammar → dialect}/hermes.d.ts +3 -3
- package/dist/types/dialect/history.d.ts +3 -0
- package/dist/types/{grammar → dialect}/inventory.d.ts +3 -3
- package/dist/types/{grammar → dialect}/kimi.d.ts +4 -3
- package/dist/types/dialect/owned-stream.d.ts +4 -0
- package/dist/types/{grammar → dialect}/pi.d.ts +3 -3
- package/dist/types/{grammar → dialect}/qwen3.d.ts +3 -3
- package/dist/types/dialect/rendering.d.ts +45 -0
- package/dist/types/{grammar → dialect}/types.d.ts +16 -14
- package/dist/types/{grammar → dialect}/xml.d.ts +3 -3
- package/dist/types/registry/azure.d.ts +4 -0
- package/dist/types/registry/registry.d.ts +3 -0
- package/dist/types/utils/stream-markup-healing.d.ts +6 -5
- package/package.json +6 -6
- package/src/{grammar → dialect}/anthropic.ts +75 -11
- package/src/{grammar → dialect}/catalog.ts +8 -6
- package/src/{grammar → dialect}/deepseek.ts +76 -16
- package/src/{grammar → dialect}/examples.ts +5 -5
- package/src/dialect/factory.ts +34 -0
- package/src/dialect/gemini.md +43 -0
- package/src/dialect/gemini.ts +596 -0
- package/src/dialect/gemma.md +32 -0
- package/src/dialect/gemma.ts +387 -0
- package/src/{grammar → dialect}/glm.ts +82 -10
- package/src/{grammar → dialect}/harmony.ts +84 -10
- package/src/{grammar → dialect}/hermes.ts +45 -10
- package/src/{grammar → dialect}/history.ts +12 -12
- package/src/dialect/inventory.ts +73 -0
- package/src/{grammar → dialect}/kimi.md +1 -1
- package/src/dialect/kimi.ts +340 -0
- package/src/{grammar → dialect}/owned-stream.ts +96 -10
- package/src/{grammar → dialect}/pi.md +9 -1
- package/src/{grammar → dialect}/pi.ts +145 -17
- package/src/{grammar → dialect}/prompt-template.md +1 -1
- package/src/{grammar → dialect}/qwen3.ts +47 -10
- package/src/dialect/rendering.ts +194 -0
- package/src/{grammar → dialect}/types.ts +16 -14
- package/src/dialect/xml.ts +90 -0
- package/src/providers/ollama.ts +5 -6
- package/src/providers/openai-completions.ts +10 -16
- package/src/registry/azure.ts +6 -0
- package/src/registry/registry.ts +2 -0
- package/src/utils/stream-markup-healing.ts +19 -12
- package/dist/types/grammar/examples.d.ts +0 -2
- package/dist/types/grammar/factory.d.ts +0 -3
- package/dist/types/grammar/history.d.ts +0 -3
- package/dist/types/grammar/owned-stream.d.ts +0 -4
- package/dist/types/grammar/rendering.d.ts +0 -24
- package/src/grammar/factory.ts +0 -30
- package/src/grammar/inventory.ts +0 -28
- package/src/grammar/kimi.ts +0 -198
- package/src/grammar/rendering.ts +0 -214
- package/src/grammar/xml.ts +0 -33
- /package/dist/types/{grammar → dialect}/coercion.d.ts +0 -0
- /package/dist/types/{grammar → dialect}/index.d.ts +0 -0
- /package/dist/types/{grammar → dialect}/thinking.d.ts +0 -0
- /package/src/{grammar → dialect}/anthropic.md +0 -0
- /package/src/{grammar → dialect}/coercion.ts +0 -0
- /package/src/{grammar → dialect}/deepseek.md +0 -0
- /package/src/{grammar → dialect}/glm.md +0 -0
- /package/src/{grammar → dialect}/harmony.md +0 -0
- /package/src/{grammar → dialect}/hermes.md +0 -0
- /package/src/{grammar → dialect}/index.ts +0 -0
- /package/src/{grammar → dialect}/qwen3.md +0 -0
- /package/src/{grammar → dialect}/thinking.ts +0 -0
- /package/src/{grammar → dialect}/xml.md +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.0.0] - 2026-06-15
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Renamed the public dialect entrypoint from `@oh-my-pi/pi-ai/grammar` to `@oh-my-pi/pi-ai/dialect`.
|
|
10
|
+
- Renamed grammar dialect identifiers from `ToolCallSyntax` to `Dialect`, renamed the `Grammar` interface to `DialectDefinition`, and renamed `Grammar.syntax` to `DialectDefinition.dialect`.
|
|
11
|
+
- Added `DialectDefinition.renderThinking` and `DialectDefinition.renderTranscript` so dialect implementations serialize complete native chat transcripts, not just tool call/result blocks.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added `renderTranscript` method to dialect definitions for serializing complete native chat transcripts
|
|
16
|
+
- Added `renderThinking` method to dialect definitions for rendering thinking/reasoning blocks
|
|
17
|
+
- Added support for 11 dialect implementations: Anthropic, DeepSeek, Gemini, Gemma, GLM, Harmony, Hermes, Kimi, Pi-native, Qwen3, and XML
|
|
18
|
+
- Added `createInbandScanner` factory function to instantiate dialect-specific scanners
|
|
19
|
+
- Added `getDialectDefinition` function to retrieve dialect implementations by name
|
|
20
|
+
- Added `renderToolCatalog` and `renderInbandToolPrompt` functions for tool catalog rendering
|
|
21
|
+
- Added `renderToolInventory` function to generate human-readable per-tool documentation with examples
|
|
22
|
+
- Added `renderToolExamples` function to render tool usage examples in the model's native dialect
|
|
23
|
+
- Added `encodeInbandToolHistory` function to encode tool call history in dialect-specific format
|
|
24
|
+
- Added `wrapInbandToolStream` function to process streaming responses with in-band tool call parsing
|
|
25
|
+
- Added `ThinkingInbandScanner` for parsing thinking/reasoning blocks across dialects
|
|
26
|
+
- Added `OwnedStream` class for managing dialect-aware streaming with tool call events
|
|
27
|
+
- 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).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Moved public dialect entrypoint from `@oh-my-pi/pi-ai/grammar` to `@oh-my-pi/pi-ai/dialect` in package exports
|
|
32
|
+
- Updated internal imports in `stream-markup-healing.ts` to use new dialect module path
|
|
33
|
+
- 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.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Fixed Gemini, Gemma, Kimi, and Pi in-band scanners to respect `parseThinking: false`, leaving private reasoning markers in visible text when parsing is disabled
|
|
38
|
+
- Fixed thinking-channel parsing for streaming Gemini, Gemma, Kimi, and Pi outputs so split or partial `<thinking>` blocks no longer leak into visible replies
|
|
39
|
+
- 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.
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- Removed `src/grammar/factory.ts` (replaced by `src/dialect/factory.ts`)
|
|
44
|
+
- Removed `src/grammar/rendering.ts` (functionality moved to `src/dialect/rendering.ts`)
|
|
45
|
+
- Removed `src/grammar/xml.ts` (replaced by `src/dialect/xml.ts`)
|
|
46
|
+
|
|
47
|
+
## [15.13.3] - 2026-06-15
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Added the `gemini` in-band tool-call syntax with Python-style ```tool_code``` blocks and `default_api` invocations
|
|
52
|
+
- Added the `gemma` token-delimited in-band tool-call syntax using `<|tool_call>` and `<|tool_response>` blocks
|
|
53
|
+
- Added `gemini` and `gemma` to owned stream tool-result token detection so their tool responses are recognized
|
|
54
|
+
- Fixed truncated Gemini and Gemma tool blocks from being emitted as plain text during streaming
|
|
55
|
+
- Added the Azure OpenAI provider definition (`azure`) to the registry; `AZURE_OPENAI_API_KEY` resolves as its env-var API key via the catalog provider table.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- Gemini tool-call examples now render without the `default_api.` namespace prefix, keeping `<example>` blocks concise. The live wire format still uses `default_api.` per the Gemini grammar.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- Fixed duplicate tool call projections by deduplicating provider-native `toolCall` events against in-band `tool_code` calls and keeping only the first real channel
|
|
64
|
+
- Dropped nameless native `toolCall` events so they no longer appear as surfaced tool calls in owned-mode streams
|
|
65
|
+
- Fixed truncated Gemini and Gemma tool blocks from being emitted as plain text during streaming
|
|
66
|
+
- Fixed Gemini/Gemma in-band tool-call parsing around Python comments, raw/unicode string literals, and Gemma close-token text inside string values.
|
|
67
|
+
|
|
5
68
|
## [15.13.2] - 2026-06-15
|
|
6
69
|
|
|
7
70
|
### Added
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
9
|
-
export default
|
|
8
|
+
declare const definition: DialectDefinition;
|
|
9
|
+
export default definition;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
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[],
|
|
3
|
+
export declare function renderInbandToolPrompt(tools: readonly InbandTool[], dialect: Dialect): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
14
|
-
export default
|
|
13
|
+
declare const definition: DialectDefinition;
|
|
14
|
+
export default definition;
|
|
@@ -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;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Scanner for the hosted-Gemini / Gemma 3 Pythonic tool-calling convention
|
|
4
|
+
* (see `docs/toolconv/gemini.md`). Tool calls arrive as a ```` ```tool_code ````
|
|
5
|
+
* fenced block whose body is one or more Python call expressions, e.g.
|
|
6
|
+
* `print(default_api.search(pattern="x", skip=40))`. Like the qwen3 scanner we
|
|
7
|
+
* buffer the whole block until its closing fence, then parse all calls at once
|
|
8
|
+
* (no incremental argument deltas — Python literals are not worth streaming).
|
|
9
|
+
*/
|
|
10
|
+
export declare class GeminiInbandScanner implements InbandScanner {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(options?: InbandScannerOptions);
|
|
13
|
+
feed(text: string): InbandScanEvent[];
|
|
14
|
+
flush(): InbandScanEvent[];
|
|
15
|
+
}
|
|
16
|
+
declare const definition: DialectDefinition;
|
|
17
|
+
export default definition;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DialectDefinition, InbandScanEvent, InbandScanner, InbandScannerOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Scanner for the Gemma 4 token-delimited tool-calling convention (see
|
|
4
|
+
* `docs/toolconv/gemma.md`). Each call is one `<|tool_call>call:NAME{…}<tool_call|>`
|
|
5
|
+
* block whose argument list is `key:value` pairs; string values are wrapped in
|
|
6
|
+
* the `<|"|>` token rather than ASCII quotes, so splitting must skip those spans.
|
|
7
|
+
*/
|
|
8
|
+
export declare class GemmaInbandScanner implements InbandScanner {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(options?: InbandScannerOptions);
|
|
11
|
+
feed(text: string): InbandScanEvent[];
|
|
12
|
+
flush(): InbandScanEvent[];
|
|
13
|
+
}
|
|
14
|
+
declare const definition: DialectDefinition;
|
|
15
|
+
export default definition;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
9
|
-
export default
|
|
8
|
+
declare const definition: DialectDefinition;
|
|
9
|
+
export default definition;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
8
|
-
export default
|
|
7
|
+
declare const definition: DialectDefinition;
|
|
8
|
+
export default definition;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
9
|
-
export default
|
|
8
|
+
declare const definition: DialectDefinition;
|
|
9
|
+
export default definition;
|
|
@@ -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
|
|
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
|
|
10
|
-
* (`
|
|
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 {
|
|
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
|
|
13
|
-
export default
|
|
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 {
|
|
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
|
|
9
|
-
export default
|
|
8
|
+
declare const definition: DialectDefinition;
|
|
9
|
+
export default definition;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
9
|
-
export default
|
|
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 {
|
|
2
|
-
import type { Context, ToolCall } from "../types";
|
|
3
|
-
export type {
|
|
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
|
|
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
|
|
43
|
+
export interface DialectRenderOptions {
|
|
44
44
|
readonly tools?: readonly InbandTool[];
|
|
45
45
|
readonly example?: boolean;
|
|
46
46
|
}
|
|
47
|
-
export interface
|
|
48
|
-
readonly
|
|
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?:
|
|
53
|
-
/** Render a batch of (parallel) tool calls as one complete block, including whatever envelope the
|
|
54
|
-
renderAssistantToolCalls(calls: readonly ToolCall[], options?:
|
|
55
|
-
renderToolResults(results: readonly
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
9
|
-
export default
|
|
8
|
+
declare const definition: DialectDefinition;
|
|
9
|
+
export default definition;
|
|
@@ -25,6 +25,9 @@ declare const ALL: ({
|
|
|
25
25
|
readonly refreshToken: (credentials: import("./oauth").OAuthCredentials) => Promise<import("./oauth").OAuthCredentials>;
|
|
26
26
|
readonly callbackPort: 54545;
|
|
27
27
|
readonly pasteCodeFlow: true;
|
|
28
|
+
} | {
|
|
29
|
+
readonly id: "azure";
|
|
30
|
+
readonly name: "Azure OpenAI";
|
|
28
31
|
} | {
|
|
29
32
|
readonly id: "cerebras";
|
|
30
33
|
readonly name: "Cerebras";
|
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
51
|
-
* chunk also carries structured `tool_calls`, keeping
|
|
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
|
-
|
|
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": "
|
|
4
|
+
"version": "16.0.0",
|
|
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": "
|
|
42
|
-
"@oh-my-pi/pi-utils": "
|
|
41
|
+
"@oh-my-pi/pi-catalog": "16.0.0",
|
|
42
|
+
"@oh-my-pi/pi-utils": "16.0.0",
|
|
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
|
-
"./
|
|
100
|
-
"types": "./dist/types/
|
|
101
|
-
"import": "./src/
|
|
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
|
|
3
|
-
import { buildStringArgsResolver, mintToolCallId } from "./coercion";
|
|
4
|
-
import {
|
|
5
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
|
517
|
-
renderAssistantToolCalls
|
|
518
|
-
renderToolResults
|
|
578
|
+
renderToolCall,
|
|
579
|
+
renderAssistantToolCalls,
|
|
580
|
+
renderToolResults,
|
|
581
|
+
renderThinking,
|
|
582
|
+
renderTranscript,
|
|
519
583
|
};
|
|
520
584
|
|
|
521
|
-
export default
|
|
585
|
+
export default definition;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { toolWireSchema } from "../utils/schema";
|
|
2
|
-
import {
|
|
2
|
+
import { getDialectDefinition } from "./factory";
|
|
3
3
|
import promptTemplate from "./prompt-template.md" with { type: "text" };
|
|
4
|
-
import type {
|
|
4
|
+
import type { Dialect, InbandTool } from "./types";
|
|
5
5
|
|
|
6
6
|
const TOOLS_TOKEN = "{{TOOLS}}";
|
|
7
|
-
const
|
|
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[],
|
|
25
|
-
const prompt =
|
|
26
|
-
return promptTemplate
|
|
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
|
}
|