@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
  3. package/dist/types/index.d.ts +2 -0
  4. package/dist/types/providers/anthropic-messages-server-schema.d.ts +31 -0
  5. package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
  6. package/dist/types/providers/openai-shared.d.ts +3 -1
  7. package/dist/types/registry/api-key-login.d.ts +1 -0
  8. package/dist/types/registry/api-key-validation.d.ts +1 -0
  9. package/dist/types/registry/coreweave.d.ts +7 -0
  10. package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
  11. package/dist/types/registry/gitlab-duo.d.ts +1 -1
  12. package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
  13. package/dist/types/registry/registry.d.ts +13 -1
  14. package/dist/types/stream.d.ts +8 -0
  15. package/dist/types/types.d.ts +12 -0
  16. package/dist/types/usage/ollama.d.ts +5 -0
  17. package/dist/types/usage/openai-codex-base-url.d.ts +1 -0
  18. package/dist/types/usage/openai-codex.d.ts +0 -1
  19. package/dist/types/usage.d.ts +33 -0
  20. package/dist/types/utils/thinking-loop.d.ts +46 -0
  21. package/package.json +4 -4
  22. package/src/api-registry.ts +1 -0
  23. package/src/auth-broker/wire-schemas.ts +7 -0
  24. package/src/auth-storage.ts +15 -2
  25. package/src/dialect/anthropic.md +2 -2
  26. package/src/dialect/anthropic.ts +1 -1
  27. package/src/dialect/deepseek.md +2 -1
  28. package/src/dialect/deepseek.ts +1 -1
  29. package/src/dialect/gemini.md +2 -1
  30. package/src/dialect/gemma.md +2 -1
  31. package/src/dialect/glm.md +2 -2
  32. package/src/dialect/harmony.md +2 -1
  33. package/src/dialect/harmony.ts +1 -1
  34. package/src/dialect/hermes.md +2 -1
  35. package/src/dialect/hermes.ts +1 -1
  36. package/src/dialect/kimi.md +2 -1
  37. package/src/dialect/kimi.ts +1 -1
  38. package/src/dialect/minimax.md +2 -2
  39. package/src/dialect/pi.md +1 -1
  40. package/src/dialect/qwen3.md +2 -1
  41. package/src/dialect/qwen3.ts +1 -1
  42. package/src/dialect/xml.md +2 -2
  43. package/src/index.ts +2 -0
  44. package/src/providers/amazon-bedrock.ts +8 -2
  45. package/src/providers/anthropic-messages-server-schema.ts +6 -1
  46. package/src/providers/anthropic.ts +2 -1
  47. package/src/providers/cursor.ts +7 -2
  48. package/src/providers/devin.ts +1 -2
  49. package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
  50. package/src/providers/gitlab-duo-workflow.ts +3038 -0
  51. package/src/providers/ollama.ts +1 -2
  52. package/src/providers/openai-codex-responses.ts +1 -1
  53. package/src/providers/openai-completions.ts +8 -2
  54. package/src/providers/openai-shared.ts +76 -4
  55. package/src/registry/api-key-login.ts +2 -0
  56. package/src/registry/api-key-validation.ts +8 -0
  57. package/src/registry/coreweave.ts +38 -0
  58. package/src/registry/gitlab-duo-workflow.ts +20 -0
  59. package/src/registry/gitlab-duo.ts +1 -1
  60. package/src/registry/oauth/gitlab-duo-workflow.ts +134 -0
  61. package/src/registry/oauth/oauth.html +177 -59
  62. package/src/registry/registry.ts +4 -0
  63. package/src/stream.ts +505 -16
  64. package/src/types.ts +13 -0
  65. package/src/usage/ollama.ts +41 -0
  66. package/src/usage/openai-codex-base-url.ts +15 -0
  67. package/src/usage/openai-codex-reset.ts +1 -1
  68. package/src/usage/openai-codex.ts +30 -15
  69. package/src/usage.ts +26 -0
  70. package/src/utils/thinking-loop.ts +183 -18
  71. package/dist/types/utils/json-parse.d.ts +0 -48
  72. 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.1.22",
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.1.22",
42
- "@oh-my-pi/pi-utils": "16.1.22",
43
- "@oh-my-pi/pi-wire": "16.1.22",
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
  },
@@ -27,6 +27,7 @@ const BUILTIN_API_IDS = [
27
27
  "google-vertex",
28
28
  "ollama-chat",
29
29
  "cursor-agent",
30
+ "gitlab-duo-agent",
30
31
  "devin-agent",
31
32
  ] as const satisfies readonly KnownApi[];
32
33
 
@@ -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({
@@ -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
- const manualCodeInput = () => ctrl.onPrompt({ message: "Paste the authorization code (or full redirect URL):" });
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) {
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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 {
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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";
@@ -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 &amp; 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
- - After emitting the ` ```tool_code ` block, YOU MUST STOP AND HALT.
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).
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST STOP AND HALT.
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).
@@ -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
- - String values are raw text (no quotes, no escaping); non-string values are valid JSON.
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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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 {
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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";
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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";
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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.
@@ -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";
@@ -17,6 +17,6 @@ verbatim tool result
17
17
  ## Rules
18
18
 
19
19
  - `name` MUST match a listed function.
20
- - String values are literal text (no JSON quotes or escaping); 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.
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 &amp; 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
- - After emitting your tool calls, YOU MUST EMIT THE STOP SEQUENCE AND HALT.
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 { $env, $flag, extractHttpStatusFromError, fetchWithRetry } from "@oh-my-pi/pi-utils";
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";
@@ -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 { $env, extractHttpStatusFromError, sanitizeText } from "@oh-my-pi/pi-utils";
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";
@@ -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.