@skydiveai/pi-server 0.1.0-beta.165 → 0.1.0-beta.2448

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -208,30 +208,7 @@ declare function createPrewarm({
208
208
  */
209
209
  declare const KNOWN_PI_PROVIDERS: readonly ["amazon-bedrock", "anthropic", "google", "google-vertex", "openai", "azure-openai-responses", "openai-codex", "deepseek", "github-copilot", "xai", "groq", "cerebras", "openrouter", "vercel-ai-gateway", "zai", "mistral", "minimax", "minimax-cn", "moonshotai", "moonshotai-cn", "huggingface", "fireworks", "opencode", "opencode-go", "kimi-coding", "cloudflare-workers-ai", "cloudflare-ai-gateway", "xiaomi", "xiaomi-token-plan-cn", "xiaomi-token-plan-ams", "xiaomi-token-plan-sgp"];
210
210
  declare const modelSpecSchema: z.ZodDiscriminatedUnion<"api", [z.ZodObject<{
211
- api: z.ZodLiteral<"openai-completions">;
212
- compat: z.ZodOptional<z.ZodObject<{
213
- supportsReasoningEffort: z.ZodOptional<z.ZodBoolean>;
214
- requiresThinkingAsText: z.ZodOptional<z.ZodBoolean>;
215
- thinkingFormat: z.ZodOptional<z.ZodEnum<["openai", "openrouter", "deepseek", "zai", "qwen", "qwen-chat-template"]>>;
216
- supportsStrictMode: z.ZodOptional<z.ZodBoolean>;
217
- maxTokensField: z.ZodOptional<z.ZodEnum<["max_completion_tokens", "max_tokens"]>>;
218
- cacheControlFormat: z.ZodOptional<z.ZodLiteral<"anthropic">>;
219
- }, "strip", z.ZodTypeAny, {
220
- supportsReasoningEffort?: boolean | undefined;
221
- requiresThinkingAsText?: boolean | undefined;
222
- thinkingFormat?: "openai" | "deepseek" | "openrouter" | "zai" | "qwen" | "qwen-chat-template" | undefined;
223
- supportsStrictMode?: boolean | undefined;
224
- maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
225
- cacheControlFormat?: "anthropic" | undefined;
226
- }, {
227
- supportsReasoningEffort?: boolean | undefined;
228
- requiresThinkingAsText?: boolean | undefined;
229
- thinkingFormat?: "openai" | "deepseek" | "openrouter" | "zai" | "qwen" | "qwen-chat-template" | undefined;
230
- supportsStrictMode?: boolean | undefined;
231
- maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
232
- cacheControlFormat?: "anthropic" | undefined;
233
- }>>; /** Exact model id the serving endpoint expects. */
234
- id: z.ZodString; /** Display name shown in pi surfaces and session logs. */
211
+ /** Exact model id the serving endpoint expects. */id: z.ZodString; /** Display name shown in pi surfaces and session logs. */
235
212
  name: z.ZodString; /** pi-ai provider key — also the auth-storage key for `apiKey`. */
236
213
  provider: z.ZodEnum<["amazon-bedrock", "anthropic", "google", "google-vertex", "openai", "azure-openai-responses", "openai-codex", "deepseek", "github-copilot", "xai", "groq", "cerebras", "openrouter", "vercel-ai-gateway", "zai", "mistral", "minimax", "minimax-cn", "moonshotai", "moonshotai-cn", "huggingface", "fireworks", "opencode", "opencode-go", "kimi-coding", "cloudflare-workers-ai", "cloudflare-ai-gateway", "xiaomi", "xiaomi-token-plan-cn", "xiaomi-token-plan-ams", "xiaomi-token-plan-sgp"]>; /** Provider API root, e.g. "https://openrouter.ai/api/v1". */
237
214
  baseUrl: z.ZodString;
@@ -244,17 +221,17 @@ declare const modelSpecSchema: z.ZodDiscriminatedUnion<"api", [z.ZodObject<{
244
221
  high: z.ZodOptional<z.ZodNullable<z.ZodString>>;
245
222
  xhigh: z.ZodOptional<z.ZodNullable<z.ZodString>>;
246
223
  }, "strip", z.ZodTypeAny, {
247
- medium?: string | null | undefined;
248
224
  off?: string | null | undefined;
249
225
  minimal?: string | null | undefined;
250
226
  low?: string | null | undefined;
227
+ medium?: string | null | undefined;
251
228
  high?: string | null | undefined;
252
229
  xhigh?: string | null | undefined;
253
230
  }, {
254
- medium?: string | null | undefined;
255
231
  off?: string | null | undefined;
256
232
  minimal?: string | null | undefined;
257
233
  low?: string | null | undefined;
234
+ medium?: string | null | undefined;
258
235
  high?: string | null | undefined;
259
236
  xhigh?: string | null | undefined;
260
237
  }>>;
@@ -268,73 +245,85 @@ declare const modelSpecSchema: z.ZodDiscriminatedUnion<"api", [z.ZodObject<{
268
245
  * placeholder that layer recognizes — the value is opaque to this server.
269
246
  */
270
247
  apiKey: z.ZodOptional<z.ZodString>;
248
+ api: z.ZodLiteral<"openai-completions">;
249
+ compat: z.ZodOptional<z.ZodObject<{
250
+ supportsReasoningEffort: z.ZodOptional<z.ZodBoolean>;
251
+ requiresThinkingAsText: z.ZodOptional<z.ZodBoolean>;
252
+ thinkingFormat: z.ZodOptional<z.ZodEnum<["openai", "openrouter", "deepseek", "zai", "qwen", "qwen-chat-template"]>>;
253
+ supportsStrictMode: z.ZodOptional<z.ZodBoolean>;
254
+ maxTokensField: z.ZodOptional<z.ZodEnum<["max_completion_tokens", "max_tokens"]>>;
255
+ cacheControlFormat: z.ZodOptional<z.ZodLiteral<"anthropic">>;
256
+ }, "strip", z.ZodTypeAny, {
257
+ supportsReasoningEffort?: boolean | undefined;
258
+ requiresThinkingAsText?: boolean | undefined;
259
+ thinkingFormat?: "deepseek" | "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined;
260
+ supportsStrictMode?: boolean | undefined;
261
+ maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
262
+ cacheControlFormat?: "anthropic" | undefined;
263
+ }, {
264
+ supportsReasoningEffort?: boolean | undefined;
265
+ requiresThinkingAsText?: boolean | undefined;
266
+ thinkingFormat?: "deepseek" | "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined;
267
+ supportsStrictMode?: boolean | undefined;
268
+ maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
269
+ cacheControlFormat?: "anthropic" | undefined;
270
+ }>>;
271
271
  }, "strip", z.ZodTypeAny, {
272
- api: "openai-completions";
273
272
  id: string;
274
273
  name: string;
275
- provider: "anthropic" | "amazon-bedrock" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "opencode" | "opencode-go" | "kimi-coding" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
274
+ provider: "amazon-bedrock" | "anthropic" | "azure-openai-responses" | "cerebras" | "cloudflare-ai-gateway" | "cloudflare-workers-ai" | "deepseek" | "fireworks" | "github-copilot" | "google" | "google-vertex" | "groq" | "huggingface" | "kimi-coding" | "minimax" | "minimax-cn" | "mistral" | "moonshotai" | "moonshotai-cn" | "openai" | "openai-codex" | "opencode" | "opencode-go" | "openrouter" | "vercel-ai-gateway" | "xai" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "zai";
276
275
  baseUrl: string;
277
276
  reasoning: boolean;
278
- input: ["text" | "image", ...("text" | "image")[]];
277
+ thinkingLevelMap?: {
278
+ off?: string | null | undefined;
279
+ minimal?: string | null | undefined;
280
+ low?: string | null | undefined;
281
+ medium?: string | null | undefined;
282
+ high?: string | null | undefined;
283
+ xhigh?: string | null | undefined;
284
+ } | undefined;
285
+ input: ["image" | "text", ...("image" | "text")[]];
279
286
  contextWindow: number;
280
287
  maxTokens: number;
288
+ apiKey?: string | undefined;
289
+ api: "openai-completions";
281
290
  compat?: {
282
291
  supportsReasoningEffort?: boolean | undefined;
283
292
  requiresThinkingAsText?: boolean | undefined;
284
- thinkingFormat?: "openai" | "deepseek" | "openrouter" | "zai" | "qwen" | "qwen-chat-template" | undefined;
293
+ thinkingFormat?: "deepseek" | "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined;
285
294
  supportsStrictMode?: boolean | undefined;
286
295
  maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
287
296
  cacheControlFormat?: "anthropic" | undefined;
288
297
  } | undefined;
298
+ }, {
299
+ id: string;
300
+ name: string;
301
+ provider: "amazon-bedrock" | "anthropic" | "azure-openai-responses" | "cerebras" | "cloudflare-ai-gateway" | "cloudflare-workers-ai" | "deepseek" | "fireworks" | "github-copilot" | "google" | "google-vertex" | "groq" | "huggingface" | "kimi-coding" | "minimax" | "minimax-cn" | "mistral" | "moonshotai" | "moonshotai-cn" | "openai" | "openai-codex" | "opencode" | "opencode-go" | "openrouter" | "vercel-ai-gateway" | "xai" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "zai";
302
+ baseUrl: string;
303
+ reasoning: boolean;
289
304
  thinkingLevelMap?: {
290
- medium?: string | null | undefined;
291
305
  off?: string | null | undefined;
292
306
  minimal?: string | null | undefined;
293
307
  low?: string | null | undefined;
308
+ medium?: string | null | undefined;
294
309
  high?: string | null | undefined;
295
310
  xhigh?: string | null | undefined;
296
311
  } | undefined;
297
- apiKey?: string | undefined;
298
- }, {
299
- api: "openai-completions";
300
- id: string;
301
- name: string;
302
- provider: "anthropic" | "amazon-bedrock" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "opencode" | "opencode-go" | "kimi-coding" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
303
- baseUrl: string;
304
- reasoning: boolean;
305
- input: ["text" | "image", ...("text" | "image")[]];
312
+ input: ["image" | "text", ...("image" | "text")[]];
306
313
  contextWindow: number;
307
314
  maxTokens: number;
315
+ apiKey?: string | undefined;
316
+ api: "openai-completions";
308
317
  compat?: {
309
318
  supportsReasoningEffort?: boolean | undefined;
310
319
  requiresThinkingAsText?: boolean | undefined;
311
- thinkingFormat?: "openai" | "deepseek" | "openrouter" | "zai" | "qwen" | "qwen-chat-template" | undefined;
320
+ thinkingFormat?: "deepseek" | "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined;
312
321
  supportsStrictMode?: boolean | undefined;
313
322
  maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
314
323
  cacheControlFormat?: "anthropic" | undefined;
315
324
  } | undefined;
316
- thinkingLevelMap?: {
317
- medium?: string | null | undefined;
318
- off?: string | null | undefined;
319
- minimal?: string | null | undefined;
320
- low?: string | null | undefined;
321
- high?: string | null | undefined;
322
- xhigh?: string | null | undefined;
323
- } | undefined;
324
- apiKey?: string | undefined;
325
325
  }>, z.ZodObject<{
326
- api: z.ZodLiteral<"anthropic-messages">;
327
- compat: z.ZodOptional<z.ZodObject<{
328
- supportsEagerToolInputStreaming: z.ZodOptional<z.ZodBoolean>;
329
- supportsLongCacheRetention: z.ZodOptional<z.ZodBoolean>;
330
- }, "strip", z.ZodTypeAny, {
331
- supportsEagerToolInputStreaming?: boolean | undefined;
332
- supportsLongCacheRetention?: boolean | undefined;
333
- }, {
334
- supportsEagerToolInputStreaming?: boolean | undefined;
335
- supportsLongCacheRetention?: boolean | undefined;
336
- }>>; /** Exact model id the serving endpoint expects. */
337
- id: z.ZodString; /** Display name shown in pi surfaces and session logs. */
326
+ /** Exact model id the serving endpoint expects. */id: z.ZodString; /** Display name shown in pi surfaces and session logs. */
338
327
  name: z.ZodString; /** pi-ai provider key — also the auth-storage key for `apiKey`. */
339
328
  provider: z.ZodEnum<["amazon-bedrock", "anthropic", "google", "google-vertex", "openai", "azure-openai-responses", "openai-codex", "deepseek", "github-copilot", "xai", "groq", "cerebras", "openrouter", "vercel-ai-gateway", "zai", "mistral", "minimax", "minimax-cn", "moonshotai", "moonshotai-cn", "huggingface", "fireworks", "opencode", "opencode-go", "kimi-coding", "cloudflare-workers-ai", "cloudflare-ai-gateway", "xiaomi", "xiaomi-token-plan-cn", "xiaomi-token-plan-ams", "xiaomi-token-plan-sgp"]>; /** Provider API root, e.g. "https://openrouter.ai/api/v1". */
340
329
  baseUrl: z.ZodString;
@@ -347,17 +336,17 @@ declare const modelSpecSchema: z.ZodDiscriminatedUnion<"api", [z.ZodObject<{
347
336
  high: z.ZodOptional<z.ZodNullable<z.ZodString>>;
348
337
  xhigh: z.ZodOptional<z.ZodNullable<z.ZodString>>;
349
338
  }, "strip", z.ZodTypeAny, {
350
- medium?: string | null | undefined;
351
339
  off?: string | null | undefined;
352
340
  minimal?: string | null | undefined;
353
341
  low?: string | null | undefined;
342
+ medium?: string | null | undefined;
354
343
  high?: string | null | undefined;
355
344
  xhigh?: string | null | undefined;
356
345
  }, {
357
- medium?: string | null | undefined;
358
346
  off?: string | null | undefined;
359
347
  minimal?: string | null | undefined;
360
348
  low?: string | null | undefined;
349
+ medium?: string | null | undefined;
361
350
  high?: string | null | undefined;
362
351
  xhigh?: string | null | undefined;
363
352
  }>>;
@@ -371,52 +360,63 @@ declare const modelSpecSchema: z.ZodDiscriminatedUnion<"api", [z.ZodObject<{
371
360
  * placeholder that layer recognizes — the value is opaque to this server.
372
361
  */
373
362
  apiKey: z.ZodOptional<z.ZodString>;
363
+ api: z.ZodLiteral<"anthropic-messages">;
364
+ compat: z.ZodOptional<z.ZodObject<{
365
+ supportsEagerToolInputStreaming: z.ZodOptional<z.ZodBoolean>;
366
+ supportsLongCacheRetention: z.ZodOptional<z.ZodBoolean>;
367
+ }, "strip", z.ZodTypeAny, {
368
+ supportsEagerToolInputStreaming?: boolean | undefined;
369
+ supportsLongCacheRetention?: boolean | undefined;
370
+ }, {
371
+ supportsEagerToolInputStreaming?: boolean | undefined;
372
+ supportsLongCacheRetention?: boolean | undefined;
373
+ }>>;
374
374
  }, "strip", z.ZodTypeAny, {
375
- api: "anthropic-messages";
376
375
  id: string;
377
376
  name: string;
378
- provider: "anthropic" | "amazon-bedrock" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "opencode" | "opencode-go" | "kimi-coding" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
377
+ provider: "amazon-bedrock" | "anthropic" | "azure-openai-responses" | "cerebras" | "cloudflare-ai-gateway" | "cloudflare-workers-ai" | "deepseek" | "fireworks" | "github-copilot" | "google" | "google-vertex" | "groq" | "huggingface" | "kimi-coding" | "minimax" | "minimax-cn" | "mistral" | "moonshotai" | "moonshotai-cn" | "openai" | "openai-codex" | "opencode" | "opencode-go" | "openrouter" | "vercel-ai-gateway" | "xai" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "zai";
379
378
  baseUrl: string;
380
379
  reasoning: boolean;
381
- input: ["text" | "image", ...("text" | "image")[]];
382
- contextWindow: number;
383
- maxTokens: number;
384
- compat?: {
385
- supportsEagerToolInputStreaming?: boolean | undefined;
386
- supportsLongCacheRetention?: boolean | undefined;
387
- } | undefined;
388
380
  thinkingLevelMap?: {
389
- medium?: string | null | undefined;
390
381
  off?: string | null | undefined;
391
382
  minimal?: string | null | undefined;
392
383
  low?: string | null | undefined;
384
+ medium?: string | null | undefined;
393
385
  high?: string | null | undefined;
394
386
  xhigh?: string | null | undefined;
395
387
  } | undefined;
396
- apiKey?: string | undefined;
397
- }, {
398
- api: "anthropic-messages";
399
- id: string;
400
- name: string;
401
- provider: "anthropic" | "amazon-bedrock" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "opencode" | "opencode-go" | "kimi-coding" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
402
- baseUrl: string;
403
- reasoning: boolean;
404
- input: ["text" | "image", ...("text" | "image")[]];
388
+ input: ["image" | "text", ...("image" | "text")[]];
405
389
  contextWindow: number;
406
390
  maxTokens: number;
391
+ apiKey?: string | undefined;
392
+ api: "anthropic-messages";
407
393
  compat?: {
408
394
  supportsEagerToolInputStreaming?: boolean | undefined;
409
395
  supportsLongCacheRetention?: boolean | undefined;
410
396
  } | undefined;
397
+ }, {
398
+ id: string;
399
+ name: string;
400
+ provider: "amazon-bedrock" | "anthropic" | "azure-openai-responses" | "cerebras" | "cloudflare-ai-gateway" | "cloudflare-workers-ai" | "deepseek" | "fireworks" | "github-copilot" | "google" | "google-vertex" | "groq" | "huggingface" | "kimi-coding" | "minimax" | "minimax-cn" | "mistral" | "moonshotai" | "moonshotai-cn" | "openai" | "openai-codex" | "opencode" | "opencode-go" | "openrouter" | "vercel-ai-gateway" | "xai" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "zai";
401
+ baseUrl: string;
402
+ reasoning: boolean;
411
403
  thinkingLevelMap?: {
412
- medium?: string | null | undefined;
413
404
  off?: string | null | undefined;
414
405
  minimal?: string | null | undefined;
415
406
  low?: string | null | undefined;
407
+ medium?: string | null | undefined;
416
408
  high?: string | null | undefined;
417
409
  xhigh?: string | null | undefined;
418
410
  } | undefined;
411
+ input: ["image" | "text", ...("image" | "text")[]];
412
+ contextWindow: number;
413
+ maxTokens: number;
419
414
  apiKey?: string | undefined;
415
+ api: "anthropic-messages";
416
+ compat?: {
417
+ supportsEagerToolInputStreaming?: boolean | undefined;
418
+ supportsLongCacheRetention?: boolean | undefined;
419
+ } | undefined;
420
420
  }>]>;
421
421
  type ModelSpec = z.infer<typeof modelSpecSchema>;
422
422
  /**
@@ -426,16 +426,20 @@ type ModelSpec = z.infer<typeof modelSpecSchema>;
426
426
  */
427
427
  declare function parseModelSpec(input: unknown, log: Logger): ModelSpec | null;
428
428
  declare function buildModelFromSpec(spec: ModelSpec): {
429
- compat?: {
430
- supportsReasoningEffort?: boolean | undefined;
431
- requiresThinkingAsText?: boolean | undefined;
432
- thinkingFormat?: "openai" | "deepseek" | "openrouter" | "zai" | "qwen" | "qwen-chat-template" | undefined;
433
- supportsStrictMode?: boolean | undefined;
434
- maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
435
- cacheControlFormat?: "anthropic" | undefined;
429
+ id: string;
430
+ name: string;
431
+ provider: "amazon-bedrock" | "anthropic" | "azure-openai-responses" | "cerebras" | "cloudflare-ai-gateway" | "cloudflare-workers-ai" | "deepseek" | "fireworks" | "github-copilot" | "google" | "google-vertex" | "groq" | "huggingface" | "kimi-coding" | "minimax" | "minimax-cn" | "mistral" | "moonshotai" | "moonshotai-cn" | "openai" | "openai-codex" | "opencode" | "opencode-go" | "openrouter" | "vercel-ai-gateway" | "xai" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "zai";
432
+ baseUrl: string;
433
+ reasoning: boolean;
434
+ thinkingLevelMap?: {
435
+ off?: string | null | undefined;
436
+ minimal?: string | null | undefined;
437
+ low?: string | null | undefined;
438
+ medium?: string | null | undefined;
439
+ high?: string | null | undefined;
440
+ xhigh?: string | null | undefined;
436
441
  } | undefined;
437
- api: "openai-completions";
438
- input: ["text" | "image", ...("text" | "image")[]];
442
+ input: ["image" | "text", ...("image" | "text")[]];
439
443
  cost: {
440
444
  input: number;
441
445
  output: number;
@@ -444,26 +448,30 @@ declare function buildModelFromSpec(spec: ModelSpec): {
444
448
  };
445
449
  contextWindow: number;
446
450
  maxTokens: number;
451
+ api: "openai-completions";
452
+ compat?: {
453
+ supportsReasoningEffort?: boolean | undefined;
454
+ requiresThinkingAsText?: boolean | undefined;
455
+ thinkingFormat?: "deepseek" | "openai" | "openrouter" | "qwen" | "qwen-chat-template" | "zai" | undefined;
456
+ supportsStrictMode?: boolean | undefined;
457
+ maxTokensField?: "max_completion_tokens" | "max_tokens" | undefined;
458
+ cacheControlFormat?: "anthropic" | undefined;
459
+ } | undefined;
460
+ } | {
461
+ id: string;
462
+ name: string;
463
+ provider: "amazon-bedrock" | "anthropic" | "azure-openai-responses" | "cerebras" | "cloudflare-ai-gateway" | "cloudflare-workers-ai" | "deepseek" | "fireworks" | "github-copilot" | "google" | "google-vertex" | "groq" | "huggingface" | "kimi-coding" | "minimax" | "minimax-cn" | "mistral" | "moonshotai" | "moonshotai-cn" | "openai" | "openai-codex" | "opencode" | "opencode-go" | "openrouter" | "vercel-ai-gateway" | "xai" | "xiaomi" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-sgp" | "zai";
464
+ baseUrl: string;
465
+ reasoning: boolean;
447
466
  thinkingLevelMap?: {
448
- medium?: string | null | undefined;
449
467
  off?: string | null | undefined;
450
468
  minimal?: string | null | undefined;
451
469
  low?: string | null | undefined;
470
+ medium?: string | null | undefined;
452
471
  high?: string | null | undefined;
453
472
  xhigh?: string | null | undefined;
454
473
  } | undefined;
455
- id: string;
456
- name: string;
457
- provider: "anthropic" | "amazon-bedrock" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "opencode" | "opencode-go" | "kimi-coding" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
458
- baseUrl: string;
459
- reasoning: boolean;
460
- } | {
461
- compat?: {
462
- supportsEagerToolInputStreaming?: boolean | undefined;
463
- supportsLongCacheRetention?: boolean | undefined;
464
- } | undefined;
465
- api: "anthropic-messages";
466
- input: ["text" | "image", ...("text" | "image")[]];
474
+ input: ["image" | "text", ...("image" | "text")[]];
467
475
  cost: {
468
476
  input: number;
469
477
  output: number;
@@ -472,19 +480,11 @@ declare function buildModelFromSpec(spec: ModelSpec): {
472
480
  };
473
481
  contextWindow: number;
474
482
  maxTokens: number;
475
- thinkingLevelMap?: {
476
- medium?: string | null | undefined;
477
- off?: string | null | undefined;
478
- minimal?: string | null | undefined;
479
- low?: string | null | undefined;
480
- high?: string | null | undefined;
481
- xhigh?: string | null | undefined;
483
+ api: "anthropic-messages";
484
+ compat?: {
485
+ supportsEagerToolInputStreaming?: boolean | undefined;
486
+ supportsLongCacheRetention?: boolean | undefined;
482
487
  } | undefined;
483
- id: string;
484
- name: string;
485
- provider: "anthropic" | "amazon-bedrock" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "opencode" | "opencode-go" | "kimi-coding" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
486
- baseUrl: string;
487
- reasoning: boolean;
488
488
  };
489
489
  /**
490
490
  * Resolve the model a request should run on: a valid `x_model` wins,
@@ -542,4 +542,27 @@ declare function getCurrentTraceparent(): string | null;
542
542
  /** Extract the trace id (second segment) from a `traceparent` value. */
543
543
  declare function parseTraceId(traceparent: string | null): string | null;
544
544
  //#endregion
545
- export { type A2AOptions, type AnthropicMessagesOptions, type ChatCompletionsOptions, type CreateSessionArgs, DEFAULT_MODEL, DEFAULT_PROVIDER, DEFAULT_THINKING_LEVEL, KNOWN_PI_PROVIDERS, type Middleware, type ModelSpec, PI_AGENT_DIR, type PostPromptCallback, type ProtocolHandler, type ProtocolsOptions, type ResponsesOptions, type SessionFactory, type SessionSetupCallback, type TraceContext, VALID_THINKING_LEVELS, type WebHandler, buildAgentCard, buildModelFromSpec, chainMiddleware, composeHandlers, createAgentExecutor, createMapSessionRegistry, createPrewarm, createProtocolHandlers, createProtocols, deriveTraceContext, getCurrentTraceparent, logger, modelSpecSchema, mountAt, newTraceContext, parseModelSpec, parseTraceId, requestHeaders, requestLogger, requestUrl, resolveRequestModel, runInTraceContext, setCurrentTraceparent, webHandlerToMiddleware, withSpecApiKey };
545
+ //#region src/billing-blocked-provider-signal.d.ts
546
+ /**
547
+ * Duplicated from the billing gate's `BlockReason` (apps/anyone/billing
548
+ * gate/block-reason.ts): pi-server runs inside the sandbox and must not
549
+ * depend on platform packages. A reason the proxy sends that predates this
550
+ * build fails the enum and degrades to the untyped `billing_blocked`
551
+ * handling — never a crash. Exported so agent-lifecycle's chat.test.ts can
552
+ * pin this copy against its own (which billing's block-reason.test.ts in
553
+ * turn pins against the gate), keeping every copy drift-checked.
554
+ */
555
+ declare const BILLING_BLOCK_REASONS: readonly ["insufficient_balance", "payment_failed", "hard_spend_limit_reached"];
556
+ declare const payloadSchema: z.ZodObject<{
557
+ blockReason: z.ZodEnum<["insufficient_balance", "payment_failed", "hard_spend_limit_reached"]>;
558
+ message: z.ZodString;
559
+ }, "strict", z.ZodTypeAny, {
560
+ blockReason: "hard_spend_limit_reached" | "insufficient_balance" | "payment_failed";
561
+ message: string;
562
+ }, {
563
+ blockReason: "hard_spend_limit_reached" | "insufficient_balance" | "payment_failed";
564
+ message: string;
565
+ }>;
566
+ type BillingBlockedProviderSignal = z.infer<typeof payloadSchema>;
567
+ //#endregion
568
+ export { type A2AOptions, type AnthropicMessagesOptions, BILLING_BLOCK_REASONS, type BillingBlockedProviderSignal, type ChatCompletionsOptions, type CreateSessionArgs, DEFAULT_MODEL, DEFAULT_PROVIDER, DEFAULT_THINKING_LEVEL, KNOWN_PI_PROVIDERS, type Middleware, type ModelSpec, PI_AGENT_DIR, type PostPromptCallback, type ProtocolHandler, type ProtocolsOptions, type ResponsesOptions, type SessionFactory, type SessionSetupCallback, type TraceContext, VALID_THINKING_LEVELS, type WebHandler, buildAgentCard, buildModelFromSpec, chainMiddleware, composeHandlers, createAgentExecutor, createMapSessionRegistry, createPrewarm, createProtocolHandlers, createProtocols, deriveTraceContext, getCurrentTraceparent, logger, modelSpecSchema, mountAt, newTraceContext, parseModelSpec, parseTraceId, requestHeaders, requestLogger, requestUrl, resolveRequestModel, runInTraceContext, setCurrentTraceparent, webHandlerToMiddleware, withSpecApiKey };
package/dist/index.mjs CHANGED
@@ -259,7 +259,7 @@ function parseShellEnv(request) {
259
259
  const env = {};
260
260
  for (const [k, v] of Object.entries(parsed)) if (typeof v === "string") env[k] = v;
261
261
  return Object.keys(env).length > 0 ? env : null;
262
- } catch {
262
+ } catch (_err) {
263
263
  return null;
264
264
  }
265
265
  }
@@ -283,12 +283,12 @@ function createSSEResponse(handler) {
283
283
  write(chunk) {
284
284
  try {
285
285
  controller.enqueue(encoder.encode(chunk));
286
- } catch {}
286
+ } catch (_err) {}
287
287
  },
288
288
  close() {
289
289
  try {
290
290
  controller.close();
291
- } catch {}
291
+ } catch (_err) {}
292
292
  }
293
293
  };
294
294
  const keepalive = setInterval(() => {
@@ -298,13 +298,13 @@ function createSSEResponse(handler) {
298
298
  clearInterval(keepalive);
299
299
  try {
300
300
  controller.close();
301
- } catch {}
301
+ } catch (_err) {}
302
302
  }, (err) => {
303
303
  logger.error({ err }, "SSE handler crashed — stream closed without response");
304
304
  clearInterval(keepalive);
305
305
  try {
306
306
  controller.close();
307
- } catch {}
307
+ } catch (_err) {}
308
308
  });
309
309
  return new Response(stream, {
310
310
  status: 200,
@@ -373,6 +373,57 @@ async function runConversation({ session, prompt, images, log, postPrompt }) {
373
373
  });
374
374
  }
375
375
  /**
376
+ * Track the agent session's built-in model-call auto-retry so it leaves a
377
+ * trace.
378
+ *
379
+ * `AgentSession` already restarts a failed assistant turn in place (via
380
+ * `agent.continue()`, so no prompt is replayed and no tool re-executes) for the
381
+ * transient provider/transport failures pi classifies as retryable -- dropped
382
+ * streams, `terminated`, 5xx, overloaded, rate limits. It is on by default,
383
+ * with its own budget and backoff, and it emits `auto_retry_start` /
384
+ * `auto_retry_end` around each attempt.
385
+ *
386
+ * Nothing consumed those events, so a retry left no trace anywhere: a call that
387
+ * succeeded first try and one that burned the whole budget before failing
388
+ * produced the same terminal error, and the fleet-wide retry rate was
389
+ * unmeasurable. That gap is why a 2026-08-16 investigation into three runs lost
390
+ * to `provider_error: terminated` could not tell whether the budget had run out
391
+ * (ANY-7101).
392
+ *
393
+ * Exposed as a handler rather than its own `session.subscribe` call so each
394
+ * protocol feeds it from the single subscription it already owns -- one
395
+ * subscriber, explicit ordering.
396
+ *
397
+ * `attempts()` reports what has been spent so far, so a terminal error can
398
+ * carry the count to the worker, where it lands in a log group we can query
399
+ * fleet-wide (the sandbox's own logs are not).
400
+ */
401
+ function createAutoRetryObserver(log) {
402
+ let attempts = 0;
403
+ return {
404
+ observe(event) {
405
+ if (event.type === "auto_retry_start") {
406
+ attempts = typeof event.attempt === "number" ? event.attempt : attempts + 1;
407
+ log.warn({
408
+ event: "model_call_auto_retry",
409
+ attempt: event.attempt,
410
+ max_attempts: event.maxAttempts,
411
+ delay_ms: event.delayMs,
412
+ error_message: event.errorMessage
413
+ }, "retrying failed model call in place");
414
+ return;
415
+ }
416
+ if (event.type === "auto_retry_end") log.warn({
417
+ event: "model_call_auto_retry_end",
418
+ attempt: event.attempt,
419
+ success: event.success,
420
+ final_error: event.finalError
421
+ }, event.success ? "model call recovered after retry" : "model call retries exhausted");
422
+ },
423
+ attempts: () => attempts
424
+ };
425
+ }
426
+ /**
376
427
  * Hard-stop the in-flight turn for a session. Shared by every protocol's
377
428
  * `/:id/abort` route: a cancel signals the stop explicitly instead of relying
378
429
  * on a dropped connection. `session.abort()` interrupts the turn and resolves
@@ -419,7 +470,10 @@ async function extractParts(parts, log) {
419
470
  }
420
471
  if (part.url != null && part.mediaType?.startsWith("image/")) {
421
472
  try {
422
- images.push(await fetchImageAsBase64({ url: part.url }));
473
+ images.push(await fetchImageAsBase64({
474
+ url: part.url,
475
+ userAgent: null
476
+ }));
423
477
  } catch (err) {
424
478
  log.warn({
425
479
  event: "a2a_image_fetch_failed",
@@ -877,6 +931,7 @@ function classifyCode(status, message, code) {
877
931
  if (status === 429 || code === "rate_limit_exceeded" || /rate[_ ]limit|too many requests|resource[_ ]exhausted/i.test(message)) return "rate_limited";
878
932
  if (status === 529 || code === "provider_overloaded" || /overloaded/i.test(message)) return "provider_overloaded";
879
933
  if (status === 503 || status === 504 || code === "provider_unavailable" || /no healthy upstream|upstream request timeout|stream timeout|service unavailable|unavailable|gateway/i.test(message)) return "provider_unavailable";
934
+ if (code === "content_filter" || /finish_reason:\s*content_filter|content[_ ]filter|content[_ ]management[_ ]policy/i.test(message)) return "content_filter";
880
935
  return "provider_error";
881
936
  }
882
937
  /**
@@ -891,7 +946,7 @@ function isModelProviderError(err) {
891
946
  if (numericStatus(e) !== null) return true;
892
947
  if (e.error && typeof e.error === "object") return true;
893
948
  const msg = messageText(err);
894
- return /prompt is too long|context[_ ]length[_ ]exceeded|exceeds maximum token limit|input token count|rate[_ ]limit|overloaded|no healthy upstream|upstream request timeout|insufficient credits|requires more credits|credit balance is too low|payment required|purchase more credits/i.test(msg);
949
+ return /prompt is too long|context[_ ]length[_ ]exceeded|exceeds maximum token limit|input token count|rate[_ ]limit|overloaded|no healthy upstream|upstream request timeout|insufficient credits|requires more credits|credit balance is too low|payment required|purchase more credits|finish_reason:\s*content_filter|content[_ ]filter|content[_ ]management[_ ]policy/i.test(msg);
895
950
  }
896
951
  /** Build the structured, forwardable error from a thrown model-provider error. */
897
952
  function toModelProviderError(err) {
@@ -947,7 +1002,10 @@ async function extractContent$1(content, log) {
947
1002
  data: src.data
948
1003
  });
949
1004
  else if (src?.type === "url" && src.url) try {
950
- images.push(await fetchImageAsBase64({ url: src.url }));
1005
+ images.push(await fetchImageAsBase64({
1006
+ url: src.url,
1007
+ userAgent: null
1008
+ }));
951
1009
  } catch (err) {
952
1010
  log.warn({
953
1011
  event: "image_fetch_failed",
@@ -1036,7 +1094,7 @@ async function handleMessages(request, ctx, registry) {
1036
1094
  let parsed;
1037
1095
  try {
1038
1096
  parsed = JSON.parse(await request.text());
1039
- } catch {
1097
+ } catch (_err) {
1040
1098
  return jsonError(400, "invalid json");
1041
1099
  }
1042
1100
  const { messages, stream = false, model: modelInput, system, x_model: modelSpecInput } = parsed ?? {};
@@ -1059,7 +1117,7 @@ async function handleMessages(request, ctx, registry) {
1059
1117
  const baseSystemPrompt = extractSystemPrompt(system);
1060
1118
  const { sessionId } = parseSessionId(request);
1061
1119
  const shellEnv = parseShellEnv(request);
1062
- const { session, sessionManager: sm } = await ctx.createSession({
1120
+ const { session } = await ctx.createSession({
1063
1121
  cwd: ctx.cwd,
1064
1122
  sessionId,
1065
1123
  perRequestApiKeys: withSpecApiKey(resolvePerRequestApiKeys(request), modelSpec),
@@ -1082,7 +1140,6 @@ async function handleMessages(request, ctx, registry) {
1082
1140
  if (stream) {
1083
1141
  const response = runStream$2({
1084
1142
  session,
1085
- sm,
1086
1143
  prompt,
1087
1144
  images,
1088
1145
  id,
@@ -1098,7 +1155,6 @@ async function handleMessages(request, ctx, registry) {
1098
1155
  }
1099
1156
  const response = await runBlocking$2({
1100
1157
  session,
1101
- sm,
1102
1158
  prompt,
1103
1159
  images,
1104
1160
  id,
@@ -1112,7 +1168,7 @@ async function handleMessages(request, ctx, registry) {
1112
1168
  if (traceId) response.headers.set("x-trace-id", traceId);
1113
1169
  return response;
1114
1170
  }
1115
- function runStream$2({ session, sm, prompt, images, id, sessionId, modelName, log, postPrompt, registry }) {
1171
+ function runStream$2({ session, prompt, images, id, sessionId, modelName, log, postPrompt, registry }) {
1116
1172
  return createSSEResponse(async (writer) => {
1117
1173
  let contentBlockIndex = 0;
1118
1174
  let textBlockOpen = false;
@@ -1158,12 +1214,14 @@ function runStream$2({ session, sm, prompt, images, id, sessionId, modelName, lo
1158
1214
  textBlockOpen = false;
1159
1215
  };
1160
1216
  let capturedModelError = null;
1217
+ const autoRetry = createAutoRetryObserver(log);
1161
1218
  const emitProviderError = (providerError) => {
1162
1219
  log.warn({
1163
1220
  event: "model_provider_error",
1164
1221
  code: providerError.code,
1165
1222
  upstream_status: providerError.upstreamStatus,
1166
- provider: providerError.provider
1223
+ provider: providerError.provider,
1224
+ retry_attempts: autoRetry.attempts()
1167
1225
  }, "forwarding model-provider error to client");
1168
1226
  sseEvent(writer, "error", {
1169
1227
  type: "error",
@@ -1173,13 +1231,15 @@ function runStream$2({ session, sm, prompt, images, id, sessionId, modelName, lo
1173
1231
  x_model_provider_error: {
1174
1232
  code: providerError.code,
1175
1233
  provider: providerError.provider,
1176
- upstream_status: providerError.upstreamStatus
1234
+ upstream_status: providerError.upstreamStatus,
1235
+ retry_attempts: autoRetry.attempts()
1177
1236
  }
1178
1237
  }
1179
1238
  });
1180
1239
  };
1181
1240
  session.subscribe((event) => {
1182
1241
  const ev = event;
1242
+ autoRetry.observe(ev);
1183
1243
  const endedMessage = ev.message ?? (Array.isArray(ev.messages) ? ev.messages[ev.messages.length - 1] : null);
1184
1244
  if (endedMessage?.stopReason === "error" && typeof endedMessage.errorMessage === "string" && endedMessage.errorMessage.length > 0) capturedModelError = endedMessage.errorMessage;
1185
1245
  if (ev.type !== "message_update") return;
@@ -1266,7 +1326,7 @@ function runStream$2({ session, sm, prompt, images, id, sessionId, modelName, lo
1266
1326
  sseEvent(writer, "message_stop", { type: "message_stop" });
1267
1327
  });
1268
1328
  }
1269
- async function runBlocking$2({ session, sm, prompt, images, id, sessionId, modelName, log, postPrompt, registry }) {
1329
+ async function runBlocking$2({ session, prompt, images, id, sessionId, modelName, log, postPrompt, registry }) {
1270
1330
  let text = "";
1271
1331
  const toolUseBlocks = [];
1272
1332
  const tcByContentIdx = /* @__PURE__ */ new Map();
@@ -1317,7 +1377,7 @@ async function runBlocking$2({ session, sm, prompt, images, id, sessionId, model
1317
1377
  }
1318
1378
  for (const block of toolUseBlocks) if (block.type === "tool_use" && typeof block.input === "string") try {
1319
1379
  block.input = JSON.parse(block.input);
1320
- } catch {
1380
+ } catch (_err) {
1321
1381
  block.input = {};
1322
1382
  }
1323
1383
  const content = [];
@@ -1379,6 +1439,87 @@ function create$2(options) {
1379
1439
  };
1380
1440
  }
1381
1441
  //#endregion
1442
+ //#region src/billing-blocked-provider-signal.ts
1443
+ const BILLING_BLOCKED_PROVIDER_SIGNAL_PREFIX = "ANYONE_BILLING_BLOCKED_V1:";
1444
+ /**
1445
+ * Duplicated from the billing gate's `BlockReason` (apps/anyone/billing
1446
+ * gate/block-reason.ts): pi-server runs inside the sandbox and must not
1447
+ * depend on platform packages. A reason the proxy sends that predates this
1448
+ * build fails the enum and degrades to the untyped `billing_blocked`
1449
+ * handling — never a crash. Exported so agent-lifecycle's chat.test.ts can
1450
+ * pin this copy against its own (which billing's block-reason.test.ts in
1451
+ * turn pins against the gate), keeping every copy drift-checked.
1452
+ */
1453
+ const BILLING_BLOCK_REASONS = [
1454
+ "insufficient_balance",
1455
+ "payment_failed",
1456
+ "hard_spend_limit_reached"
1457
+ ];
1458
+ const payloadSchema = z.object({
1459
+ blockReason: z.enum(BILLING_BLOCK_REASONS),
1460
+ message: z.string()
1461
+ }).strict();
1462
+ const nestedProxyEnvelopeSchema = z.object({
1463
+ error: z.object({
1464
+ type: z.literal("billing_blocked"),
1465
+ code: z.literal("billing_blocked"),
1466
+ message: z.string()
1467
+ }).strict(),
1468
+ blockReason: z.enum(BILLING_BLOCK_REASONS),
1469
+ message: z.string()
1470
+ }).strict();
1471
+ /**
1472
+ * pi-ai's openai-compatible providers (openrouter, xai, groq, deepseek, …) can
1473
+ * NOT fold a proxy's non-2xx body into `error.message`, so `formatProviderError`
1474
+ * (@earendil-works/pi-ai utils/error-body) composes the display string as
1475
+ * `"<status>: <body>"` or, with a provider label, `"<prefix> (<status>): <body>"`.
1476
+ * The billing gate's 402 body therefore reaches us wrapped, e.g.
1477
+ * `"402: {\"error\":{...},\"blockReason\":\"insufficient_balance\",\"message\":...}"`
1478
+ * or `"OpenRouter (402): {...}"`. Neither the bare `JSON.parse` nor the `"402 "`
1479
+ * (space) strip below recognizes that, so a real billing block from an
1480
+ * openrouter-routed model degrades to the generic model-provider error. Peel a
1481
+ * single leading `"<status>: "` / `"<prefix> (<status>): "` wrapper off the
1482
+ * front so the recovered body flows through the existing shape checks. Returns
1483
+ * the message unchanged when no wrapper is present.
1484
+ */
1485
+ function unwrapOpenAICompatStatusPrefix(message) {
1486
+ const withPrefix = message.match(/^.+ \(\d{3}\): ([\s\S]+)$/);
1487
+ if (withPrefix?.[1] !== void 0) return withPrefix[1];
1488
+ const bare = message.match(/^\d{3}: ([\s\S]+)$/);
1489
+ if (bare?.[1] !== void 0) return bare[1];
1490
+ return message;
1491
+ }
1492
+ function parsePrefixedSignal(message) {
1493
+ const normalized = message.startsWith("402 ") ? message.slice(4) : message;
1494
+ if (!normalized.startsWith(BILLING_BLOCKED_PROVIDER_SIGNAL_PREFIX)) return null;
1495
+ try {
1496
+ const parsed = payloadSchema.safeParse(JSON.parse(normalized.slice(26)));
1497
+ return parsed.success ? parsed.data : null;
1498
+ } catch (_err) {
1499
+ return null;
1500
+ }
1501
+ }
1502
+ /**
1503
+ * Decode only the versioned platform envelope. Anthropic and OpenAI prefix the
1504
+ * nested error message with HTTP 402. Google's SDK instead preserves the full
1505
+ * proxy response as JSON, so that outer shape is validated separately.
1506
+ */
1507
+ function parseBillingBlockedProviderSignal(message) {
1508
+ const unwrapped = unwrapOpenAICompatStatusPrefix(message);
1509
+ const direct = parsePrefixedSignal(unwrapped);
1510
+ if (direct) return direct;
1511
+ try {
1512
+ const envelope = nestedProxyEnvelopeSchema.safeParse(JSON.parse(unwrapped));
1513
+ if (!envelope.success) return null;
1514
+ const nested = parsePrefixedSignal(envelope.data.error.message);
1515
+ const topLevel = parsePrefixedSignal(envelope.data.message);
1516
+ if (!nested || nested.blockReason !== envelope.data.blockReason || envelope.data.message !== nested.message && (!topLevel || topLevel.blockReason !== nested.blockReason || topLevel.message !== nested.message)) return null;
1517
+ return nested;
1518
+ } catch (_err) {
1519
+ return null;
1520
+ }
1521
+ }
1522
+ //#endregion
1382
1523
  //#region src/protocols/chat-completions.ts
1383
1524
  /**
1384
1525
  * OpenAI Chat Completions–compatible protocol handler.
@@ -1563,7 +1704,7 @@ async function loadHistoryIntoSession({ sm, messages, upToIdxExclusive, modelNam
1563
1704
  let args = {};
1564
1705
  try {
1565
1706
  args = tc.function.arguments ? JSON.parse(tc.function.arguments) : {};
1566
- } catch {
1707
+ } catch (_err) {
1567
1708
  args = { _raw: tc.function.arguments };
1568
1709
  }
1569
1710
  contentArr.push({
@@ -1605,6 +1746,19 @@ async function loadHistoryIntoSession({ sm, messages, upToIdxExclusive, modelNam
1605
1746
  }
1606
1747
  }
1607
1748
  }
1749
+ /**
1750
+ * Each pi/session message carries the real wall-clock `timestamp` the
1751
+ * provider stamped when it produced that message (see @earendil-works/pi-ai
1752
+ * Message). Forward it verbatim as `x_created_at` (epoch ms) on the OAI
1753
+ * trailer message so the consumer (anyone-messaging completeRun) can stamp
1754
+ * each persisted segment at its true emit time and interleave a mid-run steer
1755
+ * by createdAt — instead of positionally reconstructing per-turn times, which
1756
+ * drifts on parallel tool calls and provider-split replies.
1757
+ */
1758
+ function messageTimestampMs(m) {
1759
+ const ts = m?.timestamp;
1760
+ return typeof ts === "number" && Number.isFinite(ts) ? ts : null;
1761
+ }
1608
1762
  function piMessagesToOpenAI(messages) {
1609
1763
  const out = [];
1610
1764
  for (const m of messages) {
@@ -1633,6 +1787,8 @@ function piMessagesToOpenAI(messages) {
1633
1787
  };
1634
1788
  if (toolCalls.length) msg.tool_calls = toolCalls;
1635
1789
  if (thinking.length) msg.x_thinking = thinking;
1790
+ const createdAtMs = messageTimestampMs(m);
1791
+ if (createdAtMs !== null) msg.x_created_at = createdAtMs;
1636
1792
  out.push(msg);
1637
1793
  continue;
1638
1794
  }
@@ -1644,6 +1800,8 @@ function piMessagesToOpenAI(messages) {
1644
1800
  content: text
1645
1801
  };
1646
1802
  if (m.isError) tm.x_is_error = true;
1803
+ const createdAtMs = messageTimestampMs(m);
1804
+ if (createdAtMs !== null) tm.x_created_at = createdAtMs;
1647
1805
  out.push(tm);
1648
1806
  continue;
1649
1807
  }
@@ -1658,7 +1816,7 @@ async function handleChatCompletions(request, ctx, registry, pendingSteerIds) {
1658
1816
  let parsed;
1659
1817
  try {
1660
1818
  parsed = JSON.parse(await request.text());
1661
- } catch {
1819
+ } catch (_err) {
1662
1820
  return jsonError(400, "invalid json");
1663
1821
  }
1664
1822
  const { messages, stream = false, model: modelInput, thinkingLevel: thinkingInput, context_window: contextWindowInput, x_model: modelSpecInput } = parsed ?? {};
@@ -1670,7 +1828,7 @@ async function handleChatCompletions(request, ctx, registry, pendingSteerIds) {
1670
1828
  }
1671
1829
  if (lastUserIdx < 0) return jsonError(400, "no user message in history");
1672
1830
  const lastUser = messages[lastUserIdx];
1673
- const reqUA = request.headers.get("user-agent") ?? void 0;
1831
+ const reqUA = request.headers.get("user-agent") ?? null;
1674
1832
  const { text: prompt, images } = await extractContent(lastUser.content, {
1675
1833
  userAgent: reqUA,
1676
1834
  log
@@ -1701,7 +1859,7 @@ async function handleChatCompletions(request, ctx, registry, pendingSteerIds) {
1701
1859
  const sessionId = parsedSession.source === "header" ? parsedSession.sessionId : `chatcmpl-${parsedSession.sessionId}`;
1702
1860
  const shellEnv = parseShellEnv(request);
1703
1861
  const sessionSetupStart = performance.now();
1704
- const [{ session, sessionManager: sm }, customTools] = await Promise.all([ctx.createSession({
1862
+ const [{ session, sessionManager: sm }] = await Promise.all([ctx.createSession({
1705
1863
  cwd: ctx.cwd,
1706
1864
  sessionId,
1707
1865
  perRequestApiKeys: withSpecApiKey(resolvePerRequestApiKeys(request), modelSpec),
@@ -1788,6 +1946,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1788
1946
  let usage = null;
1789
1947
  let lastFollowUpCount = 0;
1790
1948
  let capturedModelError = null;
1949
+ const autoRetry = createAutoRetryObserver(log);
1791
1950
  const sendChunk = (delta, finishReason) => {
1792
1951
  if (firstChunkAt === null) {
1793
1952
  firstChunkAt = performance.now();
@@ -1812,12 +1971,13 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1812
1971
  };
1813
1972
  const ensureRole = () => {
1814
1973
  if (roleEmitted) return;
1815
- sendChunk({ role: "assistant" });
1974
+ sendChunk({ role: "assistant" }, null);
1816
1975
  roleEmitted = true;
1817
1976
  };
1818
1977
  let _evCount = 0;
1819
1978
  session.subscribe((event) => {
1820
1979
  const ev = event;
1980
+ autoRetry.observe(ev);
1821
1981
  const endedMessage = ev.message ?? (Array.isArray(ev.messages) ? ev.messages[ev.messages.length - 1] : null);
1822
1982
  if (endedMessage?.stopReason === "error" && typeof endedMessage.errorMessage === "string" && endedMessage.errorMessage.length > 0) capturedModelError = endedMessage.errorMessage;
1823
1983
  if (ev.type === "queue_update") {
@@ -1835,7 +1995,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1835
1995
  steering,
1836
1996
  follow_up: followUp,
1837
1997
  ...consumedIds.length > 0 ? { consumed_steer_ids: consumedIds } : {}
1838
- } });
1998
+ } }, null);
1839
1999
  return;
1840
2000
  }
1841
2001
  _evCount++;
@@ -1858,7 +2018,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1858
2018
  ...ev.type === "message_start" || ev.type === "message_end" ? { messageJson: JSON.stringify(ev.message).slice(0, 500) } : {}
1859
2019
  }, "harness session event");
1860
2020
  if (ev.type === "tool_execution_start") {
1861
- sendChunk({ x_tool_execution_start: { tool_call_id: String(ev.toolCallId ?? "") } });
2021
+ sendChunk({ x_tool_execution_start: { tool_call_id: String(ev.toolCallId ?? "") } }, null);
1862
2022
  return;
1863
2023
  }
1864
2024
  if (ev.type === "tool_execution_end") {
@@ -1867,7 +2027,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1867
2027
  tool_call_id: String(ev.toolCallId ?? ""),
1868
2028
  content: text,
1869
2029
  ...ev.isError ? { is_error: true } : {}
1870
- } });
2030
+ } }, null);
1871
2031
  return;
1872
2032
  }
1873
2033
  if (ev.type === "agent_end") {
@@ -1879,11 +2039,11 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1879
2039
  const t = inner?.type;
1880
2040
  if (t === "text_delta") {
1881
2041
  ensureRole();
1882
- sendChunk({ content: inner.delta ?? "" });
2042
+ sendChunk({ content: inner.delta ?? "" }, null);
1883
2043
  return;
1884
2044
  }
1885
2045
  if (t === "thinking_delta") {
1886
- sendChunk({ x_thinking_delta: inner.delta ?? "" });
2046
+ sendChunk({ x_thinking_delta: inner.delta ?? "" }, null);
1887
2047
  return;
1888
2048
  }
1889
2049
  if (t === "toolcall_start") {
@@ -1901,7 +2061,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1901
2061
  name: tc?.name ?? "",
1902
2062
  arguments: ""
1903
2063
  }
1904
- }] });
2064
+ }] }, null);
1905
2065
  return;
1906
2066
  }
1907
2067
  if (t === "toolcall_delta") {
@@ -1910,7 +2070,7 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1910
2070
  sendChunk({ tool_calls: [{
1911
2071
  index: openaiIdx,
1912
2072
  function: { arguments: inner.delta ?? "" }
1913
- }] });
2073
+ }] }, null);
1914
2074
  }
1915
2075
  });
1916
2076
  try {
@@ -1922,6 +2082,22 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1922
2082
  postPrompt
1923
2083
  });
1924
2084
  if (capturedModelError !== null) {
2085
+ const billingBlocked = parseBillingBlockedProviderSignal(capturedModelError);
2086
+ if (billingBlocked) {
2087
+ log.warn({
2088
+ event: "billing_blocked",
2089
+ chatcmpl_id: sessionId,
2090
+ source: "stop_reason_error"
2091
+ }, "forwarding platform billing block to client");
2092
+ emitBillingBlocked({
2093
+ writer,
2094
+ sessionId,
2095
+ created,
2096
+ billingBlocked
2097
+ });
2098
+ writer.write("data: [DONE]\n\n");
2099
+ return;
2100
+ }
1925
2101
  const providerError = toModelProviderError(new Error(capturedModelError));
1926
2102
  log.warn({
1927
2103
  event: "model_provider_error",
@@ -1929,13 +2105,15 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1929
2105
  code: providerError.code,
1930
2106
  upstream_status: providerError.upstreamStatus,
1931
2107
  provider: providerError.provider,
2108
+ retry_attempts: autoRetry.attempts(),
1932
2109
  source: "stop_reason_error"
1933
2110
  }, "forwarding model-provider error to client");
1934
2111
  emitModelProviderError({
1935
2112
  writer,
1936
2113
  sessionId,
1937
2114
  created,
1938
- providerError
2115
+ providerError,
2116
+ retryAttempts: autoRetry.attempts()
1939
2117
  });
1940
2118
  writer.write("data: [DONE]\n\n");
1941
2119
  } else {
@@ -1957,19 +2135,32 @@ function runStream$1({ session, sm, prompt, images, sessionId, created, reqStart
1957
2135
  err
1958
2136
  }, "chat error");
1959
2137
  if (isModelProviderError(err)) {
2138
+ const billingBlocked = parseBillingBlockedProviderSignal(err instanceof Error ? err.message : String(err));
2139
+ if (billingBlocked) {
2140
+ emitBillingBlocked({
2141
+ writer,
2142
+ sessionId,
2143
+ created,
2144
+ billingBlocked
2145
+ });
2146
+ writer.write("data: [DONE]\n\n");
2147
+ return;
2148
+ }
1960
2149
  const providerError = toModelProviderError(err);
1961
2150
  log.warn({
1962
2151
  event: "model_provider_error",
1963
2152
  chatcmpl_id: sessionId,
1964
2153
  code: providerError.code,
1965
2154
  upstream_status: providerError.upstreamStatus,
1966
- provider: providerError.provider
2155
+ provider: providerError.provider,
2156
+ retry_attempts: autoRetry.attempts()
1967
2157
  }, "forwarding model-provider error to client");
1968
2158
  emitModelProviderError({
1969
2159
  writer,
1970
2160
  sessionId,
1971
2161
  created,
1972
- providerError
2162
+ providerError,
2163
+ retryAttempts: autoRetry.attempts()
1973
2164
  });
1974
2165
  } else sendChunk({ content: `\n[error: ${err?.message ?? err}]` }, "stop");
1975
2166
  writer.write("data: [DONE]\n\n");
@@ -2010,7 +2201,7 @@ function emitSessionMessagesTrailer({ writer, sm, baselineMessageCount, sessionI
2010
2201
  * cleanly closes the stream for any OpenAI-shaped reader that ignores the
2011
2202
  * extension field.
2012
2203
  */
2013
- function emitModelProviderError({ writer, sessionId, created, providerError }) {
2204
+ function emitModelProviderError({ writer, sessionId, created, providerError, retryAttempts }) {
2014
2205
  const chunk = {
2015
2206
  id: sessionId,
2016
2207
  object: "chat.completion.chunk",
@@ -2025,7 +2216,25 @@ function emitModelProviderError({ writer, sessionId, created, providerError }) {
2025
2216
  message: providerError.message,
2026
2217
  provider: providerError.provider,
2027
2218
  type: providerError.type,
2028
- upstream_status: providerError.upstreamStatus
2219
+ upstream_status: providerError.upstreamStatus,
2220
+ retry_attempts: retryAttempts
2221
+ }
2222
+ };
2223
+ writer.write(`data: ${JSON.stringify(chunk)}\n\n`);
2224
+ }
2225
+ function emitBillingBlocked({ writer, sessionId, created, billingBlocked }) {
2226
+ const chunk = {
2227
+ id: sessionId,
2228
+ object: "chat.completion.chunk",
2229
+ created,
2230
+ choices: [{
2231
+ index: 0,
2232
+ delta: {},
2233
+ finish_reason: "stop"
2234
+ }],
2235
+ x_billing_blocked: {
2236
+ block_reason: billingBlocked.blockReason,
2237
+ message: billingBlocked.message
2029
2238
  }
2030
2239
  };
2031
2240
  writer.write(`data: ${JSON.stringify(chunk)}\n\n`);
@@ -2127,7 +2336,6 @@ const steerRequestSchema = z.object({
2127
2336
  mode: z.enum(["steer", "follow-up"]).default("steer"),
2128
2337
  text: z.string().optional()
2129
2338
  }).refine((v) => v.content !== void 0 || v.text !== void 0, { message: "content or text is required" });
2130
- z.string().uuid();
2131
2339
  async function handleSteer(request, sessionId, registry, pendingSteerIds) {
2132
2340
  const { log } = requestLogger(request);
2133
2341
  const session = registry.get(sessionId);
@@ -2135,14 +2343,14 @@ async function handleSteer(request, sessionId, registry, pendingSteerIds) {
2135
2343
  let body;
2136
2344
  try {
2137
2345
  body = JSON.parse(await request.text());
2138
- } catch {
2346
+ } catch (_err) {
2139
2347
  return jsonError(400, "invalid json");
2140
2348
  }
2141
2349
  const parsed = steerRequestSchema.safeParse(body);
2142
2350
  if (!parsed.success) return jsonError(400, parsed.error.message);
2143
2351
  const { mode } = parsed.data;
2144
2352
  const { text, images } = await extractContent(parsed.data.content ?? parsed.data.text ?? "", {
2145
- userAgent: request.headers.get("user-agent") ?? void 0,
2353
+ userAgent: request.headers.get("user-agent") ?? null,
2146
2354
  log
2147
2355
  });
2148
2356
  if (!text && images.length === 0) return jsonError(400, "steer has no content");
@@ -2295,7 +2503,10 @@ async function extractImagesFromContent(content, log) {
2295
2503
  for (const part of content) if (part?.type === "input_image") {
2296
2504
  const url = part.image_url ?? part.url;
2297
2505
  if (typeof url === "string" && /^https?:\/\//i.test(url)) try {
2298
- images.push(await fetchImageAsBase64({ url }));
2506
+ images.push(await fetchImageAsBase64({
2507
+ url,
2508
+ userAgent: null
2509
+ }));
2299
2510
  } catch (err) {
2300
2511
  log.warn({
2301
2512
  event: "image_fetch_failed",
@@ -2316,7 +2527,7 @@ async function handleResponses(request, ctx, registry) {
2316
2527
  let parsed;
2317
2528
  try {
2318
2529
  parsed = JSON.parse(await request.text());
2319
- } catch {
2530
+ } catch (_err) {
2320
2531
  return jsonError(400, "invalid json");
2321
2532
  }
2322
2533
  const { input, stream = false, model: modelInput, instructions, x_model: modelSpecInput } = parsed ?? {};
@@ -2388,6 +2599,7 @@ function runStream({ session, prompt, images, responseId, sessionId, modelName,
2388
2599
  const tcByContentIdx = /* @__PURE__ */ new Map();
2389
2600
  let nextOutputIndex = 0;
2390
2601
  let capturedModelError = null;
2602
+ const autoRetry = createAutoRetryObserver(log);
2391
2603
  const send = (event) => {
2392
2604
  writer.write(`data: ${JSON.stringify(event)}\n\n`);
2393
2605
  };
@@ -2396,7 +2608,8 @@ function runStream({ session, prompt, images, responseId, sessionId, modelName,
2396
2608
  event: "model_provider_error",
2397
2609
  code: providerError.code,
2398
2610
  upstream_status: providerError.upstreamStatus,
2399
- provider: providerError.provider
2611
+ provider: providerError.provider,
2612
+ retry_attempts: autoRetry.attempts()
2400
2613
  }, "forwarding model-provider error to client");
2401
2614
  send({
2402
2615
  type: "response.failed",
@@ -2410,7 +2623,8 @@ function runStream({ session, prompt, images, responseId, sessionId, modelName,
2410
2623
  x_model_provider_error: {
2411
2624
  provider: providerError.provider,
2412
2625
  type: providerError.type,
2413
- upstream_status: providerError.upstreamStatus
2626
+ upstream_status: providerError.upstreamStatus,
2627
+ retry_attempts: autoRetry.attempts()
2414
2628
  }
2415
2629
  }
2416
2630
  }
@@ -2446,6 +2660,7 @@ function runStream({ session, prompt, images, responseId, sessionId, modelName,
2446
2660
  };
2447
2661
  session.subscribe((event) => {
2448
2662
  const ev = event;
2663
+ autoRetry.observe(ev);
2449
2664
  const endedMessage = ev.message ?? (Array.isArray(ev.messages) ? ev.messages[ev.messages.length - 1] : null);
2450
2665
  if (endedMessage?.stopReason === "error" && typeof endedMessage.errorMessage === "string" && endedMessage.errorMessage.length > 0) capturedModelError = endedMessage.errorMessage;
2451
2666
  if (ev.type !== "message_update") return;
@@ -2851,4 +3066,4 @@ function chainMiddleware(middlewares) {
2851
3066
  };
2852
3067
  }
2853
3068
  //#endregion
2854
- export { DEFAULT_MODEL, DEFAULT_PROVIDER, DEFAULT_THINKING_LEVEL, KNOWN_PI_PROVIDERS, PI_AGENT_DIR, VALID_THINKING_LEVELS, buildAgentCard, buildModelFromSpec, chainMiddleware, composeHandlers, createAgentExecutor, createMapSessionRegistry, createPrewarm, createProtocolHandlers, createProtocols, deriveTraceContext, getCurrentTraceparent, logger, modelSpecSchema, mountAt, newTraceContext, parseModelSpec, parseTraceId, requestHeaders, requestLogger, requestUrl, resolveRequestModel, runInTraceContext, setCurrentTraceparent, webHandlerToMiddleware, withSpecApiKey };
3069
+ export { BILLING_BLOCK_REASONS, DEFAULT_MODEL, DEFAULT_PROVIDER, DEFAULT_THINKING_LEVEL, KNOWN_PI_PROVIDERS, PI_AGENT_DIR, VALID_THINKING_LEVELS, buildAgentCard, buildModelFromSpec, chainMiddleware, composeHandlers, createAgentExecutor, createMapSessionRegistry, createPrewarm, createProtocolHandlers, createProtocols, deriveTraceContext, getCurrentTraceparent, logger, modelSpecSchema, mountAt, newTraceContext, parseModelSpec, parseTraceId, requestHeaders, requestLogger, requestUrl, resolveRequestModel, runInTraceContext, setCurrentTraceparent, webHandlerToMiddleware, withSpecApiKey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skydiveai/pi-server",
3
- "version": "0.1.0-beta.165",
3
+ "version": "0.1.0-beta.2448",
4
4
  "homepage": "https://skydive.com",
5
5
  "license": "MIT",
6
6
  "author": "Create, Inc.",
@@ -17,20 +17,13 @@
17
17
  },
18
18
  "publishConfig": {
19
19
  "access": "public",
20
- "exports": {
21
- ".": {
22
- "types": "./dist/index.d.mts",
23
- "default": "./dist/index.mjs"
24
- }
25
- },
26
20
  "registry": "https://registry.npmjs.org"
27
21
  },
28
22
  "scripts": {
29
23
  "build": "tsdown",
30
24
  "typecheck": "tsgo --noEmit",
31
25
  "test:unit": "vitest run --passWithNoTests",
32
- "test:ci": "vitest run --coverage --coverage.reporter=lcovonly --reporter=default --reporter=github-actions --minWorkers=1 --maxWorkers=2 --passWithNoTests",
33
- "publish:system-artifacts": "doppler run --preserve-env -- node ../../scripts/anyone/publish-system-artifact.mjs"
26
+ "test:ci": "vitest run --coverage --coverage.reporter=lcovonly --reporter=default --reporter=github-actions --minWorkers=1 --maxWorkers=2 --passWithNoTests"
34
27
  },
35
28
  "dependencies": {
36
29
  "@a2a-js/sdk": "^0.3.13",