@oh-my-pi/pi-ai 16.1.22 → 16.2.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 +52 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +31 -0
- package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
- package/dist/types/providers/openai-shared.d.ts +3 -1
- package/dist/types/registry/api-key-login.d.ts +1 -0
- package/dist/types/registry/api-key-validation.d.ts +1 -0
- package/dist/types/registry/coreweave.d.ts +7 -0
- package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
- package/dist/types/registry/gitlab-duo.d.ts +1 -1
- package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
- package/dist/types/registry/registry.d.ts +13 -1
- package/dist/types/stream.d.ts +8 -0
- package/dist/types/types.d.ts +12 -0
- package/dist/types/usage/ollama.d.ts +5 -0
- package/dist/types/usage/openai-codex-base-url.d.ts +1 -0
- package/dist/types/usage/openai-codex.d.ts +0 -1
- package/dist/types/usage.d.ts +33 -0
- package/dist/types/utils/thinking-loop.d.ts +46 -0
- package/package.json +4 -4
- package/src/api-registry.ts +1 -0
- package/src/auth-broker/wire-schemas.ts +7 -0
- package/src/auth-storage.ts +15 -2
- package/src/dialect/anthropic.md +2 -2
- package/src/dialect/anthropic.ts +1 -1
- package/src/dialect/deepseek.md +2 -1
- package/src/dialect/deepseek.ts +1 -1
- package/src/dialect/gemini.md +2 -1
- package/src/dialect/gemma.md +2 -1
- package/src/dialect/glm.md +2 -2
- package/src/dialect/harmony.md +2 -1
- package/src/dialect/harmony.ts +1 -1
- package/src/dialect/hermes.md +2 -1
- package/src/dialect/hermes.ts +1 -1
- package/src/dialect/kimi.md +2 -1
- package/src/dialect/kimi.ts +1 -1
- package/src/dialect/minimax.md +2 -2
- package/src/dialect/pi.md +1 -1
- package/src/dialect/qwen3.md +2 -1
- package/src/dialect/qwen3.ts +1 -1
- package/src/dialect/xml.md +2 -2
- package/src/index.ts +2 -0
- package/src/providers/amazon-bedrock.ts +8 -2
- package/src/providers/anthropic-messages-server-schema.ts +6 -1
- package/src/providers/anthropic.ts +2 -1
- package/src/providers/cursor.ts +7 -2
- package/src/providers/devin.ts +1 -2
- package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
- package/src/providers/gitlab-duo-workflow.ts +3038 -0
- package/src/providers/ollama.ts +1 -2
- package/src/providers/openai-codex-responses.ts +1 -1
- package/src/providers/openai-completions.ts +8 -2
- package/src/providers/openai-shared.ts +76 -4
- package/src/registry/api-key-login.ts +2 -0
- package/src/registry/api-key-validation.ts +8 -0
- package/src/registry/coreweave.ts +38 -0
- package/src/registry/gitlab-duo-workflow.ts +20 -0
- package/src/registry/gitlab-duo.ts +1 -1
- package/src/registry/oauth/gitlab-duo-workflow.ts +134 -0
- package/src/registry/oauth/oauth.html +177 -59
- package/src/registry/registry.ts +4 -0
- package/src/stream.ts +505 -16
- package/src/types.ts +13 -0
- package/src/usage/ollama.ts +41 -0
- package/src/usage/openai-codex-base-url.ts +15 -0
- package/src/usage/openai-codex-reset.ts +1 -1
- package/src/usage/openai-codex.ts +30 -15
- package/src/usage.ts +26 -0
- package/src/utils/thinking-loop.ts +183 -18
- package/dist/types/utils/json-parse.d.ts +0 -48
- package/src/utils/json-parse.ts +0 -558
|
@@ -4,6 +4,15 @@ import { AssistantMessageEventStream } from "./event-stream";
|
|
|
4
4
|
* message also carries "stream stall" so the session + transport retry
|
|
5
5
|
* classifiers treat it as a transient (retryable) stop without bespoke rules. */
|
|
6
6
|
export declare const THINKING_LOOP_ERROR_MARKER = "Thinking loop detected";
|
|
7
|
+
/**
|
|
8
|
+
* True when `model` is a Gemini model whose native thinking stream surfaces the
|
|
9
|
+
* "thought summary" titles this module's header guard counts.
|
|
10
|
+
*
|
|
11
|
+
* OpenAI-compat transports can serve Gemini under an arbitrary provider/id, so they
|
|
12
|
+
* carry the explicit `compat.enableGeminiThinkingLoopGuard` flag; direct Gemini
|
|
13
|
+
* transports carry a clearly shaped id/provider, so a string match is sufficient.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isGeminiThinkingModel(model: Model<Api>): boolean;
|
|
7
16
|
/**
|
|
8
17
|
* True when `model` should be guarded for thinking/response loops (Gemini & DeepSeek).
|
|
9
18
|
*
|
|
@@ -27,6 +36,43 @@ export declare class ThinkingLoopDetector {
|
|
|
27
36
|
* which may be the one that completes a duplicate cluster — is not dropped. */
|
|
28
37
|
flush(): string | null;
|
|
29
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Consecutive Gemini thought-summary headers in one uninterrupted reasoning
|
|
41
|
+
* stream that trips the tool-call reminder. Gemini occasionally narrates a long
|
|
42
|
+
* chain of titled summaries ("Examining Result Handling", "Refining Result
|
|
43
|
+
* Rendering", …) without ever calling a tool, burning the whole budget on
|
|
44
|
+
* planning; at this many distinct titles it has almost certainly stalled. This
|
|
45
|
+
* is the over-planning shape {@link ThinkingLoopDetector} misses — those titles
|
|
46
|
+
* are stripped before its similarity analysis precisely because their wording
|
|
47
|
+
* keeps changing, so a genuinely-distinct planning runaway never trips it.
|
|
48
|
+
*/
|
|
49
|
+
export declare const GEMINI_HEADER_RUNAWAY_THRESHOLD = 10;
|
|
50
|
+
/**
|
|
51
|
+
* True when a single trimmed line is a Gemini reasoning-summary title: a markdown
|
|
52
|
+
* ATX heading (`## …`) or a whole-line bold / bold-italic run (`**Title**`,
|
|
53
|
+
* `***Title***`). Inline emphasis inside prose never matches — the bold run must
|
|
54
|
+
* span the entire line. Mirrors the title shapes {@link ThinkingLoopDetector}
|
|
55
|
+
* strips before similarity analysis.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isReasoningSummaryHeader(line: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Counts consecutive Gemini reasoning-summary headers across a streamed thinking
|
|
60
|
+
* block. {@link push} returns true exactly once — when the running header count
|
|
61
|
+
* first reaches {@link GEMINI_HEADER_RUNAWAY_THRESHOLD} — and the caller then
|
|
62
|
+
* interrupts the stream and reminds the model to issue a tool call. Paragraph
|
|
63
|
+
* lines between titles do NOT reset the run (Gemini emits header + paragraph per
|
|
64
|
+
* thought, so the run IS the number of summaries); leaving the reasoning channel
|
|
65
|
+
* does, via {@link reset} on a new thinking block / prose / tool call.
|
|
66
|
+
*/
|
|
67
|
+
export declare class GeminiHeaderRunDetector {
|
|
68
|
+
#private;
|
|
69
|
+
/** Feed a thinking delta. Returns true the first time the run hits the threshold. */
|
|
70
|
+
push(delta: string): boolean;
|
|
71
|
+
/** Number of summary titles counted in the current run (for the reminder/log). */
|
|
72
|
+
get count(): number;
|
|
73
|
+
/** Re-arm for a fresh reasoning block: clears the buffer, count, and latch. */
|
|
74
|
+
reset(): void;
|
|
75
|
+
}
|
|
30
76
|
/**
|
|
31
77
|
* Wrap a provider stream with the loop guard. `controller` is the guard's own
|
|
32
78
|
* abort handle: aborting it (after wiring it into the provider's signal via
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "16.
|
|
4
|
+
"version": "16.2.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,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.0",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "16.
|
|
42
|
-
"@oh-my-pi/pi-utils": "16.
|
|
43
|
-
"@oh-my-pi/pi-wire": "16.
|
|
41
|
+
"@oh-my-pi/pi-catalog": "16.2.0",
|
|
42
|
+
"@oh-my-pi/pi-utils": "16.2.0",
|
|
43
|
+
"@oh-my-pi/pi-wire": "16.2.0",
|
|
44
44
|
"arktype": "^2.2.0",
|
|
45
45
|
"zod": "^4"
|
|
46
46
|
},
|
package/src/api-registry.ts
CHANGED
|
@@ -183,8 +183,15 @@ const usageLimitSchema = type({
|
|
|
183
183
|
"notes?": "string[]",
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
+
const usageResetCreditDetailSchema = type({
|
|
187
|
+
"grantedAt?": "string",
|
|
188
|
+
"expiresAt?": "string",
|
|
189
|
+
"status?": "string",
|
|
190
|
+
});
|
|
191
|
+
|
|
186
192
|
const usageResetCreditsSchema = type({
|
|
187
193
|
availableCount: "number",
|
|
194
|
+
"credits?": usageResetCreditDetailSchema.array(),
|
|
188
195
|
});
|
|
189
196
|
|
|
190
197
|
const arkUsageReportSchema = type({
|
package/src/auth-storage.ts
CHANGED
|
@@ -13,7 +13,7 @@ import * as path from "node:path";
|
|
|
13
13
|
import { extractHttpStatusFromError, getAgentDbPath, logger } from "@oh-my-pi/pi-utils";
|
|
14
14
|
import type { ApiKeyResolver } from "./auth-retry";
|
|
15
15
|
import { isUsageLimitOutcome } from "./rate-limit-utils";
|
|
16
|
-
import { getProviderDefinition } from "./registry";
|
|
16
|
+
import { getProviderDefinition, PASTE_CODE_LOGIN_PROVIDERS } from "./registry";
|
|
17
17
|
import { getOAuthApiKey, getOAuthProvider, refreshOAuthToken } from "./registry/oauth";
|
|
18
18
|
import type { OAuthController, OAuthCredentials, OAuthProvider, OAuthProviderId } from "./registry/oauth/types";
|
|
19
19
|
import { getEnvApiKey, getEnvApiKeyName } from "./stream";
|
|
@@ -39,6 +39,7 @@ import { googleGeminiCliUsageProvider } from "./usage/gemini";
|
|
|
39
39
|
import { githubCopilotUsageProvider } from "./usage/github-copilot";
|
|
40
40
|
import { antigravityRankingStrategy, antigravityUsageProvider } from "./usage/google-antigravity";
|
|
41
41
|
import { kimiUsageProvider } from "./usage/kimi";
|
|
42
|
+
import { ollamaCloudUsageProvider, ollamaUsageProvider } from "./usage/ollama";
|
|
42
43
|
import { codexRankingStrategy, openaiCodexUsageProvider } from "./usage/openai-codex";
|
|
43
44
|
import {
|
|
44
45
|
type CodexResetConsumeCode,
|
|
@@ -496,6 +497,8 @@ const DEFAULT_USAGE_PROVIDERS: UsageProvider[] = [
|
|
|
496
497
|
kimiUsageProvider,
|
|
497
498
|
antigravityUsageProvider,
|
|
498
499
|
googleGeminiCliUsageProvider,
|
|
500
|
+
ollamaUsageProvider,
|
|
501
|
+
ollamaCloudUsageProvider,
|
|
499
502
|
claudeUsageProvider,
|
|
500
503
|
zaiUsageProvider,
|
|
501
504
|
opencodeGoUsageProvider,
|
|
@@ -1884,7 +1887,17 @@ export class AuthStorage {
|
|
|
1884
1887
|
onPrompt: (prompt: { message: string; placeholder?: string }) => Promise<string>;
|
|
1885
1888
|
},
|
|
1886
1889
|
): Promise<void> {
|
|
1887
|
-
|
|
1890
|
+
// Only paste-code providers (fixed non-loopback redirect, e.g. GitLab Duo
|
|
1891
|
+
// Agent's vscode:// URI) get a default manual-code prompt. For loopback OAuth
|
|
1892
|
+
// providers the `OAuthCallbackFlow` would otherwise race this readline prompt
|
|
1893
|
+
// against the HTTP callback and, when the callback wins, leave the prompt
|
|
1894
|
+
// outstanding — a dirty/blocked terminal. Synthesizing the default only for
|
|
1895
|
+
// paste-code providers is the authoritative gate (it covers every caller, not
|
|
1896
|
+
// just the CLI); an explicit caller-supplied `onManualCodeInput` is still
|
|
1897
|
+
// honored for any provider as an escape hatch.
|
|
1898
|
+
const manualCodeInput = PASTE_CODE_LOGIN_PROVIDERS.has(provider)
|
|
1899
|
+
? () => ctrl.onPrompt({ message: "Paste the authorization code (or full redirect URL):" })
|
|
1900
|
+
: undefined;
|
|
1888
1901
|
// Built-in registry first, then runtime-registered extension providers.
|
|
1889
1902
|
const def = getProviderDefinition(provider) ?? getOAuthProvider(provider);
|
|
1890
1903
|
if (!def?.login) {
|
package/src/dialect/anthropic.md
CHANGED
|
@@ -22,10 +22,10 @@ Results arrive later in a `<function_results>` block, one `<result>` per call (f
|
|
|
22
22
|
## Rules
|
|
23
23
|
|
|
24
24
|
- `name` MUST match a listed function.
|
|
25
|
-
- String/scalar parameters: exact text, spaces preserved. Lists/objects: JSON.
|
|
25
|
+
- String/scalar parameters: exact text, spaces preserved — bodies are read by regex (delimiter matching), NOT a real XML parser, so never HTML-escape them (emit `a & b`, not `a & b`; `<`/`>` stay literal); only the body's own `</parameter>` closing tag is reserved. Lists/objects: JSON.
|
|
26
26
|
- Multiple calls: multiple `<invoke>` blocks in one `<function_calls>`.
|
|
27
27
|
- You MAY write visible text before the calls.
|
|
28
28
|
- NEVER emit `tool_calls` JSON.
|
|
29
29
|
- NEVER use the legacy `<tool_name>`/`<parameters>` call syntax.
|
|
30
30
|
- Read each `<result>`/`<error>` in call order. NEVER emit `<function_results>` yourself.
|
|
31
|
-
-
|
|
31
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<invoke>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/anthropic.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { parseJsonWithRepair } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { Message, ToolCall } from "../types";
|
|
2
|
-
import { parseJsonWithRepair } from "../utils/json-parse";
|
|
3
3
|
import dialectPrompt from "./anthropic.md" with { type: "text" };
|
|
4
4
|
import { buildArgShapes, buildStringArgsResolver, mintToolCallId, type ToolArgShape } from "./coercion";
|
|
5
5
|
import {
|
package/src/dialect/deepseek.md
CHANGED
|
@@ -16,8 +16,9 @@ Results arrive as output tokens:
|
|
|
16
16
|
|
|
17
17
|
- Use `|` (U+FF5C) and `▁` (U+2581) exactly.
|
|
18
18
|
- Tool name MUST match an available function; arguments are one valid JSON object.
|
|
19
|
+
- Argument string values use only normal JSON string escaping (`\"`, `\\`, `\n`); never HTML-escape their contents — write `a & b`, not `a & b`.
|
|
19
20
|
- NEVER wrap arguments in Markdown fences; NEVER emit a `type` field or `function` prefix.
|
|
20
21
|
- Multiple calls chain `<|tool▁call▁begin|>...<|tool▁call▁end|>` directly — no separators, spaces, or newlines between them.
|
|
21
22
|
- Private reasoning, when needed, goes in `<think>...</think>` before the tokens.
|
|
22
23
|
- Read each output token in call order. NEVER emit output tokens yourself.
|
|
23
|
-
-
|
|
24
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<|tool▁call▁begin|>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/deepseek.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { parseJsonWithRepair } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { Message, ToolCall } from "../types";
|
|
2
|
-
import { parseJsonWithRepair } from "../utils/json-parse";
|
|
3
3
|
import { asRecord, mintToolCallId, partialSuffixOverlapAny } from "./coercion";
|
|
4
4
|
import dialectPrompt from "./deepseek.md" with { type: "text" };
|
|
5
5
|
import { assistantTranscriptParts, collectToolResultRun, messageContentText, stringifyJson } from "./rendering";
|
package/src/dialect/gemini.md
CHANGED
|
@@ -37,7 +37,8 @@ brief reasoning
|
|
|
37
37
|
## Rules
|
|
38
38
|
|
|
39
39
|
- The function name MUST match a listed function; arguments are keyword form (`name=value`).
|
|
40
|
+
- Argument string values use only normal Python string escaping; never HTML-escape their contents — write `"a & b"`, not `"a & b"`.
|
|
40
41
|
- Multiple calls = a single `[...]` list (or one `default_api...` call per line) inside one ` ```tool_code ` block.
|
|
41
42
|
- Put private reasoning in a ` ```thinking ` block before the ` ```tool_code ` block, never inside ` ```tool_code `.
|
|
42
43
|
- Read each ` ```tool_outputs ` block in call order. NEVER write a ` ```tool_outputs ` block yourself.
|
|
43
|
-
-
|
|
44
|
+
- Emit the ` ```tool_code ` block in full, THEN stop and halt — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no ` ```tool_code ` block emitted).
|
package/src/dialect/gemma.md
CHANGED
|
@@ -25,8 +25,9 @@ brief reasoning
|
|
|
25
25
|
## Rules
|
|
26
26
|
|
|
27
27
|
- `NAME` MUST match a listed function; arguments are `key:value` pairs separated by commas.
|
|
28
|
+
- String values between `<|"|>` tokens are raw literal text (no escaping); never HTML-escape them — write `a & b`, not `a & b`.
|
|
28
29
|
- Multiple calls = consecutive `<|tool_call>...<tool_call|>` blocks; keep prose outside them.
|
|
29
30
|
- The closer is `<tool_call|>` (pipe on the right), not `</tool_call>` or `<|tool_call>`.
|
|
30
31
|
- Private reasoning goes in a `<|channel>thought…<channel|>` block before any call; NEVER put tool calls inside it.
|
|
31
32
|
- Read each `<|tool_response>` block in call order. NEVER write a `<|tool_response>` block yourself.
|
|
32
|
-
-
|
|
33
|
+
- Write each call in full, THEN stop and halt — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<|tool_call>` block emitted).
|
package/src/dialect/glm.md
CHANGED
|
@@ -25,8 +25,8 @@ verbatim tool result
|
|
|
25
25
|
|
|
26
26
|
- The name after `<tool_call>` must match a listed function and sit on the same line.
|
|
27
27
|
- Emit one `<arg_key>name</arg_key>` + `<arg_value>value</arg_value>` pair per argument; omit unset optional args.
|
|
28
|
-
-
|
|
28
|
+
- `<arg_value>` bodies are read by regex (delimiter matching), NOT a real XML parser: write string values as raw literal text and never HTML-escape them (emit `a & b`, not `a & b`; `<`/`>` stay literal); only the body's own `</arg_value>` closing tag is reserved. Non-string values are valid JSON.
|
|
29
29
|
- Multiple calls are consecutive `<tool_call>…</tool_call>` blocks.
|
|
30
30
|
- Private reasoning goes in `<think>…</think>`; NEVER put tool calls inside `<think>`.
|
|
31
31
|
- Read each `<tool_response>` in call order. NEVER emit `<tool_response>` yourself.
|
|
32
|
-
-
|
|
32
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<tool_call>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/harmony.md
CHANGED
|
@@ -22,9 +22,10 @@ Tool results arrive as messages authored by the function, addressed back to the
|
|
|
22
22
|
|
|
23
23
|
- Recipient is `functions.` + a listed function name.
|
|
24
24
|
- Body is one JSON object matching the schema; omit optional arguments you are not setting.
|
|
25
|
+
- Argument string values use only normal JSON string escaping (`\"`, `\\`, `\n`); never HTML-escape their contents — write `a & b`, not `a & b`.
|
|
25
26
|
- Multiple calls = consecutive call messages.
|
|
26
27
|
- An optional visible preamble is a `commentary` message ending `<|end|>`.
|
|
27
28
|
- NEVER put tool calls in `analysis`.
|
|
28
29
|
- NEVER wrap calls in Markdown/code fences.
|
|
29
30
|
- Read each tool-result message in call order. NEVER emit tool-result messages yourself.
|
|
30
|
-
-
|
|
31
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<|call|>` message emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/harmony.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { parseJsonWithRepair } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { Message, ToolCall } from "../types";
|
|
2
|
-
import { parseJsonWithRepair } from "../utils/json-parse";
|
|
3
3
|
import { asRecord, mintToolCallId, partialSuffixOverlapAny } from "./coercion";
|
|
4
4
|
import dialectPrompt from "./harmony.md" with { type: "text" };
|
|
5
5
|
import {
|
package/src/dialect/hermes.md
CHANGED
|
@@ -19,6 +19,7 @@ verbatim tool result
|
|
|
19
19
|
## Rules
|
|
20
20
|
|
|
21
21
|
- `name` MUST match a listed function; `arguments` is a JSON object, never a stringified JSON.
|
|
22
|
+
- Argument string values use only normal JSON string escaping (`\"`, `\\`, `\n`); never HTML-escape their contents — write `a & b`, not `a & b`.
|
|
22
23
|
- Emit multiple calls as consecutive `<tool_call>` blocks; keep any prose outside them.
|
|
23
24
|
- Read each `<tool_response>` in call order. NEVER emit `<tool_response>` yourself.
|
|
24
|
-
-
|
|
25
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<tool_call>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/hermes.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { parseJsonWithRepair, parseStreamingJson } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { Message, ToolCall } from "../types";
|
|
2
|
-
import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse";
|
|
3
3
|
import { asRecord, mintToolCallId, partialSuffixOverlapAny } from "./coercion";
|
|
4
4
|
import dialectPrompt from "./hermes.md" with { type: "text" };
|
|
5
5
|
import { renderChatMlTranscript, renderDelimitedThinking, renderToolResponseResults, stringifyJson } from "./rendering";
|
package/src/dialect/kimi.md
CHANGED
|
@@ -17,7 +17,8 @@ verbatim tool result<|im_end|>
|
|
|
17
17
|
|
|
18
18
|
- `NAME` MUST match a listed function exactly.
|
|
19
19
|
- Arguments MUST be one JSON object with double-quoted keys.
|
|
20
|
+
- Argument string values use only normal JSON string escaping (`\"`, `\\`, `\n`); never HTML-escape their contents — write `a & b`, not `a & b`.
|
|
20
21
|
- Multiple calls = consecutive `<|tool_call_begin|>…<|tool_call_end|>` blocks in the same section; `INDEX` increments from `0`.
|
|
21
22
|
- Private reasoning, when supported, goes in `<think>…</think>` before the tool-call section; NEVER put tool calls inside `<think>`.
|
|
22
23
|
- Read each result turn in call order. NEVER emit result turns yourself.
|
|
23
|
-
-
|
|
24
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<|tool_call_begin|>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/kimi.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { parseJsonWithRepair } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { Message, ToolCall } from "../types";
|
|
2
|
-
import { parseJsonWithRepair } from "../utils/json-parse";
|
|
3
3
|
import { asRecord, normalizeKimiFunctionName, partialSuffixOverlapAny } from "./coercion";
|
|
4
4
|
import dialectPrompt from "./kimi.md" with { type: "text" };
|
|
5
5
|
import { assistantTranscriptParts, collectToolResultRun, messageContentText, stringifyJson } from "./rendering";
|
package/src/dialect/minimax.md
CHANGED
|
@@ -22,10 +22,10 @@ Results arrive later in a `<function_results>` block, one `<result>` per call (f
|
|
|
22
22
|
## Rules
|
|
23
23
|
|
|
24
24
|
- `name` MUST match a listed function.
|
|
25
|
-
- String/scalar parameters: exact text, spaces preserved. Lists/objects: JSON.
|
|
25
|
+
- String/scalar parameters: exact text, spaces preserved — bodies are read by regex (delimiter matching), NOT a real XML parser, so never HTML-escape them (emit `a & b`, not `a & b`; `<`/`>` stay literal); only the body's own `</parameter>` closing tag is reserved. Lists/objects: JSON.
|
|
26
26
|
- Multiple calls: multiple `<invoke>` blocks in one `<minimax:tool_call>`.
|
|
27
27
|
- You MAY write visible text before the calls.
|
|
28
28
|
- NEVER emit `tool_calls` JSON.
|
|
29
29
|
- NEVER use `<function_calls>` or the legacy `<tool_name>`/`<parameters>` call syntax.
|
|
30
30
|
- Read each `<result>`/`<error>` in call order. NEVER emit `<function_results>` yourself.
|
|
31
|
-
-
|
|
31
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<invoke>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/pi.md
CHANGED
|
@@ -52,4 +52,4 @@ verbatim tool result
|
|
|
52
52
|
- Emit parallel calls as consecutive `§…` blocks. NEVER invent call ids; results are positional.
|
|
53
53
|
- Private reasoning goes in a `¤…¤` block before your calls; NEVER put calls inside it, and keep a literal `¤` out of the reasoning text.
|
|
54
54
|
- Read each `‡‡…‡‡` result in call order. NEVER emit a `‡‡` block yourself.
|
|
55
|
-
-
|
|
55
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `§` call emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/qwen3.md
CHANGED
|
@@ -21,7 +21,8 @@ verbatim tool result
|
|
|
21
21
|
## Rules
|
|
22
22
|
|
|
23
23
|
- `name` MUST match a listed function; `arguments` is a JSON object, never a JSON string.
|
|
24
|
+
- Argument string values use only normal JSON string escaping (`\"`, `\\`, `\n`); never HTML-escape their contents — write `a & b`, not `a & b`.
|
|
24
25
|
- Multiple calls = consecutive `<tool_call>...</tool_call>` blocks; keep prose outside them.
|
|
25
26
|
- NEVER put tool calls inside `<think>`.
|
|
26
27
|
- Read each `<tool_response>` in call order. NEVER emit `<tool_response>` yourself.
|
|
27
|
-
-
|
|
28
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<tool_call>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/dialect/qwen3.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { parseJsonWithRepair } from "@oh-my-pi/pi-utils";
|
|
1
2
|
import type { Message, ToolCall } from "../types";
|
|
2
|
-
import { parseJsonWithRepair } from "../utils/json-parse";
|
|
3
3
|
import { asRecord, mintToolCallId, partialSuffixOverlapAny } from "./coercion";
|
|
4
4
|
import dialectPrompt from "./qwen3.md" with { type: "text" };
|
|
5
5
|
import { renderChatMlTranscript, renderToolResponseResults, stringifyJson } from "./rendering";
|
package/src/dialect/xml.md
CHANGED
|
@@ -17,6 +17,6 @@ verbatim tool result
|
|
|
17
17
|
## Rules
|
|
18
18
|
|
|
19
19
|
- `name` MUST match a listed function.
|
|
20
|
-
-
|
|
20
|
+
- Parameter values are read literally by regex (delimiter matching), NOT a real XML parser: write them verbatim and never HTML-escape (emit `a & b`, never `a & b`; `<`/`>` stay literal too). Only the body's own `</parameter>` closing tag is reserved. Non-string values are JSON; add `string="false"` to a parameter only to force JSON parsing of a value the schema treats as a string.
|
|
21
21
|
- Read each `<tool_response>` in call order. NEVER emit `<tool_response>` yourself.
|
|
22
|
-
-
|
|
22
|
+
- Emit the stop sequence ONLY after the call is fully written — NEVER announce a tool then stop (e.g. halting at "Let's run `cargo clippy`" with no `<invoke>` emitted). Write the complete call, THEN the stop sequence, THEN halt.
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./providers/anthropic-client";
|
|
|
13
13
|
export * from "./providers/azure-openai-responses";
|
|
14
14
|
export type * from "./providers/cursor";
|
|
15
15
|
export * from "./providers/gitlab-duo";
|
|
16
|
+
export * from "./providers/gitlab-duo-workflow";
|
|
16
17
|
export type * from "./providers/google";
|
|
17
18
|
export type * from "./providers/google-gemini-cli";
|
|
18
19
|
export type * from "./providers/google-vertex";
|
|
@@ -34,6 +35,7 @@ export * from "./usage/github-copilot";
|
|
|
34
35
|
export * from "./usage/google-antigravity";
|
|
35
36
|
export * from "./usage/kimi";
|
|
36
37
|
export * from "./usage/minimax-code";
|
|
38
|
+
export * from "./usage/ollama";
|
|
37
39
|
export * from "./usage/openai-codex";
|
|
38
40
|
export * from "./usage/openai-codex-reset";
|
|
39
41
|
export * from "./usage/opencode-go";
|
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
|
|
11
11
|
import { mapEffortToAnthropicAdaptiveEffort, requireSupportedEffort } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
12
12
|
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
$env,
|
|
15
|
+
$flag,
|
|
16
|
+
extractHttpStatusFromError,
|
|
17
|
+
fetchWithRetry,
|
|
18
|
+
parseStreamingJson,
|
|
19
|
+
parseStreamingJsonThrottled,
|
|
20
|
+
} from "@oh-my-pi/pi-utils";
|
|
14
21
|
import { ProviderHttpError } from "../errors";
|
|
15
22
|
import type {
|
|
16
23
|
Api,
|
|
@@ -32,7 +39,6 @@ import { normalizeToolCallId, resolveCacheRetention } from "../utils";
|
|
|
32
39
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
33
40
|
import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
34
41
|
import { armPreResponseTimeout, getStreamFirstEventTimeoutMs } from "../utils/idle-iterator";
|
|
35
|
-
import { parseStreamingJson, parseStreamingJsonThrottled } from "../utils/json-parse";
|
|
36
42
|
import { toolWireSchema } from "../utils/schema/wire";
|
|
37
43
|
import { invalidateAwsCredentialCache, resolveAwsCredentials } from "./aws-credentials";
|
|
38
44
|
import { decodeEventStream } from "./aws-eventstream";
|
|
@@ -135,12 +135,17 @@ export const userMessageSchema = type({
|
|
|
135
135
|
content: type("string").or(userContentBlockSchema.array()),
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
export const systemMessageSchema = type({
|
|
139
|
+
role: "'system'",
|
|
140
|
+
content: type("string").or(systemBlockSchema.array()),
|
|
141
|
+
});
|
|
142
|
+
|
|
138
143
|
export const assistantMessageSchema = type({
|
|
139
144
|
role: "'assistant'",
|
|
140
145
|
content: type("string").or(assistantContentBlockSchema.array()),
|
|
141
146
|
});
|
|
142
147
|
|
|
143
|
-
export const messageSchema = userMessageSchema.or(assistantMessageSchema);
|
|
148
|
+
export const messageSchema = userMessageSchema.or(assistantMessageSchema).or(systemMessageSchema);
|
|
144
149
|
|
|
145
150
|
// ─── Tools ─────────────────────────────────────────────────────────────────
|
|
146
151
|
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
isRetryableError,
|
|
16
16
|
isUnexpectedSocketCloseMessage,
|
|
17
17
|
logger,
|
|
18
|
+
parseJsonWithRepair,
|
|
19
|
+
parseStreamingJsonThrottled,
|
|
18
20
|
readSseEvents,
|
|
19
21
|
} from "@oh-my-pi/pi-utils";
|
|
20
22
|
import { isUsageLimitError } from "../rate-limit-utils";
|
|
@@ -51,7 +53,6 @@ import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
|
51
53
|
import { isFoundryEnabled } from "../utils/foundry";
|
|
52
54
|
import { finalizeErrorMessage, type RawHttpRequestDump, rewriteCopilotError } from "../utils/http-inspector";
|
|
53
55
|
import { getStreamFirstEventTimeoutMs, getStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
|
|
54
|
-
import { parseJsonWithRepair, parseStreamingJsonThrottled } from "../utils/json-parse";
|
|
55
56
|
import { notifyProviderResponse } from "../utils/provider-response";
|
|
56
57
|
import { isCopilotTransientModelError } from "../utils/retry";
|
|
57
58
|
import { COMBINATOR_KEYS, NO_STRICT, toolWireSchema } from "../utils/schema";
|
package/src/providers/cursor.ts
CHANGED
|
@@ -102,7 +102,13 @@ import {
|
|
|
102
102
|
WriteSuccessSchema,
|
|
103
103
|
} from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
|
|
104
104
|
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
|
105
|
-
import {
|
|
105
|
+
import {
|
|
106
|
+
$env,
|
|
107
|
+
extractHttpStatusFromError,
|
|
108
|
+
parseJsonWithRepair,
|
|
109
|
+
parseStreamingJson,
|
|
110
|
+
sanitizeText,
|
|
111
|
+
} from "@oh-my-pi/pi-utils";
|
|
106
112
|
import type {
|
|
107
113
|
Api,
|
|
108
114
|
AssistantMessage,
|
|
@@ -126,7 +132,6 @@ import type {
|
|
|
126
132
|
import { normalizeSystemPrompts } from "../utils";
|
|
127
133
|
import { deterministicUuid } from "../utils/deterministic-id";
|
|
128
134
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
129
|
-
import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse";
|
|
130
135
|
import { connectProxiedSocket, getProxyForProvider, shouldBypassProxy } from "../utils/proxy";
|
|
131
136
|
import { createRequestDebugSession, isRequestDebugEnabled, type RequestDebugResponseLog } from "../utils/request-debug";
|
|
132
137
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
package/src/providers/devin.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
StopReason,
|
|
29
29
|
} from "@oh-my-pi/pi-catalog/discovery/devin-gen/exa/codeium_common_pb/codeium_common_pb";
|
|
30
30
|
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
|
31
|
-
import { extractHttpStatusFromError, logger } from "@oh-my-pi/pi-utils";
|
|
31
|
+
import { extractHttpStatusFromError, logger, parseStreamingJson } from "@oh-my-pi/pi-utils";
|
|
32
32
|
import type {
|
|
33
33
|
Api,
|
|
34
34
|
AssistantMessage,
|
|
@@ -44,7 +44,6 @@ import type {
|
|
|
44
44
|
} from "../types";
|
|
45
45
|
import { deterministicUuid } from "../utils/deterministic-id";
|
|
46
46
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
47
|
-
import { parseStreamingJson } from "../utils/json-parse";
|
|
48
47
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
49
48
|
import { toolWireSchema } from "../utils/schema/wire";
|
|
50
49
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
The task below is a transcript of the conversation so far, written as a plain-text log. Turn boundaries (`<|im_start|>role` … `<|im_end|>`) and any `<ran NAME>{…}</ran>` / `<ran:result>` entries inside it are a RECORD of what already happened — past tool calls and their results. They are not a syntax for you to emit. To call a tool, use your normal structured tool-calling channel; never write `<ran …>`, `<tool_call>`, `<|im_start|>`, or similar markers as your own output.
|