@lenylvt/pi-ai 0.64.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 (167) hide show
  1. package/README.md +203 -0
  2. package/dist/api-registry.d.ts +20 -0
  3. package/dist/api-registry.d.ts.map +1 -0
  4. package/dist/api-registry.js +44 -0
  5. package/dist/api-registry.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +119 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/env-api-keys.d.ts +7 -0
  11. package/dist/env-api-keys.d.ts.map +1 -0
  12. package/dist/env-api-keys.js +13 -0
  13. package/dist/env-api-keys.js.map +1 -0
  14. package/dist/index.d.ts +20 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +14 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/models.d.ts +24 -0
  19. package/dist/models.d.ts.map +1 -0
  20. package/dist/models.generated.d.ts +2332 -0
  21. package/dist/models.generated.d.ts.map +1 -0
  22. package/dist/models.generated.js +2186 -0
  23. package/dist/models.generated.js.map +1 -0
  24. package/dist/models.js +60 -0
  25. package/dist/models.js.map +1 -0
  26. package/dist/oauth.d.ts +2 -0
  27. package/dist/oauth.d.ts.map +1 -0
  28. package/dist/oauth.js +2 -0
  29. package/dist/oauth.js.map +1 -0
  30. package/dist/providers/anthropic.d.ts +40 -0
  31. package/dist/providers/anthropic.d.ts.map +1 -0
  32. package/dist/providers/anthropic.js +749 -0
  33. package/dist/providers/anthropic.js.map +1 -0
  34. package/dist/providers/faux.d.ts +56 -0
  35. package/dist/providers/faux.d.ts.map +1 -0
  36. package/dist/providers/faux.js +367 -0
  37. package/dist/providers/faux.js.map +1 -0
  38. package/dist/providers/github-copilot-headers.d.ts +8 -0
  39. package/dist/providers/github-copilot-headers.d.ts.map +1 -0
  40. package/dist/providers/github-copilot-headers.js +29 -0
  41. package/dist/providers/github-copilot-headers.js.map +1 -0
  42. package/dist/providers/openai-codex-responses.d.ts +9 -0
  43. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  44. package/dist/providers/openai-codex-responses.js +741 -0
  45. package/dist/providers/openai-codex-responses.js.map +1 -0
  46. package/dist/providers/openai-completions.d.ts +15 -0
  47. package/dist/providers/openai-completions.d.ts.map +1 -0
  48. package/dist/providers/openai-completions.js +687 -0
  49. package/dist/providers/openai-completions.js.map +1 -0
  50. package/dist/providers/openai-responses-shared.d.ts +17 -0
  51. package/dist/providers/openai-responses-shared.d.ts.map +1 -0
  52. package/dist/providers/openai-responses-shared.js +458 -0
  53. package/dist/providers/openai-responses-shared.js.map +1 -0
  54. package/dist/providers/openai-responses.d.ts +13 -0
  55. package/dist/providers/openai-responses.d.ts.map +1 -0
  56. package/dist/providers/openai-responses.js +190 -0
  57. package/dist/providers/openai-responses.js.map +1 -0
  58. package/dist/providers/register-builtins.d.ts +16 -0
  59. package/dist/providers/register-builtins.d.ts.map +1 -0
  60. package/dist/providers/register-builtins.js +140 -0
  61. package/dist/providers/register-builtins.js.map +1 -0
  62. package/dist/providers/simple-options.d.ts +8 -0
  63. package/dist/providers/simple-options.d.ts.map +1 -0
  64. package/dist/providers/simple-options.js +35 -0
  65. package/dist/providers/simple-options.js.map +1 -0
  66. package/dist/providers/transform-messages.d.ts +8 -0
  67. package/dist/providers/transform-messages.d.ts.map +1 -0
  68. package/dist/providers/transform-messages.js +155 -0
  69. package/dist/providers/transform-messages.js.map +1 -0
  70. package/dist/stream.d.ts +8 -0
  71. package/dist/stream.d.ts.map +1 -0
  72. package/dist/stream.js +27 -0
  73. package/dist/stream.js.map +1 -0
  74. package/dist/types.d.ts +283 -0
  75. package/dist/types.d.ts.map +1 -0
  76. package/dist/types.js +2 -0
  77. package/dist/types.js.map +1 -0
  78. package/dist/utils/event-stream.d.ts +21 -0
  79. package/dist/utils/event-stream.d.ts.map +1 -0
  80. package/dist/utils/event-stream.js +81 -0
  81. package/dist/utils/event-stream.js.map +1 -0
  82. package/dist/utils/hash.d.ts +3 -0
  83. package/dist/utils/hash.d.ts.map +1 -0
  84. package/dist/utils/hash.js +14 -0
  85. package/dist/utils/hash.js.map +1 -0
  86. package/dist/utils/json-parse.d.ts +9 -0
  87. package/dist/utils/json-parse.d.ts.map +1 -0
  88. package/dist/utils/json-parse.js +29 -0
  89. package/dist/utils/json-parse.js.map +1 -0
  90. package/dist/utils/oauth/anthropic.d.ts +25 -0
  91. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  92. package/dist/utils/oauth/anthropic.js +335 -0
  93. package/dist/utils/oauth/anthropic.js.map +1 -0
  94. package/dist/utils/oauth/github-copilot.d.ts +30 -0
  95. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  96. package/dist/utils/oauth/github-copilot.js +292 -0
  97. package/dist/utils/oauth/github-copilot.js.map +1 -0
  98. package/dist/utils/oauth/index.d.ts +36 -0
  99. package/dist/utils/oauth/index.d.ts.map +1 -0
  100. package/dist/utils/oauth/index.js +92 -0
  101. package/dist/utils/oauth/index.js.map +1 -0
  102. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  103. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  104. package/dist/utils/oauth/oauth-page.js +105 -0
  105. package/dist/utils/oauth/oauth-page.js.map +1 -0
  106. package/dist/utils/oauth/openai-codex.d.ts +34 -0
  107. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  108. package/dist/utils/oauth/openai-codex.js +373 -0
  109. package/dist/utils/oauth/openai-codex.js.map +1 -0
  110. package/dist/utils/oauth/pkce.d.ts +13 -0
  111. package/dist/utils/oauth/pkce.d.ts.map +1 -0
  112. package/dist/utils/oauth/pkce.js +31 -0
  113. package/dist/utils/oauth/pkce.js.map +1 -0
  114. package/dist/utils/oauth/types.d.ts +47 -0
  115. package/dist/utils/oauth/types.d.ts.map +1 -0
  116. package/dist/utils/oauth/types.js +2 -0
  117. package/dist/utils/oauth/types.js.map +1 -0
  118. package/dist/utils/overflow.d.ts +53 -0
  119. package/dist/utils/overflow.d.ts.map +1 -0
  120. package/dist/utils/overflow.js +119 -0
  121. package/dist/utils/overflow.js.map +1 -0
  122. package/dist/utils/sanitize-unicode.d.ts +22 -0
  123. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  124. package/dist/utils/sanitize-unicode.js +26 -0
  125. package/dist/utils/sanitize-unicode.js.map +1 -0
  126. package/dist/utils/typebox-helpers.d.ts +17 -0
  127. package/dist/utils/typebox-helpers.d.ts.map +1 -0
  128. package/dist/utils/typebox-helpers.js +21 -0
  129. package/dist/utils/typebox-helpers.js.map +1 -0
  130. package/dist/utils/validation.d.ts +18 -0
  131. package/dist/utils/validation.d.ts.map +1 -0
  132. package/dist/utils/validation.js +80 -0
  133. package/dist/utils/validation.js.map +1 -0
  134. package/package.json +89 -0
  135. package/src/api-registry.ts +98 -0
  136. package/src/cli.ts +136 -0
  137. package/src/env-api-keys.ts +22 -0
  138. package/src/index.ts +29 -0
  139. package/src/models.generated.ts +2188 -0
  140. package/src/models.ts +82 -0
  141. package/src/oauth.ts +1 -0
  142. package/src/providers/anthropic.ts +905 -0
  143. package/src/providers/faux.ts +498 -0
  144. package/src/providers/github-copilot-headers.ts +37 -0
  145. package/src/providers/openai-codex-responses.ts +929 -0
  146. package/src/providers/openai-completions.ts +811 -0
  147. package/src/providers/openai-responses-shared.ts +513 -0
  148. package/src/providers/openai-responses.ts +251 -0
  149. package/src/providers/register-builtins.ts +232 -0
  150. package/src/providers/simple-options.ts +46 -0
  151. package/src/providers/transform-messages.ts +172 -0
  152. package/src/stream.ts +59 -0
  153. package/src/types.ts +294 -0
  154. package/src/utils/event-stream.ts +87 -0
  155. package/src/utils/hash.ts +13 -0
  156. package/src/utils/json-parse.ts +28 -0
  157. package/src/utils/oauth/anthropic.ts +402 -0
  158. package/src/utils/oauth/github-copilot.ts +396 -0
  159. package/src/utils/oauth/index.ts +123 -0
  160. package/src/utils/oauth/oauth-page.ts +109 -0
  161. package/src/utils/oauth/openai-codex.ts +450 -0
  162. package/src/utils/oauth/pkce.ts +34 -0
  163. package/src/utils/oauth/types.ts +59 -0
  164. package/src/utils/overflow.ts +125 -0
  165. package/src/utils/sanitize-unicode.ts +25 -0
  166. package/src/utils/typebox-helpers.ts +24 -0
  167. package/src/utils/validation.ts +93 -0
package/src/stream.ts ADDED
@@ -0,0 +1,59 @@
1
+ import "./providers/register-builtins.js";
2
+
3
+ import { getApiProvider } from "./api-registry.js";
4
+ import type {
5
+ Api,
6
+ AssistantMessage,
7
+ AssistantMessageEventStream,
8
+ Context,
9
+ Model,
10
+ ProviderStreamOptions,
11
+ SimpleStreamOptions,
12
+ StreamOptions,
13
+ } from "./types.js";
14
+
15
+ export { getEnvApiKey } from "./env-api-keys.js";
16
+
17
+ function resolveApiProvider(api: Api) {
18
+ const provider = getApiProvider(api);
19
+ if (!provider) {
20
+ throw new Error(`No API provider registered for api: ${api}`);
21
+ }
22
+ return provider;
23
+ }
24
+
25
+ export function stream<TApi extends Api>(
26
+ model: Model<TApi>,
27
+ context: Context,
28
+ options?: ProviderStreamOptions,
29
+ ): AssistantMessageEventStream {
30
+ const provider = resolveApiProvider(model.api);
31
+ return provider.stream(model, context, options as StreamOptions);
32
+ }
33
+
34
+ export async function complete<TApi extends Api>(
35
+ model: Model<TApi>,
36
+ context: Context,
37
+ options?: ProviderStreamOptions,
38
+ ): Promise<AssistantMessage> {
39
+ const s = stream(model, context, options);
40
+ return s.result();
41
+ }
42
+
43
+ export function streamSimple<TApi extends Api>(
44
+ model: Model<TApi>,
45
+ context: Context,
46
+ options?: SimpleStreamOptions,
47
+ ): AssistantMessageEventStream {
48
+ const provider = resolveApiProvider(model.api);
49
+ return provider.streamSimple(model, context, options);
50
+ }
51
+
52
+ export async function completeSimple<TApi extends Api>(
53
+ model: Model<TApi>,
54
+ context: Context,
55
+ options?: SimpleStreamOptions,
56
+ ): Promise<AssistantMessage> {
57
+ const s = streamSimple(model, context, options);
58
+ return s.result();
59
+ }
package/src/types.ts ADDED
@@ -0,0 +1,294 @@
1
+ import type { AssistantMessageEventStream } from "./utils/event-stream.js";
2
+
3
+ export type { AssistantMessageEventStream } from "./utils/event-stream.js";
4
+
5
+ export const SUPPORTED_PROVIDERS = ["anthropic", "github-copilot", "openai-codex", "openrouter"] as const;
6
+
7
+ export type SupportedProvider = (typeof SUPPORTED_PROVIDERS)[number];
8
+
9
+ export type KnownApi = "openai-completions" | "openai-responses" | "openai-codex-responses" | "anthropic-messages";
10
+
11
+ export type Api = KnownApi | (string & {});
12
+
13
+ export type KnownProvider = "anthropic" | "openai-codex" | "github-copilot" | "openrouter";
14
+ export type Provider = KnownProvider | string;
15
+
16
+ export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
17
+
18
+ /** Token budgets for each thinking level (token-based providers only) */
19
+ export interface ThinkingBudgets {
20
+ minimal?: number;
21
+ low?: number;
22
+ medium?: number;
23
+ high?: number;
24
+ }
25
+
26
+ // Base options all providers share
27
+ export type CacheRetention = "none" | "short" | "long";
28
+
29
+ export type Transport = "sse" | "websocket" | "auto";
30
+
31
+ export interface StreamOptions {
32
+ temperature?: number;
33
+ maxTokens?: number;
34
+ signal?: AbortSignal;
35
+ apiKey?: string;
36
+ /**
37
+ * Preferred transport for providers that support multiple transports.
38
+ * Providers that do not support this option ignore it.
39
+ */
40
+ transport?: Transport;
41
+ /**
42
+ * Prompt cache retention preference. Providers map this to their supported values.
43
+ * Default: "short".
44
+ */
45
+ cacheRetention?: CacheRetention;
46
+ /**
47
+ * Optional session identifier for providers that support session-based caching.
48
+ * Providers can use this to enable prompt caching, request routing, or other
49
+ * session-aware features. Ignored by providers that don't support it.
50
+ */
51
+ sessionId?: string;
52
+ /**
53
+ * Optional callback for inspecting or replacing provider payloads before sending.
54
+ * Return undefined to keep the payload unchanged.
55
+ */
56
+ onPayload?: (payload: unknown, model: Model<Api>) => unknown | undefined | Promise<unknown | undefined>;
57
+ /**
58
+ * Optional custom HTTP headers to include in API requests.
59
+ * Merged with provider defaults; can override default headers.
60
+ * Not supported by all providers (e.g., AWS Bedrock uses SDK auth).
61
+ */
62
+ headers?: Record<string, string>;
63
+ /**
64
+ * Maximum delay in milliseconds to wait for a retry when the server requests a long wait.
65
+ * If the server's requested delay exceeds this value, the request fails immediately
66
+ * with an error containing the requested delay, allowing higher-level retry logic
67
+ * to handle it with user visibility.
68
+ * Default: 60000 (60 seconds). Set to 0 to disable the cap.
69
+ */
70
+ maxRetryDelayMs?: number;
71
+ /**
72
+ * Optional metadata to include in API requests.
73
+ * Providers extract the fields they understand and ignore the rest.
74
+ * For example, Anthropic uses `user_id` for abuse tracking and rate limiting.
75
+ */
76
+ metadata?: Record<string, unknown>;
77
+ }
78
+
79
+ export type ProviderStreamOptions = StreamOptions & Record<string, unknown>;
80
+
81
+ // Unified options with reasoning passed to streamSimple() and completeSimple()
82
+ export interface SimpleStreamOptions extends StreamOptions {
83
+ reasoning?: ThinkingLevel;
84
+ /** Custom token budgets for thinking levels (token-based providers only) */
85
+ thinkingBudgets?: ThinkingBudgets;
86
+ }
87
+
88
+ // Generic StreamFunction with typed options.
89
+ //
90
+ // Contract:
91
+ // - Must return an AssistantMessageEventStream.
92
+ // - Once invoked, request/model/runtime failures should be encoded in the
93
+ // returned stream, not thrown.
94
+ // - Error termination must produce an AssistantMessage with stopReason
95
+ // "error" or "aborted" and errorMessage, emitted via the stream protocol.
96
+ export type StreamFunction<TApi extends Api = Api, TOptions extends StreamOptions = StreamOptions> = (
97
+ model: Model<TApi>,
98
+ context: Context,
99
+ options?: TOptions,
100
+ ) => AssistantMessageEventStream;
101
+
102
+ export interface TextSignatureV1 {
103
+ v: 1;
104
+ id: string;
105
+ phase?: "commentary" | "final_answer";
106
+ }
107
+
108
+ export interface TextContent {
109
+ type: "text";
110
+ text: string;
111
+ textSignature?: string; // e.g., for OpenAI responses, message metadata (legacy id string or TextSignatureV1 JSON)
112
+ }
113
+
114
+ export interface ThinkingContent {
115
+ type: "thinking";
116
+ thinking: string;
117
+ thinkingSignature?: string; // e.g., for OpenAI responses, the reasoning item ID
118
+ /** When true, the thinking content was redacted by safety filters. The opaque
119
+ * encrypted payload is stored in `thinkingSignature` so it can be passed back
120
+ * to the API for multi-turn continuity. */
121
+ redacted?: boolean;
122
+ }
123
+
124
+ export interface ImageContent {
125
+ type: "image";
126
+ data: string; // base64 encoded image data
127
+ mimeType: string; // e.g., "image/jpeg", "image/png"
128
+ }
129
+
130
+ export interface ToolCall {
131
+ type: "toolCall";
132
+ id: string;
133
+ name: string;
134
+ arguments: Record<string, any>;
135
+ thoughtSignature?: string; // Google-specific: opaque signature for reusing thought context
136
+ }
137
+
138
+ export interface Usage {
139
+ input: number;
140
+ output: number;
141
+ cacheRead: number;
142
+ cacheWrite: number;
143
+ totalTokens: number;
144
+ cost: {
145
+ input: number;
146
+ output: number;
147
+ cacheRead: number;
148
+ cacheWrite: number;
149
+ total: number;
150
+ };
151
+ }
152
+
153
+ export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
154
+
155
+ export interface UserMessage {
156
+ role: "user";
157
+ content: string | (TextContent | ImageContent)[];
158
+ timestamp: number; // Unix timestamp in milliseconds
159
+ }
160
+
161
+ export interface AssistantMessage {
162
+ role: "assistant";
163
+ content: (TextContent | ThinkingContent | ToolCall)[];
164
+ api: Api;
165
+ provider: Provider;
166
+ model: string;
167
+ responseId?: string; // Provider-specific response/message identifier when the upstream API exposes one
168
+ usage: Usage;
169
+ stopReason: StopReason;
170
+ errorMessage?: string;
171
+ timestamp: number; // Unix timestamp in milliseconds
172
+ }
173
+
174
+ export interface ToolResultMessage<TDetails = any> {
175
+ role: "toolResult";
176
+ toolCallId: string;
177
+ toolName: string;
178
+ content: (TextContent | ImageContent)[]; // Supports text and images
179
+ details?: TDetails;
180
+ isError: boolean;
181
+ timestamp: number; // Unix timestamp in milliseconds
182
+ }
183
+
184
+ export type Message = UserMessage | AssistantMessage | ToolResultMessage;
185
+
186
+ import type { TSchema } from "@sinclair/typebox";
187
+
188
+ export interface Tool<TParameters extends TSchema = TSchema> {
189
+ name: string;
190
+ description: string;
191
+ parameters: TParameters;
192
+ }
193
+
194
+ export interface Context {
195
+ systemPrompt?: string;
196
+ messages: Message[];
197
+ tools?: Tool[];
198
+ }
199
+
200
+ /**
201
+ * Event protocol for AssistantMessageEventStream.
202
+ *
203
+ * Streams should emit `start` before partial updates, then terminate with either:
204
+ * - `done` carrying the final successful AssistantMessage, or
205
+ * - `error` carrying the final AssistantMessage with stopReason "error" or "aborted"
206
+ * and errorMessage.
207
+ */
208
+ export type AssistantMessageEvent =
209
+ | { type: "start"; partial: AssistantMessage }
210
+ | { type: "text_start"; contentIndex: number; partial: AssistantMessage }
211
+ | { type: "text_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
212
+ | { type: "text_end"; contentIndex: number; content: string; partial: AssistantMessage }
213
+ | { type: "thinking_start"; contentIndex: number; partial: AssistantMessage }
214
+ | { type: "thinking_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
215
+ | { type: "thinking_end"; contentIndex: number; content: string; partial: AssistantMessage }
216
+ | { type: "toolcall_start"; contentIndex: number; partial: AssistantMessage }
217
+ | { type: "toolcall_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
218
+ | { type: "toolcall_end"; contentIndex: number; toolCall: ToolCall; partial: AssistantMessage }
219
+ | { type: "done"; reason: Extract<StopReason, "stop" | "length" | "toolUse">; message: AssistantMessage }
220
+ | { type: "error"; reason: Extract<StopReason, "aborted" | "error">; error: AssistantMessage };
221
+
222
+ /**
223
+ * Compatibility settings for OpenAI-compatible completions APIs.
224
+ * Use this to override URL-based auto-detection for custom providers.
225
+ */
226
+ export interface OpenAICompletionsCompat {
227
+ /** Whether the provider supports the `store` field. Default: auto-detected from URL. */
228
+ supportsStore?: boolean;
229
+ /** Whether the provider supports the `developer` role (vs `system`). Default: auto-detected from URL. */
230
+ supportsDeveloperRole?: boolean;
231
+ /** Whether the provider supports `reasoning_effort`. Default: auto-detected from URL. */
232
+ supportsReasoningEffort?: boolean;
233
+ /** Optional mapping from pi-ai reasoning levels to provider/model-specific `reasoning_effort` values. */
234
+ reasoningEffortMap?: Partial<Record<ThinkingLevel, string>>;
235
+ /** Whether the provider supports `stream_options: { include_usage: true }` for token usage in streaming responses. Default: true. */
236
+ supportsUsageInStreaming?: boolean;
237
+ /** Which field to use for max tokens. Default: auto-detected from URL. */
238
+ maxTokensField?: "max_completion_tokens" | "max_tokens";
239
+ /** Whether tool results require the `name` field. Default: auto-detected from URL. */
240
+ requiresToolResultName?: boolean;
241
+ /** Whether a user message after tool results requires an assistant message in between. Default: auto-detected from URL. */
242
+ requiresAssistantAfterToolResult?: boolean;
243
+ /** Whether thinking blocks must be converted to text blocks with <thinking> delimiters. Default: auto-detected from URL. */
244
+ requiresThinkingAsText?: boolean;
245
+ /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort and "openrouter" uses reasoning: { effort }. Default: "openai". */
246
+ thinkingFormat?: "openai" | "openrouter";
247
+ /** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */
248
+ openRouterRouting?: OpenRouterRouting;
249
+ /** Whether the provider supports the `strict` field in tool definitions. Default: true. */
250
+ supportsStrictMode?: boolean;
251
+ }
252
+
253
+ /** Compatibility settings for OpenAI Responses APIs. */
254
+ export interface OpenAIResponsesCompat {
255
+ // Reserved for future use
256
+ }
257
+
258
+ /**
259
+ * OpenRouter provider routing preferences.
260
+ * Controls which upstream providers OpenRouter routes requests to.
261
+ * @see https://openrouter.ai/docs/provider-routing
262
+ */
263
+ export interface OpenRouterRouting {
264
+ /** List of provider slugs to exclusively use for this request (e.g., ["anthropic"]). */
265
+ only?: string[];
266
+ /** List of provider slugs to try in order (e.g., ["anthropic", "openrouter"]). */
267
+ order?: string[];
268
+ }
269
+
270
+ // Model interface for the unified model system
271
+ export interface Model<TApi extends Api> {
272
+ id: string;
273
+ name: string;
274
+ api: TApi;
275
+ provider: Provider;
276
+ baseUrl: string;
277
+ reasoning: boolean;
278
+ input: ("text" | "image")[];
279
+ cost: {
280
+ input: number; // $/million tokens
281
+ output: number; // $/million tokens
282
+ cacheRead: number; // $/million tokens
283
+ cacheWrite: number; // $/million tokens
284
+ };
285
+ contextWindow: number;
286
+ maxTokens: number;
287
+ headers?: Record<string, string>;
288
+ /** Compatibility overrides for OpenAI-compatible APIs. If not set, auto-detected from baseUrl. */
289
+ compat?: TApi extends "openai-completions"
290
+ ? OpenAICompletionsCompat
291
+ : TApi extends "openai-responses"
292
+ ? OpenAIResponsesCompat
293
+ : never;
294
+ }
@@ -0,0 +1,87 @@
1
+ import type { AssistantMessage, AssistantMessageEvent } from "../types.js";
2
+
3
+ // Generic event stream class for async iteration
4
+ export class EventStream<T, R = T> implements AsyncIterable<T> {
5
+ private queue: T[] = [];
6
+ private waiting: ((value: IteratorResult<T>) => void)[] = [];
7
+ private done = false;
8
+ private finalResultPromise: Promise<R>;
9
+ private resolveFinalResult!: (result: R) => void;
10
+
11
+ constructor(
12
+ private isComplete: (event: T) => boolean,
13
+ private extractResult: (event: T) => R,
14
+ ) {
15
+ this.finalResultPromise = new Promise((resolve) => {
16
+ this.resolveFinalResult = resolve;
17
+ });
18
+ }
19
+
20
+ push(event: T): void {
21
+ if (this.done) return;
22
+
23
+ if (this.isComplete(event)) {
24
+ this.done = true;
25
+ this.resolveFinalResult(this.extractResult(event));
26
+ }
27
+
28
+ // Deliver to waiting consumer or queue it
29
+ const waiter = this.waiting.shift();
30
+ if (waiter) {
31
+ waiter({ value: event, done: false });
32
+ } else {
33
+ this.queue.push(event);
34
+ }
35
+ }
36
+
37
+ end(result?: R): void {
38
+ this.done = true;
39
+ if (result !== undefined) {
40
+ this.resolveFinalResult(result);
41
+ }
42
+ // Notify all waiting consumers that we're done
43
+ while (this.waiting.length > 0) {
44
+ const waiter = this.waiting.shift()!;
45
+ waiter({ value: undefined as any, done: true });
46
+ }
47
+ }
48
+
49
+ async *[Symbol.asyncIterator](): AsyncIterator<T> {
50
+ while (true) {
51
+ if (this.queue.length > 0) {
52
+ yield this.queue.shift()!;
53
+ } else if (this.done) {
54
+ return;
55
+ } else {
56
+ const result = await new Promise<IteratorResult<T>>((resolve) => this.waiting.push(resolve));
57
+ if (result.done) return;
58
+ yield result.value;
59
+ }
60
+ }
61
+ }
62
+
63
+ result(): Promise<R> {
64
+ return this.finalResultPromise;
65
+ }
66
+ }
67
+
68
+ export class AssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
69
+ constructor() {
70
+ super(
71
+ (event) => event.type === "done" || event.type === "error",
72
+ (event) => {
73
+ if (event.type === "done") {
74
+ return event.message;
75
+ } else if (event.type === "error") {
76
+ return event.error;
77
+ }
78
+ throw new Error("Unexpected event type for final result");
79
+ },
80
+ );
81
+ }
82
+ }
83
+
84
+ /** Factory function for AssistantMessageEventStream (for use in extensions) */
85
+ export function createAssistantMessageEventStream(): AssistantMessageEventStream {
86
+ return new AssistantMessageEventStream();
87
+ }
@@ -0,0 +1,13 @@
1
+ /** Fast deterministic hash to shorten long strings */
2
+ export function shortHash(str: string): string {
3
+ let h1 = 0xdeadbeef;
4
+ let h2 = 0x41c6ce57;
5
+ for (let i = 0; i < str.length; i++) {
6
+ const ch = str.charCodeAt(i);
7
+ h1 = Math.imul(h1 ^ ch, 2654435761);
8
+ h2 = Math.imul(h2 ^ ch, 1597334677);
9
+ }
10
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
11
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
12
+ return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
13
+ }
@@ -0,0 +1,28 @@
1
+ import { parse as partialParse } from "partial-json";
2
+
3
+ /**
4
+ * Attempts to parse potentially incomplete JSON during streaming.
5
+ * Always returns a valid object, even if the JSON is incomplete.
6
+ *
7
+ * @param partialJson The partial JSON string from streaming
8
+ * @returns Parsed object or empty object if parsing fails
9
+ */
10
+ export function parseStreamingJson<T = any>(partialJson: string | undefined): T {
11
+ if (!partialJson || partialJson.trim() === "") {
12
+ return {} as T;
13
+ }
14
+
15
+ // Try standard parsing first (fastest for complete JSON)
16
+ try {
17
+ return JSON.parse(partialJson) as T;
18
+ } catch {
19
+ // Try partial-json for incomplete JSON
20
+ try {
21
+ const result = partialParse(partialJson);
22
+ return (result ?? {}) as T;
23
+ } catch {
24
+ // If all parsing fails, return empty object
25
+ return {} as T;
26
+ }
27
+ }
28
+ }