@oh-my-pi/pi-ai 16.2.1 → 16.2.3

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 (170) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/types/auth-gateway/server.d.ts +0 -19
  3. package/dist/types/auth-retry.d.ts +2 -10
  4. package/dist/types/auth-storage.d.ts +1 -2
  5. package/dist/types/dialect/demotion.d.ts +22 -0
  6. package/dist/types/dialect/index.d.ts +2 -0
  7. package/dist/types/error/abort.d.ts +14 -0
  8. package/dist/types/error/auth-classify.d.ts +16 -0
  9. package/dist/types/error/auth.d.ts +27 -0
  10. package/dist/types/error/aws.d.ts +23 -0
  11. package/dist/types/error/classes.d.ts +102 -0
  12. package/dist/types/error/finalize.d.ts +39 -0
  13. package/dist/types/error/flags.d.ts +70 -0
  14. package/dist/types/error/format.d.ts +20 -0
  15. package/dist/types/error/gateway.d.ts +20 -0
  16. package/dist/types/error/index.d.ts +13 -0
  17. package/dist/types/error/oauth.d.ts +43 -0
  18. package/dist/types/error/provider.d.ts +42 -0
  19. package/dist/types/{rate-limit-utils.d.ts → error/rate-limit.d.ts} +14 -1
  20. package/dist/types/error/retryable.d.ts +27 -0
  21. package/dist/types/error/validation.d.ts +32 -0
  22. package/dist/types/index.d.ts +1 -3
  23. package/dist/types/providers/amazon-bedrock.d.ts +0 -5
  24. package/dist/types/providers/anthropic-client.d.ts +2 -16
  25. package/dist/types/providers/anthropic.d.ts +6 -1
  26. package/dist/types/providers/aws-eventstream.d.ts +2 -1
  27. package/dist/types/providers/cursor.d.ts +8 -7
  28. package/dist/types/providers/google-gemini-cli.d.ts +0 -5
  29. package/dist/types/providers/google-shared.d.ts +0 -5
  30. package/dist/types/providers/ollama.d.ts +0 -5
  31. package/dist/types/providers/openai-codex/request-transformer.d.ts +2 -2
  32. package/dist/types/providers/openai-codex/response-handler.d.ts +1 -1
  33. package/dist/types/providers/openai-codex-responses.d.ts +2 -2
  34. package/dist/types/providers/openai-responses-server-schema.d.ts +6 -0
  35. package/dist/types/providers/openai-responses-wire.d.ts +1 -1
  36. package/dist/types/providers/openai-responses.d.ts +3 -2
  37. package/dist/types/providers/openai-shared.d.ts +8 -6
  38. package/dist/types/providers/pi-native-client.d.ts +0 -9
  39. package/dist/types/registry/oauth/xai-oauth.d.ts +0 -9
  40. package/dist/types/types.d.ts +6 -0
  41. package/dist/types/usage/openai-codex-base-url.d.ts +17 -0
  42. package/dist/types/utils/block-symbols.d.ts +20 -0
  43. package/dist/types/utils/openai-http.d.ts +4 -8
  44. package/dist/types/utils/retry.d.ts +2 -14
  45. package/dist/types/utils/stream-markup-healing.d.ts +14 -5
  46. package/dist/types/utils/thinking-loop.d.ts +3 -1
  47. package/package.json +8 -4
  48. package/src/api-registry.ts +3 -1
  49. package/src/auth-broker/discover.ts +7 -2
  50. package/src/auth-broker/remote-store.ts +8 -7
  51. package/src/auth-gateway/server.ts +27 -115
  52. package/src/auth-retry.ts +9 -21
  53. package/src/auth-storage.ts +34 -49
  54. package/src/dialect/demotion.ts +31 -0
  55. package/src/dialect/factory.ts +0 -2
  56. package/src/dialect/index.ts +2 -0
  57. package/src/dialect/owned-stream.ts +0 -1
  58. package/src/dialect/thinking.ts +22 -10
  59. package/src/error/abort.ts +18 -0
  60. package/src/error/auth-classify.ts +30 -0
  61. package/src/error/auth.ts +48 -0
  62. package/src/error/aws.ts +31 -0
  63. package/src/error/classes.ts +186 -0
  64. package/src/error/finalize.ts +69 -0
  65. package/src/error/flags.ts +486 -0
  66. package/src/error/format.ts +36 -0
  67. package/src/error/gateway.ts +96 -0
  68. package/src/error/index.ts +13 -0
  69. package/src/error/oauth.ts +58 -0
  70. package/src/error/provider.ts +56 -0
  71. package/src/{rate-limit-utils.ts → error/rate-limit.ts} +9 -3
  72. package/src/error/retryable.ts +70 -0
  73. package/src/error/validation.ts +44 -0
  74. package/src/index.ts +1 -3
  75. package/src/providers/amazon-bedrock.ts +61 -57
  76. package/src/providers/anthropic-client.ts +13 -41
  77. package/src/providers/anthropic-messages-server.ts +9 -2
  78. package/src/providers/anthropic.ts +84 -147
  79. package/src/providers/aws-credentials.ts +41 -11
  80. package/src/providers/aws-eventstream.ts +12 -21
  81. package/src/providers/azure-openai-responses.ts +27 -17
  82. package/src/providers/cursor.ts +59 -53
  83. package/src/providers/devin.ts +28 -20
  84. package/src/providers/gitlab-duo-workflow.ts +30 -10
  85. package/src/providers/gitlab-duo.ts +22 -6
  86. package/src/providers/google-auth.ts +15 -5
  87. package/src/providers/google-gemini-cli.ts +46 -60
  88. package/src/providers/google-shared.ts +40 -38
  89. package/src/providers/google-vertex.ts +3 -2
  90. package/src/providers/google.ts +5 -1
  91. package/src/providers/mock.ts +10 -7
  92. package/src/providers/ollama.ts +40 -30
  93. package/src/providers/openai-chat-server.ts +2 -1
  94. package/src/providers/openai-codex/request-transformer.ts +13 -12
  95. package/src/providers/openai-codex/response-handler.ts +1 -1
  96. package/src/providers/openai-codex-responses.ts +931 -1012
  97. package/src/providers/openai-completions.ts +46 -36
  98. package/src/providers/openai-responses-server-schema.ts +3 -0
  99. package/src/providers/openai-responses-server.ts +82 -30
  100. package/src/providers/openai-responses-wire.ts +1 -1
  101. package/src/providers/openai-responses.ts +50 -24
  102. package/src/providers/openai-shared.ts +118 -51
  103. package/src/providers/pi-native-client.ts +12 -18
  104. package/src/providers/pi-native-server.ts +9 -6
  105. package/src/providers/register-builtins.ts +5 -2
  106. package/src/providers/transform-messages.ts +31 -63
  107. package/src/registry/alibaba-coding-plan.ts +6 -5
  108. package/src/registry/api-key-login.ts +4 -3
  109. package/src/registry/api-key-validation.ts +4 -3
  110. package/src/registry/cloudflare-ai-gateway.ts +4 -3
  111. package/src/registry/coreweave.ts +2 -1
  112. package/src/registry/deepseek.ts +2 -1
  113. package/src/registry/google-antigravity.ts +2 -1
  114. package/src/registry/google-gemini-cli.ts +2 -1
  115. package/src/registry/kagi.ts +4 -3
  116. package/src/registry/kilo.ts +32 -10
  117. package/src/registry/litellm.ts +4 -3
  118. package/src/registry/llama-cpp.ts +3 -2
  119. package/src/registry/lm-studio.ts +3 -2
  120. package/src/registry/nvidia.ts +7 -7
  121. package/src/registry/oauth/anthropic.ts +23 -6
  122. package/src/registry/oauth/callback-server.ts +5 -3
  123. package/src/registry/oauth/cursor.ts +18 -4
  124. package/src/registry/oauth/devin.ts +14 -3
  125. package/src/registry/oauth/github-copilot.ts +21 -10
  126. package/src/registry/oauth/gitlab-duo-workflow.ts +15 -3
  127. package/src/registry/oauth/gitlab-duo.ts +30 -6
  128. package/src/registry/oauth/google-antigravity.ts +14 -5
  129. package/src/registry/oauth/google-gemini-cli.ts +24 -7
  130. package/src/registry/oauth/google-oauth-shared.ts +6 -3
  131. package/src/registry/oauth/index.ts +19 -8
  132. package/src/registry/oauth/kimi.ts +40 -10
  133. package/src/registry/oauth/openai-codex.ts +26 -11
  134. package/src/registry/oauth/opencode.ts +4 -3
  135. package/src/registry/oauth/perplexity.ts +33 -11
  136. package/src/registry/oauth/xai-oauth.ts +62 -18
  137. package/src/registry/oauth/xiaomi.ts +21 -9
  138. package/src/registry/ollama-cloud.ts +5 -4
  139. package/src/registry/ollama.ts +3 -2
  140. package/src/registry/parallel.ts +4 -3
  141. package/src/registry/qwen-portal.ts +4 -3
  142. package/src/registry/tavily.ts +4 -3
  143. package/src/registry/vercel-ai-gateway.ts +4 -3
  144. package/src/registry/vllm.ts +3 -2
  145. package/src/stream.ts +79 -24
  146. package/src/types.ts +6 -0
  147. package/src/usage/claude.ts +2 -1
  148. package/src/usage/github-copilot.ts +4 -3
  149. package/src/usage/google-antigravity.ts +2 -1
  150. package/src/usage/openai-codex-base-url.ts +30 -10
  151. package/src/utils/abort.ts +4 -2
  152. package/src/utils/block-symbols.ts +32 -0
  153. package/src/utils/event-stream.ts +15 -3
  154. package/src/utils/http-inspector.ts +4 -4
  155. package/src/utils/idle-iterator.ts +6 -5
  156. package/src/utils/openai-http.ts +11 -46
  157. package/src/utils/parse-bind.ts +7 -5
  158. package/src/utils/proxy.ts +2 -1
  159. package/src/utils/retry.ts +7 -23
  160. package/src/utils/schema/normalize.ts +3 -2
  161. package/src/utils/stream-markup-healing.ts +41 -18
  162. package/src/utils/thinking-loop.ts +15 -11
  163. package/src/utils/validation.ts +4 -3
  164. package/dist/types/dialect/pi.d.ts +0 -9
  165. package/dist/types/errors.d.ts +0 -24
  166. package/dist/types/utils/overflow.d.ts +0 -55
  167. package/src/dialect/pi.md +0 -55
  168. package/src/dialect/pi.ts +0 -600
  169. package/src/errors.ts +0 -32
  170. package/src/utils/overflow.ts +0 -140
package/src/dialect/pi.ts DELETED
@@ -1,600 +0,0 @@
1
- import type { Message, ToolCall } from "../types";
2
- import type { ToolArgShape } from "./coercion";
3
- import { buildArgShapes, coerceValue, isStringOnlySchema, mintToolCallId, partialSuffixOverlapAny } from "./coercion";
4
- import dialectPrompt from "./pi.md" with { type: "text" };
5
- import { renderChatMlTranscript, stringifyJson } from "./rendering";
6
- import type {
7
- DialectDefinition,
8
- DialectRenderOptions,
9
- DialectToolResult,
10
- InbandScanEvent,
11
- InbandScanner,
12
- InbandScannerOptions,
13
- } from "./types";
14
-
15
- // Pi — a sigil-delimited, token-frugal owned dialect.
16
- //
17
- // §read path=src/a.ts offset=50 ← scalar-only call (newline-terminated)
18
- // §edit path=src/a.ts« ← header + verbatim body fence
19
- // *** Begin Patch
20
- // ...
21
- // *** End Patch»
22
- //
23
- // Design goals vs the XML-ish `pi` dialect:
24
- // - one-token structural sigils (`§` call, `«»` body fence, `¤` thinking, `‡‡`
25
- // tool result — each a single o200k token that never occurs in source code)
26
- // instead of `<call:NAME>` / `</call:NAME>` (3 tokens + the repeated name);
27
- // - scalar arguments inline as `key=value` (the key appears once) rather than
28
- // `<key>value</key>` (key twice + four bracket tokens);
29
- // - the dominant string argument fills a verbatim body fence, dropping its key
30
- // entirely and needing no escaping for code/patches.
31
- //
32
- // Verbatim fences (body `«»`, result `‡‡`) escalate Markdown/raw-string style:
33
- // when the content contains the closer, the renderer widens the fence (`««…»»`,
34
- // `‡‡‡…‡‡‡`) so re-rendered history can never collide with payload content.
35
-
36
- const CALL_SIGIL = "§";
37
- const FENCE_OPEN = "«";
38
- const FENCE_CLOSE = "»";
39
- const THINK_SIGIL = "¤";
40
- const RESULT_FENCE = "‡";
41
- const OUTSIDE_TAGS = [CALL_SIGIL, THINK_SIGIL] as const;
42
- const CALL_TAGS = [CALL_SIGIL] as const;
43
- const NAME_START = /[A-Za-z_]/;
44
- const NAME_CHAR = /[A-Za-z0-9_-]/;
45
- const EMPTY_STRING_ARGS: ReadonlySet<string> = new Set<string>();
46
-
47
- type ScannerState = "outside" | "body" | "thinking";
48
-
49
- type HeaderEnd =
50
- | { kind: "fence"; index: number }
51
- | { kind: "newline"; index: number }
52
- | { kind: "eof"; index: number }
53
- | { kind: "incomplete" };
54
-
55
- export class PiNativeInbandScanner implements InbandScanner {
56
- #buffer = "";
57
- #state: ScannerState = "outside";
58
- #id = "";
59
- #name = "";
60
- #args: Record<string, unknown> = {};
61
- #bodyKey = "";
62
- #bodyValue = "";
63
- #bodyLeading = false;
64
- #closeMarker = "";
65
- #rawBlock = "";
66
- #thinking = "";
67
- readonly #argShapes: Map<string, ToolArgShape>;
68
- readonly #stringArgs: (toolName: string) => ReadonlySet<string>;
69
- readonly #knowsTools: boolean;
70
- readonly #parseThinking: boolean;
71
-
72
- constructor(options: InbandScannerOptions = {}) {
73
- this.#argShapes = buildArgShapes(options.tools);
74
- this.#knowsTools = this.#argShapes.size > 0;
75
- this.#stringArgs =
76
- options.stringArgs ?? (toolName => this.#argShapes.get(toolName)?.stringArgs ?? EMPTY_STRING_ARGS);
77
- this.#parseThinking = options.parseThinking !== false;
78
- }
79
-
80
- feed(text: string): InbandScanEvent[] {
81
- if (text.length === 0) return [];
82
- this.#buffer += text;
83
- return this.#consume(false);
84
- }
85
-
86
- flush(): InbandScanEvent[] {
87
- return this.#consume(true);
88
- }
89
-
90
- #consume(final: boolean): InbandScanEvent[] {
91
- const events: InbandScanEvent[] = [];
92
- while (this.#buffer.length > 0) {
93
- if (this.#state === "outside") {
94
- if (!this.#consumeOutside(events, final)) break;
95
- continue;
96
- }
97
- if (this.#state === "thinking") {
98
- if (!this.#consumeThinking(events, final)) break;
99
- continue;
100
- }
101
- if (!this.#consumeBody(events, final)) break;
102
- }
103
- if (final && this.#state === "thinking") this.#endThinking(events);
104
- return events;
105
- }
106
-
107
- #consumeOutside(events: InbandScanEvent[], final: boolean): boolean {
108
- const call = this.#buffer.indexOf(CALL_SIGIL);
109
- const think = this.#parseThinking ? this.#buffer.indexOf(THINK_SIGIL) : -1;
110
- let start = call;
111
- let isThink = false;
112
- if (think !== -1 && (start === -1 || think < start)) {
113
- start = think;
114
- isThink = true;
115
- }
116
- if (start === -1) {
117
- const tags = this.#parseThinking ? OUTSIDE_TAGS : CALL_TAGS;
118
- const hold = final ? 0 : partialSuffixOverlapAny(this.#buffer, tags);
119
- const emit = this.#buffer.slice(0, this.#buffer.length - hold);
120
- if (emit.length > 0) events.push({ type: "text", text: emit });
121
- this.#buffer = this.#buffer.slice(this.#buffer.length - hold);
122
- return false;
123
- }
124
-
125
- if (start > 0) {
126
- events.push({ type: "text", text: this.#buffer.slice(0, start) });
127
- this.#buffer = this.#buffer.slice(start);
128
- }
129
-
130
- if (isThink) {
131
- this.#buffer = this.#buffer.slice(THINK_SIGIL.length);
132
- this.#thinking = "";
133
- events.push({ type: "thinkingStart" });
134
- this.#state = "thinking";
135
- return true;
136
- }
137
-
138
- return this.#beginCall(events, final);
139
- }
140
-
141
- // Buffer starts with `§`. Resolve the tool name, then the header terminator.
142
- // Returns false to wait for more input (call still streaming in).
143
- #beginCall(events: InbandScanEvent[], final: boolean): boolean {
144
- const nameStart = CALL_SIGIL.length;
145
- if (nameStart >= this.#buffer.length && !final) return false; // just `§` so far
146
- let nameEnd = nameStart;
147
- if (!isNameStart(this.#buffer[nameEnd])) return this.#rejectSigil(events);
148
- nameEnd++;
149
- while (nameEnd < this.#buffer.length && isNameChar(this.#buffer[nameEnd])) nameEnd++;
150
- if (nameEnd >= this.#buffer.length && !final) return false; // name may continue
151
-
152
- const name = this.#buffer.slice(CALL_SIGIL.length, nameEnd);
153
- // Guard against `§` in prose: only claim a known tool when schemas exist.
154
- if (this.#knowsTools && !this.#argShapes.has(name)) return this.#rejectSigil(events);
155
-
156
- const header = findHeaderEnd(this.#buffer, nameEnd);
157
- if (header.kind === "fence") {
158
- let runEnd = header.index;
159
- while (runEnd < this.#buffer.length && this.#buffer[runEnd] === FENCE_OPEN) runEnd++;
160
- if (runEnd >= this.#buffer.length && !final) return false; // fence run may grow
161
- return this.#startCall(events, name, nameEnd, header.index, "fence", runEnd - header.index);
162
- }
163
- if (header.kind === "newline") {
164
- return this.#startCall(events, name, nameEnd, header.index, "newline", 0);
165
- }
166
- // "eof"/"incomplete": the header may still be streaming in — only a scalar
167
- // call with no trailing newline at true end-of-stream finalizes here.
168
- if (!final) return false;
169
- return this.#startCall(events, name, nameEnd, this.#buffer.length, "eof", 0);
170
- }
171
-
172
- // `§` not followed by a known tool name is prose — surface it as literal text.
173
- #rejectSigil(events: InbandScanEvent[]): boolean {
174
- events.push({ type: "text", text: CALL_SIGIL });
175
- this.#buffer = this.#buffer.slice(CALL_SIGIL.length);
176
- return true;
177
- }
178
-
179
- #startCall(
180
- events: InbandScanEvent[],
181
- name: string,
182
- argsStart: number,
183
- headerEnd: number,
184
- kind: "fence" | "newline" | "eof",
185
- fenceLen: number,
186
- ): boolean {
187
- const shape = this.#argShapes.get(name);
188
- this.#id = mintToolCallId();
189
- this.#name = name;
190
- this.#args = parseHeaderArgs(this.#buffer.slice(argsStart, headerEnd), shape?.properties ?? {});
191
- events.push({ type: "toolStart", id: this.#id, name: this.#name });
192
-
193
- if (kind === "fence") {
194
- const fenceEnd = headerEnd + fenceLen;
195
- this.#rawBlock = this.#buffer.slice(0, fenceEnd);
196
- this.#closeMarker = FENCE_CLOSE.repeat(fenceLen);
197
- this.#bodyKey = this.#inlineTargetKey() ?? "input";
198
- this.#bodyValue = "";
199
- this.#bodyLeading = true;
200
- this.#buffer = this.#buffer.slice(fenceEnd);
201
- this.#state = "body";
202
- return true;
203
- }
204
-
205
- this.#rawBlock = this.#buffer.slice(0, headerEnd);
206
- events.push({
207
- type: "toolEnd",
208
- id: this.#id,
209
- name: this.#name,
210
- arguments: this.#args,
211
- rawBlock: this.#rawBlock,
212
- });
213
- let next = headerEnd;
214
- if (kind === "newline") {
215
- if (this.#buffer[next] === "\r") next++;
216
- if (this.#buffer[next] === "\n") next++;
217
- }
218
- this.#buffer = this.#buffer.slice(next);
219
- this.#reset();
220
- return true;
221
- }
222
-
223
- #consumeBody(events: InbandScanEvent[], final: boolean): boolean {
224
- this.#stripBodyLeading(final);
225
- const close = this.#buffer.indexOf(this.#closeMarker);
226
- if (close === -1) {
227
- if (final) {
228
- this.#reset();
229
- this.#buffer = "";
230
- return false;
231
- }
232
- const overlap = partialSuffixOverlapAny(this.#buffer, [this.#closeMarker]);
233
- let hold = Math.max(this.#closeMarker.length, overlap);
234
- if (overlap > 0) {
235
- const beforeOverlap = this.#buffer.length - overlap - 1;
236
- if (this.#buffer[beforeOverlap] === "\n") {
237
- hold = Math.max(hold, overlap + 1);
238
- if (this.#buffer[beforeOverlap - 1] === "\r") hold = Math.max(hold, overlap + 2);
239
- }
240
- }
241
- const emitLength = this.#buffer.length - hold;
242
- if (emitLength > 0) {
243
- const delta = this.#buffer.slice(0, emitLength);
244
- this.#rawBlock += delta;
245
- this.#emitBodyDelta(delta, events);
246
- this.#buffer = this.#buffer.slice(emitLength);
247
- }
248
- return false;
249
- }
250
-
251
- const rawDelta = this.#buffer.slice(0, close);
252
- this.#rawBlock += rawDelta + this.#closeMarker;
253
- let delta = rawDelta;
254
- if (delta.endsWith("\r\n")) delta = delta.slice(0, -2);
255
- else if (delta.endsWith("\n")) delta = delta.slice(0, -1);
256
- this.#emitBodyDelta(delta, events);
257
- this.#args[this.#bodyKey] = this.#bodyValue;
258
- events.push({
259
- type: "toolEnd",
260
- id: this.#id,
261
- name: this.#name,
262
- arguments: this.#args,
263
- rawBlock: this.#rawBlock,
264
- });
265
- this.#buffer = this.#buffer.slice(close + this.#closeMarker.length);
266
- this.#reset();
267
- return true;
268
- }
269
-
270
- #stripBodyLeading(final: boolean): void {
271
- if (!this.#bodyLeading) return;
272
- if (this.#buffer.length === 0) return;
273
- if (this.#buffer[0] === "\r") {
274
- if (this.#buffer.length === 1 && !final) return;
275
- if (this.#buffer[1] === "\n") {
276
- this.#rawBlock += this.#buffer.slice(0, 2);
277
- this.#buffer = this.#buffer.slice(2);
278
- }
279
- this.#bodyLeading = false;
280
- return;
281
- }
282
- if (this.#buffer[0] === "\n") {
283
- this.#rawBlock += this.#buffer[0];
284
- this.#buffer = this.#buffer.slice(1);
285
- }
286
- this.#bodyLeading = false;
287
- }
288
-
289
- #emitBodyDelta(delta: string, events: InbandScanEvent[]): void {
290
- if (delta.length === 0) return;
291
- this.#bodyValue += delta;
292
- events.push({ type: "toolArgDelta", id: this.#id, name: this.#name, key: this.#bodyKey, delta });
293
- }
294
-
295
- #consumeThinking(events: InbandScanEvent[], final: boolean): boolean {
296
- const close = this.#buffer.indexOf(THINK_SIGIL);
297
- if (close === -1) {
298
- const hold = final ? 0 : partialSuffixOverlapAny(this.#buffer, [THINK_SIGIL]);
299
- this.#emitThinking(this.#buffer.slice(0, this.#buffer.length - hold), events);
300
- this.#buffer = this.#buffer.slice(this.#buffer.length - hold);
301
- if (final) {
302
- this.#endThinking(events);
303
- this.#state = "outside";
304
- }
305
- return false;
306
- }
307
- this.#emitThinking(this.#buffer.slice(0, close), events);
308
- this.#buffer = this.#buffer.slice(close + THINK_SIGIL.length);
309
- this.#endThinking(events);
310
- this.#state = "outside";
311
- return true;
312
- }
313
-
314
- #emitThinking(delta: string, events: InbandScanEvent[]): void {
315
- if (delta.length === 0) return;
316
- this.#thinking += delta;
317
- events.push({ type: "thinkingDelta", delta });
318
- }
319
-
320
- #endThinking(events: InbandScanEvent[]): void {
321
- events.push({ type: "thinkingEnd", thinking: this.#thinking });
322
- this.#thinking = "";
323
- this.#state = "outside";
324
- }
325
-
326
- #inlineTargetKey(): string | undefined {
327
- const shape = this.#argShapes.get(this.#name);
328
- if (shape) {
329
- for (const key of shape.parameterOrder) {
330
- if (Object.hasOwn(this.#args, key)) continue;
331
- return isStringOnlySchema(shape.properties[key]) ? key : undefined;
332
- }
333
- return undefined;
334
- }
335
- for (const key of this.#stringArgs(this.#name)) {
336
- if (!Object.hasOwn(this.#args, key)) return key;
337
- }
338
- return "input";
339
- }
340
-
341
- #reset(): void {
342
- this.#state = "outside";
343
- this.#id = "";
344
- this.#name = "";
345
- this.#args = {};
346
- this.#bodyKey = "";
347
- this.#bodyValue = "";
348
- this.#bodyLeading = false;
349
- this.#closeMarker = "";
350
- this.#rawBlock = "";
351
- }
352
- }
353
-
354
- function parseHeaderArgs(text: string, properties: Record<string, unknown>): Record<string, unknown> {
355
- const args: Record<string, unknown> = {};
356
- let index = skipWhitespace(text, 0);
357
- while (index < text.length) {
358
- if (!isNameStart(text[index])) {
359
- index++;
360
- continue;
361
- }
362
- const nameStart = index;
363
- index++;
364
- while (index < text.length && isNameChar(text[index])) index++;
365
- const key = text.slice(nameStart, index);
366
- index = skipWhitespace(text, index);
367
- if (text[index] !== "=") {
368
- args[key] = true;
369
- continue;
370
- }
371
- index = skipWhitespace(text, index + 1);
372
- const parsed = readInlineValue(text, index, properties[key]);
373
- args[key] = parsed.value;
374
- index = skipWhitespace(text, parsed.next);
375
- }
376
- return args;
377
- }
378
-
379
- type InlineValue = { value: unknown; next: number };
380
-
381
- function readInlineValue(text: string, start: number, schema: unknown): InlineValue {
382
- const ch = text[start];
383
- if (ch === '"') {
384
- let index = start + 1;
385
- while (index < text.length) {
386
- const c = text[index];
387
- if (c === "\\") {
388
- index += 2;
389
- continue;
390
- }
391
- if (c === '"') {
392
- index++;
393
- break;
394
- }
395
- index++;
396
- }
397
- const raw = text.slice(start, index);
398
- try {
399
- return { value: JSON.parse(raw) as unknown, next: index };
400
- } catch {
401
- return { value: raw.slice(1, raw.endsWith('"') ? -1 : undefined), next: index };
402
- }
403
- }
404
- if (ch === "[" || ch === "{") {
405
- const end = matchBracket(text, start);
406
- const raw = text.slice(start, end);
407
- try {
408
- return { value: JSON.parse(raw) as unknown, next: end };
409
- } catch {
410
- return { value: raw, next: end };
411
- }
412
- }
413
- let index = start;
414
- while (index < text.length && !isWhitespace(text[index])) index++;
415
- return { value: coerceValue(text.slice(start, index), schema), next: index };
416
- }
417
-
418
- function matchBracket(text: string, start: number): number {
419
- let depth = 0;
420
- let inString = false;
421
- for (let index = start; index < text.length; index++) {
422
- const ch = text[index];
423
- if (inString) {
424
- if (ch === "\\") {
425
- index++;
426
- continue;
427
- }
428
- if (ch === '"') inString = false;
429
- continue;
430
- }
431
- if (ch === '"') {
432
- inString = true;
433
- continue;
434
- }
435
- if (ch === "[" || ch === "{") depth++;
436
- else if (ch === "]" || ch === "}") {
437
- depth--;
438
- if (depth === 0) return index + 1;
439
- }
440
- }
441
- return text.length;
442
- }
443
-
444
- // Locate where a call header ends: the first top-level body fence, the first
445
- // literal newline (scalar-only call), end-of-input, or "incomplete" when a
446
- // quoted/bracketed value is still mid-stream.
447
- function findHeaderEnd(text: string, start: number): HeaderEnd {
448
- let inString = false;
449
- let depth = 0;
450
- for (let index = start; index < text.length; index++) {
451
- const ch = text[index];
452
- if (inString) {
453
- if (ch === "\\") {
454
- index++;
455
- continue;
456
- }
457
- if (ch === '"') inString = false;
458
- continue;
459
- }
460
- if (ch === '"') {
461
- inString = true;
462
- continue;
463
- }
464
- if (ch === "[" || ch === "{") {
465
- depth++;
466
- continue;
467
- }
468
- if (ch === "]" || ch === "}") {
469
- if (depth > 0) depth--;
470
- continue;
471
- }
472
- if (depth > 0) continue;
473
- if (ch === FENCE_OPEN) return { kind: "fence", index };
474
- if (ch === "\n" || ch === "\r") return { kind: "newline", index };
475
- }
476
- if (inString || depth > 0) return { kind: "incomplete" };
477
- return { kind: "eof", index: text.length };
478
- }
479
-
480
- function skipWhitespace(text: string, index: number): number {
481
- while (index < text.length && isWhitespace(text[index])) index++;
482
- return index;
483
- }
484
-
485
- function isWhitespace(ch: string | undefined): boolean {
486
- return ch === " " || ch === "\n" || ch === "\r" || ch === "\t" || ch === "\f";
487
- }
488
-
489
- function isNameStart(ch: string | undefined): boolean {
490
- return ch !== undefined && NAME_START.test(ch);
491
- }
492
-
493
- function isNameChar(ch: string | undefined): boolean {
494
- return ch !== undefined && NAME_CHAR.test(ch);
495
- }
496
-
497
- function renderToolCall(call: ToolCall, options: DialectRenderOptions = {}): string {
498
- return renderInvocation(call, buildArgShapes(options.tools).get(call.name));
499
- }
500
-
501
- function renderAssistantToolCalls(calls: readonly ToolCall[], options: DialectRenderOptions = {}): string {
502
- const shapes = buildArgShapes(options.tools);
503
- return calls.map(call => renderInvocation(call, shapes.get(call.name))).join("\n");
504
- }
505
-
506
- function renderInvocation(call: ToolCall, shape: ToolArgShape | undefined): string {
507
- const properties = shape?.properties ?? {};
508
- const bodyKey = selectBodyKey(call.arguments, shape);
509
- let header = `${CALL_SIGIL}${call.name}`;
510
- for (const key in call.arguments) {
511
- if (key === bodyKey) continue;
512
- header += ` ${key}=${renderInlineValue(call.arguments[key], properties[key])}`;
513
- }
514
- if (bodyKey === undefined) return header;
515
- const body = String(call.arguments[bodyKey]);
516
- const fence = 1 + maxRun(body, FENCE_CLOSE);
517
- return `${header}${FENCE_OPEN.repeat(fence)}\n${body}\n${FENCE_CLOSE.repeat(fence)}`;
518
- }
519
-
520
- // The body holds a single dominant string argument: the first string-only
521
- // parameter whose value contains a newline. Single-line strings stay inline
522
- // (quoted when needed) so the verbatim fence is reserved for genuine blocks.
523
- function selectBodyKey(args: Record<string, unknown>, shape: ToolArgShape | undefined): string | undefined {
524
- // Round-trip requires renderer and scanner to agree on the omitted body key.
525
- // The scanner assigns the body to the first string-only parameter missing from
526
- // the header, so a body is only safe when no earlier parameter is also absent
527
- // (no schema → keep everything inline).
528
- if (!shape) return undefined;
529
- for (const key of shape.parameterOrder) {
530
- if (!Object.hasOwn(args, key)) return undefined;
531
- const value = args[key];
532
- if (typeof value === "string" && value.includes("\n") && isStringOnlySchema(shape.properties[key])) return key;
533
- }
534
- return undefined;
535
- }
536
-
537
- function renderInlineValue(value: unknown, schema: unknown): string {
538
- if (typeof value === "string") {
539
- return needsQuote(value) ? JSON.stringify(value) : value;
540
- }
541
- if (isStringOnlySchema(schema) && value === null) return '""';
542
- return stringifyJson(value);
543
- }
544
-
545
- function needsQuote(value: string): boolean {
546
- if (value.length === 0) return true;
547
- const first = value[0];
548
- if (first === '"' || first === "[" || first === "{") return true;
549
- return /[\s«»]/.test(value);
550
- }
551
-
552
- function maxRun(text: string, ch: string): number {
553
- let best = 0;
554
- let run = 0;
555
- for (let index = 0; index < text.length; index++) {
556
- if (text[index] === ch) {
557
- run++;
558
- if (run > best) best = run;
559
- } else {
560
- run = 0;
561
- }
562
- }
563
- return best;
564
- }
565
-
566
- function renderToolResults(results: readonly DialectToolResult[], _options?: DialectRenderOptions): string {
567
- return results
568
- .map(result => {
569
- const fence = RESULT_FENCE.repeat(Math.max(2, 1 + maxRun(result.text, RESULT_FENCE)));
570
- return `${fence}\n${result.text}\n${fence}`;
571
- })
572
- .join("\n");
573
- }
574
-
575
- function renderThinking(text: string): string {
576
- if (!text) return "";
577
- return `${THINK_SIGIL}\n${text}\n${THINK_SIGIL}`;
578
- }
579
-
580
- function renderTranscript(messages: readonly Message[], options: DialectRenderOptions = {}): string {
581
- return renderChatMlTranscript(messages, options, {
582
- toolResultRole: "tool",
583
- renderThinking,
584
- renderCalls: renderAssistantToolCalls,
585
- renderResultsBody: renderToolResults,
586
- });
587
- }
588
-
589
- const definition: DialectDefinition = {
590
- dialect: "pi",
591
- prompt: dialectPrompt,
592
- createScanner: options => new PiNativeInbandScanner(options),
593
- renderToolCall,
594
- renderAssistantToolCalls,
595
- renderToolResults,
596
- renderThinking,
597
- renderTranscript,
598
- };
599
-
600
- export default definition;
package/src/errors.ts DELETED
@@ -1,32 +0,0 @@
1
- /**
2
- * Structured HTTP errors thrown by provider clients.
3
- *
4
- * Downstream classification reads these fields structurally rather than via
5
- * `instanceof`: `extractHttpStatusFromError` (pi-utils) reads `status`,
6
- * `getHeadersFromError` (retry-after extraction) reads `headers`, and retry
7
- * policies such as `isCopilotTransientModelError` read `code`. Per-provider
8
- * subclasses exist so call sites can narrow with `instanceof` and logs carry
9
- * a meaningful `error.name`.
10
- */
11
- export interface ProviderHttpErrorOptions {
12
- /** Response headers; enables `retry-after`/rate-limit extraction downstream. */
13
- headers?: Headers;
14
- /** Machine-readable error code from the response body (`error.code` / `error.type`). */
15
- code?: string;
16
- cause?: unknown;
17
- }
18
-
19
- /** Non-2xx HTTP response from a provider endpoint. */
20
- export class ProviderHttpError extends Error {
21
- readonly status: number;
22
- readonly headers: Headers | undefined;
23
- readonly code: string | undefined;
24
-
25
- constructor(message: string, status: number, options?: ProviderHttpErrorOptions) {
26
- super(message, options?.cause === undefined ? undefined : { cause: options.cause });
27
- this.name = "ProviderHttpError";
28
- this.status = status;
29
- this.headers = options?.headers;
30
- this.code = options?.code;
31
- }
32
- }