@linxiraos/pi-ai 1.2.0 → 1.3.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 (148) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +4 -2
  3. package/THIRD-PARTY-NOTICES.txt +55 -5
  4. package/dist/types/auth/cascade.d.ts +5 -3
  5. package/dist/types/auth/pool.d.ts +3 -3
  6. package/dist/types/auth/select.d.ts +5 -1
  7. package/dist/types/auth/types.d.ts +15 -5
  8. package/dist/types/auth/usage-cache.d.ts +3 -1
  9. package/dist/types/auth-gateway/clients.d.ts +67 -0
  10. package/dist/types/auth-gateway/dispatch.d.ts +28 -10
  11. package/dist/types/auth-gateway/http.d.ts +2 -0
  12. package/dist/types/auth-gateway/index.d.ts +1 -0
  13. package/dist/types/auth-gateway/server.d.ts +9 -4
  14. package/dist/types/auth-gateway/session-state.d.ts +6 -0
  15. package/dist/types/auth-gateway/types.d.ts +16 -2
  16. package/dist/types/dialect/coercion.d.ts +6 -0
  17. package/dist/types/error/classes.d.ts +1 -1
  18. package/dist/types/error/flags.d.ts +15 -0
  19. package/dist/types/error/index.d.ts +1 -0
  20. package/dist/types/error/network-cause.d.ts +40 -0
  21. package/dist/types/images/shared.d.ts +26 -2
  22. package/dist/types/index.d.ts +1 -0
  23. package/dist/types/judgment/native.d.ts +51 -3
  24. package/dist/types/judgment/systemone.d.ts +9 -2
  25. package/dist/types/judgment/text.d.ts +11 -5
  26. package/dist/types/judgment/types.d.ts +5 -1
  27. package/dist/types/judgment/wire.d.ts +16 -3
  28. package/dist/types/provider-session-state.d.ts +2 -2
  29. package/dist/types/providers/anthropic-images.d.ts +7 -0
  30. package/dist/types/providers/anthropic-signature.d.ts +12 -2
  31. package/dist/types/providers/anthropic-state.d.ts +7 -0
  32. package/dist/types/providers/anthropic.d.ts +21 -6
  33. package/dist/types/providers/connection-prewarm.d.ts +15 -0
  34. package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
  35. package/dist/types/providers/cursor-pi-args.d.ts +13 -0
  36. package/dist/types/providers/cursor.d.ts +7 -0
  37. package/dist/types/providers/openai-codex-responses.d.ts +6 -0
  38. package/dist/types/providers/openai-responses.d.ts +9 -0
  39. package/dist/types/providers/openai-shared.d.ts +30 -0
  40. package/dist/types/providers/systemone-server.d.ts +7 -3
  41. package/dist/types/providers/transform-messages.d.ts +26 -5
  42. package/dist/types/registry/oauth/google-antigravity.d.ts +1 -1
  43. package/dist/types/stream.d.ts +0 -2
  44. package/dist/types/types.d.ts +33 -9
  45. package/dist/types/utils/dsml-leak.d.ts +4 -2
  46. package/dist/types/utils/narration.d.ts +9 -0
  47. package/dist/types/utils/openai-http.d.ts +2 -0
  48. package/dist/types/utils/request-body.d.ts +11 -0
  49. package/dist/types/utils/request-compression.d.ts +21 -0
  50. package/dist/types/utils/tool-call-arguments.d.ts +2 -1
  51. package/dist/types/utils/transport-fetch.d.ts +8 -0
  52. package/dist/types/utils.d.ts +1 -1
  53. package/package.json +8 -8
  54. package/src/auth/cascade.ts +23 -13
  55. package/src/auth/pool.ts +3 -3
  56. package/src/auth/refresh.ts +39 -12
  57. package/src/auth/select.ts +8 -2
  58. package/src/auth/types.ts +16 -5
  59. package/src/auth/usage-cache.ts +23 -7
  60. package/src/auth/usage.ts +2 -4
  61. package/src/auth-broker/remote-store.ts +31 -6
  62. package/src/auth-gateway/clients.ts +318 -0
  63. package/src/auth-gateway/dispatch.ts +66 -35
  64. package/src/auth-gateway/http.ts +11 -5
  65. package/src/auth-gateway/index.ts +1 -0
  66. package/src/auth-gateway/routes/embeddings.ts +4 -10
  67. package/src/auth-gateway/routes/images.ts +6 -10
  68. package/src/auth-gateway/routes/rerank.ts +4 -10
  69. package/src/auth-gateway/routes/speech.ts +10 -10
  70. package/src/auth-gateway/routes/systemone.ts +22 -12
  71. package/src/auth-gateway/routes/transcriptions.ts +4 -2
  72. package/src/auth-gateway/routes/video.ts +12 -15
  73. package/src/auth-gateway/server.ts +136 -53
  74. package/src/auth-gateway/session-state.ts +14 -0
  75. package/src/auth-gateway/types.ts +16 -2
  76. package/src/dialect/coercion.ts +16 -0
  77. package/src/dialect/hermes.ts +7 -11
  78. package/src/dialect/qwen3.ts +3 -11
  79. package/src/error/classes.ts +3 -2
  80. package/src/error/flags.ts +24 -3
  81. package/src/error/index.ts +1 -0
  82. package/src/error/network-cause.ts +179 -0
  83. package/src/images/codex-format-directive.md +3 -0
  84. package/src/images/google-antigravity.ts +2 -1
  85. package/src/images/google-generative-ai.ts +4 -2
  86. package/src/images/openai-hosted.ts +25 -2
  87. package/src/images/openai-images.ts +11 -6
  88. package/src/images/shared.ts +77 -3
  89. package/src/index.ts +1 -0
  90. package/src/judgment/chat.ts +17 -1
  91. package/src/judgment/native.ts +182 -12
  92. package/src/judgment/openai-decisions.ts +27 -6
  93. package/src/judgment/systemone.ts +131 -10
  94. package/src/judgment/text.ts +20 -7
  95. package/src/judgment/types.ts +5 -1
  96. package/src/judgment/wire.ts +17 -3
  97. package/src/provider-session-state.ts +2 -2
  98. package/src/providers/amazon-bedrock.ts +4 -1
  99. package/src/providers/anthropic-client.ts +1 -1
  100. package/src/providers/anthropic-images.ts +263 -0
  101. package/src/providers/anthropic-messages-server.ts +84 -1
  102. package/src/providers/anthropic-signature.ts +35 -8
  103. package/src/providers/anthropic-state.ts +13 -0
  104. package/src/providers/anthropic.ts +479 -397
  105. package/src/providers/connection-prewarm.ts +78 -0
  106. package/src/providers/cowork-fetch.ts +5 -1
  107. package/src/providers/cursor/exec-modern.ts +1 -0
  108. package/src/providers/cursor-pi-args.ts +19 -0
  109. package/src/providers/cursor.ts +53 -13
  110. package/src/providers/devin.ts +10 -4
  111. package/src/providers/factory-droid/gemini.ts +9 -3
  112. package/src/providers/google-gemini-cli.ts +11 -1
  113. package/src/providers/google-shared.ts +2 -6
  114. package/src/providers/images-server.ts +6 -1
  115. package/src/providers/ollama.ts +15 -5
  116. package/src/providers/openai-anthropic-shim.ts +1 -0
  117. package/src/providers/openai-chat-server.ts +29 -0
  118. package/src/providers/openai-codex/access-programs.ts +2 -1
  119. package/src/providers/openai-codex-responses.ts +49 -82
  120. package/src/providers/openai-completions.ts +117 -19
  121. package/src/providers/openai-responses.ts +25 -2
  122. package/src/providers/openai-shared.ts +36 -5
  123. package/src/providers/pi-native-server.ts +1 -0
  124. package/src/providers/systemone-server.ts +72 -12
  125. package/src/providers/transform-messages.ts +95 -51
  126. package/src/registry/oauth/google-antigravity.ts +1 -1
  127. package/src/stream.ts +13 -73
  128. package/src/types.ts +33 -9
  129. package/src/usage/claude.ts +2 -9
  130. package/src/usage/github-copilot.ts +26 -4
  131. package/src/usage/openai-codex.ts +13 -7
  132. package/src/usage/opencode-go.ts +7 -8
  133. package/src/usage/xai-oauth.ts +2 -9
  134. package/src/utils/dsml-leak.ts +30 -14
  135. package/src/utils/leaked-thinking-stream.ts +10 -0
  136. package/src/utils/narration.ts +18 -0
  137. package/src/utils/openai-http.ts +27 -18
  138. package/src/utils/request-body.ts +27 -0
  139. package/src/utils/request-compression.ts +202 -0
  140. package/src/utils/request-debug.ts +23 -9
  141. package/src/utils/schema/dereference.ts +51 -18
  142. package/src/utils/schema/draft.ts +10 -2
  143. package/src/utils/schema/json-schema-validator.ts +4 -10
  144. package/src/utils/schema/normalize.ts +6 -9
  145. package/src/utils/tool-call-arguments.ts +2 -1
  146. package/src/utils/transport-fetch.ts +37 -0
  147. package/src/utils/validation.ts +26 -16
  148. package/src/utils.ts +38 -23
@@ -47,7 +47,7 @@ export declare class AnthropicApiError extends ProviderHttpError {
47
47
  }
48
48
  /** Network-level failure (DNS, TLS, socket reset) after retries were exhausted. */
49
49
  export declare class AnthropicConnectionError extends Error {
50
- constructor(cause: unknown);
50
+ constructor(cause: unknown, requestUrl?: string);
51
51
  }
52
52
  /** No response headers arrived within the configured request timeout. */
53
53
  export declare class AnthropicConnectionTimeoutError extends Error {
@@ -68,10 +68,25 @@ export declare function isCodexSteerRejection(message: {
68
68
  api?: Api;
69
69
  errorMessage?: string;
70
70
  }): boolean;
71
+ /**
72
+ * Transport-failure wording that marks an error transient. Covers
73
+ * diagnostics forwarded verbatim by gateways/proxies in other runtimes, e.g.
74
+ * undici's "other side closed" and Go's `net.ErrClosed` ("use of closed
75
+ * network connection", #15385). Also covers OMP's own network-failure
76
+ * sentences from `networkFailureMessage` (offline / unreachable / refused /
77
+ * TLS), which must stay at least as retryable as the bare `Connection error.`
78
+ * text they replaced.
79
+ */
71
80
  export declare const TRANSIENT_TRANSPORT_PATTERN: RegExp;
72
81
  export declare const ANTHROPIC_ACCOUNT_POLICY_PATTERN: RegExp;
73
82
  /** Whether an error message represents an Anthropic account-scoped permission/policy denial. */
74
83
  export declare function isAnthropicAccountPolicyText(text: string, provider?: string, statusArg?: number): boolean;
84
+ /**
85
+ * Anthropic's rejection of a replayed latest assistant turn whose `thinking` or
86
+ * `redacted_thinking` blocks no longer verify. Resending the same turn fails the
87
+ * same way; only a request that changes that turn's reasoning can succeed.
88
+ */
89
+ export declare function isImmutableLatestThinkingText(text: string): boolean;
75
90
  /**
76
91
  * Local llama.cpp / Ollama deterministic tool-call argument JSON parse failure.
77
92
  * The model emitted invalid JSON in a tool call and the server returned HTTP 500
@@ -8,6 +8,7 @@ export * from "./finalize.js";
8
8
  export * from "./flags.js";
9
9
  export * from "./format.js";
10
10
  export * from "./gateway.js";
11
+ export * from "./network-cause.js";
11
12
  export * from "./oauth.js";
12
13
  export * from "./provider.js";
13
14
  export * from "./rate-limit.js";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Local transport diagnosis for a rejected `fetch`.
3
+ *
4
+ * Bun and Node describe the same outage with different shapes — Bun throws a
5
+ * `TypeError` carrying `code: "ETIMEOUT" | "FailedToOpenSocket" |
6
+ * "ConnectionRefused"`, undici nests an `ENOTFOUND`/`ECONNREFUSED` errno under
7
+ * `TypeError: fetch failed` (sometimes through an `AggregateError`), and
8
+ * `fetchWithRetry` may already have flattened the chain into a message-only
9
+ * `Error`. Both the code and the text are inspected so every one of those
10
+ * shapes lands in the right bucket.
11
+ */
12
+ /** Transport-level failure families that get their own user-visible wording. */
13
+ export type NetworkFailureKind = "offline" | "unreachable" | "refused" | "tls";
14
+ /** What {@link classifyNetworkCause} reports about a rejected `fetch`: the failure family plus the bits its sentence needs. */
15
+ export interface NetworkFailure {
16
+ kind: NetworkFailureKind;
17
+ /** Runtime error code when the chain exposed one (`ETIMEOUT`, `FailedToOpenSocket`, …). */
18
+ code: string | undefined;
19
+ /** Short parenthetical detail for the user-visible message. */
20
+ detail: string | undefined;
21
+ /** Host named by the error text, when it named one. */
22
+ host: string | undefined;
23
+ }
24
+ /**
25
+ * Walk an error's `cause`/`errors` graph (bounded depth, cycle-safe) and report
26
+ * the outermost link that describes a local transport failure. Returns
27
+ * `undefined` for anything else, including HTTP-level provider errors.
28
+ */
29
+ export declare function classifyNetworkCause(err: unknown): NetworkFailure | undefined;
30
+ /**
31
+ * User-visible sentence for a network failure, or `undefined` when the error is
32
+ * not one. `requestUrl` wins over any host parsed out of the error text.
33
+ */
34
+ export declare function networkFailureMessage(err: unknown, requestUrl?: string): string | undefined;
35
+ /**
36
+ * Re-describe a rejected `fetch` with {@link networkFailureMessage}, keeping the
37
+ * original error as `cause`. Anything that is not a local transport failure is
38
+ * returned untouched, so provider/HTTP errors keep their identity.
39
+ */
40
+ export declare function networkFailureError(err: unknown, requestUrl?: string): unknown;
@@ -1,7 +1,8 @@
1
1
  import type { FetchImpl, Model, Usage } from "@linxiraos/pi-catalog/types";
2
2
  import type { ApiKey } from "../auth-retry.js";
3
3
  import * as AIError from "../error/index.js";
4
- import type { GeneratedImage } from "./types.js";
4
+ import type { ImageContent } from "../types.js";
5
+ import type { GeneratedImage, ImageInput } from "./types.js";
5
6
  export declare class ImageApiError extends AIError.ProviderHttpError {
6
7
  readonly name = "ImageApiError";
7
8
  }
@@ -38,6 +39,29 @@ export declare function decodeImageResponse(value: unknown, fetch: FetchImpl, si
38
39
  * multi-MB payloads are never fully decoded just to be labelled.
39
40
  */
40
41
  export declare function sniffBase64ImageMimeType(base64: string): string | undefined;
41
- export declare function toDataUrl(image: GeneratedImage): string;
42
+ /**
43
+ * Validate provider-bound image input without changing it. Accepts non-empty standard base64
44
+ * with canonical trailing bits and optional padding, and case-insensitive image MIME syntax
45
+ * (RFC 6838 restricted names). Does not sniff the MIME type, decode image pixels, or enforce
46
+ * provider-specific format support; data-URL transports additionally go through `toDataUrl`.
47
+ *
48
+ * @throws {AIError.ValidationError} If the base64 data or image MIME type is invalid.
49
+ */
50
+ export declare function validateImageInput(image: ImageInput): void;
51
+ /**
52
+ * Encode validated image input as a base64 data URL.
53
+ *
54
+ * @throws {AIError.ValidationError} If the input is invalid or its MIME type cannot survive a data URL.
55
+ */
56
+ export declare function toDataUrl(image: ImageInput): string;
57
+ /**
58
+ * Whether a MIME type survives a data URL intact: a raw `#` starts a URL fragment and
59
+ * data-URL consumers leave a `%23` escape undecoded, so no data-URL transport preserves it.
60
+ */
61
+ export declare function isDataUrlMimeType(mimeType: string): boolean;
62
+ /** Whether a value is an embedded image with valid base64 data, MIME type, and detail. */
63
+ export declare function isEmbeddedImageContent(image: unknown): image is ImageContent;
64
+ /** Convert one validated embedded image to a lowercase-MIME inline data URL. */
65
+ export declare function embeddedImageDataUrl(image: unknown): string;
42
66
  export declare function resolveOpenAIImageSize(aspectRatio?: string, imageSize?: string): string | undefined;
43
67
  export {};
@@ -64,6 +64,7 @@ export * from "./utils/anthropic-auth.js";
64
64
  export * from "./utils/event-stream.js";
65
65
  export * from "./utils/glyph-codec.js";
66
66
  export * from "./utils/openrouter-headers.js";
67
+ export * from "./utils/narration.js";
67
68
  export * from "./utils/schema/index.js";
68
69
  export * from "./utils/thinking-loop.js";
69
70
  export * from "./utils/tool-call-loop-guard.js";
@@ -1,9 +1,32 @@
1
+ /**
2
+ * Native judgment client: the {@link Judge} backend for catalog models whose
3
+ * API answers typed questions directly ({@link isJudgmentApi}).
4
+ *
5
+ * Each judgment API is one wire dialect: TypeSafe's System One route
6
+ * (`POST /v1/systemone`), OpenRouter Decisions (`POST /api/alpha/decisions`,
7
+ * same body), Cloudflare Workers AI (`POST /ai/run/{model}`, same body plus
8
+ * top-level data URL images; `systemone.ts`), and OpenAI Decisions
9
+ * (`POST /v1/decisions`, translated both ways; `openai-decisions.ts`).
10
+ * Credentials flow through {@link withAuth}, so a stored key rotates on
11
+ * 401/403 exactly like chat providers; transient 429/5xx responses retry with
12
+ * bounded, `retry-after`-aware backoff. Endpoints that cap the questions per
13
+ * request (the catalog's `judgment-questions-max`, e.g. OpenJEV at 16) get
14
+ * oversized requests split into chunks under `maxQuestions`, at most
15
+ * {@link CHUNK_PARALLEL} in flight at once.
16
+ *
17
+ * Environment (mirrors the official TypeSafe SDK, for judges built without an
18
+ * explicit model): `TYPESAFE_API_KEY` is resolved by the auth registry
19
+ * (`rules/auth/typesafe.kdl`), `TYPESAFE_BASE_URL` overrides the API root,
20
+ * `TYPESAFE_DEFAULT_MODEL` the model.
21
+ */
22
+ import { type JudgmentImageInput } from "@linxiraos/pi-catalog/compat/judgment-images";
1
23
  import type { Api, FetchImpl } from "@linxiraos/pi-catalog/types";
2
24
  import { type ApiKey } from "../auth-retry.js";
3
25
  import * as AIError from "../error/index.js";
4
- import type { Model, Usage } from "../types.js";
26
+ import type { ImageContent, Model, Usage } from "../types.js";
5
27
  import { type Judge, type JudgeOptions, type JudgmentRequest, type JudgmentResult, type Questions } from "./types.js";
6
- import type { JudgmentWire } from "./wire.js";
28
+ import type { JudgmentImageAssets, JudgmentWire } from "./wire.js";
29
+ export type { JudgmentImageAssets } from "./wire.js";
7
30
  export declare const TYPESAFE_PROVIDER = "typesafe";
8
31
  export declare const TYPESAFE_DEFAULT_MODEL = "jev-latest";
9
32
  declare const JUDGMENT_WIRES: {
@@ -16,6 +39,19 @@ declare const JUDGMENT_WIRES: {
16
39
  export type JudgmentApi = keyof typeof JUDGMENT_WIRES;
17
40
  /** Whether a catalog API answers judgments natively. */
18
41
  export declare function isJudgmentApi(api: Api): api is JudgmentApi;
42
+ /** Whether both the native protocol and catalog model support image input. */
43
+ export declare function nativeJudgmentSupportsImages(model: Model): boolean;
44
+ /**
45
+ * Whether `model`'s native judgment API takes these exact images: image support plus every documented
46
+ * transport and catalog bound. A judge chain skips a candidate that would refuse them instead of failing on it.
47
+ */
48
+ export declare function nativeJudgmentAcceptsImages(model: Model, images: readonly ImageContent[]): boolean;
49
+ /**
50
+ * Reject images that `model`'s native judgment API cannot take — no image support, an unsupported or
51
+ * unreferenced asset, or a transport or catalog bound exceeded — before any credential or upstream work.
52
+ * @throws {AIError.ValidationError} naming the reason.
53
+ */
54
+ export declare function checkNativeJudgmentImages(model: Model, request: JudgmentRequest, imageAssets?: JudgmentImageAssets): void;
19
55
  /** `TYPESAFE_BASE_URL` when set, else the public API root; trailing slashes stripped. */
20
56
  export declare function typesafeBaseUrl(): string;
21
57
  /** `TYPESAFE_DEFAULT_MODEL` when set, else {@link TYPESAFE_DEFAULT_MODEL}. */
@@ -43,9 +79,18 @@ export interface NativeJudgeOptions extends NativeJudgeConnection {
43
79
  * requests are split. Unset: no cap.
44
80
  */
45
81
  maxQuestions?: number;
82
+ /**
83
+ * Whether images are enabled for this judge. `false` disables image input; `true` enables
84
+ * dialects that require explicit opt-in but cannot enable a wire without image support.
85
+ */
86
+ supportsImages?: boolean;
87
+ /** Catalog-selected image bounds, applied in addition to the wire's transport constraints. */
88
+ imageInput?: JudgmentImageInput;
46
89
  }
47
90
  /** Per-call options for {@link NativeJudge.judge}. */
48
91
  export interface NativeJudgeCallOptions extends JudgeOptions {
92
+ /** Assets keyed by their existing references; wires without asset transport reject them. */
93
+ imageAssets?: JudgmentImageAssets;
49
94
  /**
50
95
  * Called before a split judgment rejects, with the summed usage of the chunks that had already
51
96
  * succeeded: billed upstream, but their answers are discarded with the failure. Not called when
@@ -60,6 +105,8 @@ export declare class JudgmentApiError extends AIError.ProviderHttpError {
60
105
  export declare class NativeJudge implements Judge {
61
106
  #private;
62
107
  readonly label: string;
108
+ /** True when the selected native wire and model or direct-call option permit image input. */
109
+ readonly supportsImages?: boolean;
63
110
  readonly api: JudgmentApi;
64
111
  readonly provider: string;
65
112
  /** Wire model id sent with every request. */
@@ -67,6 +114,8 @@ export declare class NativeJudge implements Judge {
67
114
  readonly baseUrl: string;
68
115
  constructor(options: NativeJudgeOptions);
69
116
  judge<Q extends Questions>(request: JudgmentRequest<Q>, options?: NativeJudgeCallOptions): Promise<JudgmentResult<Q>>;
117
+ /** Effective image detail for cache identity after applying this wire's forwarding behavior. */
118
+ effectiveImageDetail(detail: ImageContent["detail"]): ImageContent["detail"];
70
119
  }
71
120
  /**
72
121
  * The {@link NativeJudge} for a catalog judge model: its API's dialect, wire id
@@ -77,4 +126,3 @@ export declare class NativeJudge implements Judge {
77
126
  * @throws ConfigurationError when the model's API is not a judgment API.
78
127
  */
79
128
  export declare function createNativeJudge(model: Model, connection: NativeJudgeConnection): NativeJudge;
80
- export {};
@@ -1,7 +1,14 @@
1
- import { type JudgmentWire } from "./wire.js";
1
+ import { type JudgmentRequest } from "./types.js";
2
+ import { type JudgmentImageAssets, type JudgmentWire } from "./wire.js";
3
+ /**
4
+ * The subset of `assets` that `request` references. TypeSafe substitutes a reference wherever it
5
+ * occurs as text in a string of `state`, a question's `instructions`, or its `criteria` (never in
6
+ * object keys) and rejects a request carrying an unreferenced asset.
7
+ */
8
+ export declare function referencedImageAssets(request: JudgmentRequest, assets: JudgmentImageAssets): JudgmentImageAssets;
2
9
  /** TypeSafe's own route, shared by every System One server. */
3
10
  export declare const typesafeWire: JudgmentWire;
4
- /** OpenRouter Decisions: the System One body under `/api/alpha`. */
11
+ /** OpenRouter Decisions: System One body under `/api/alpha`, with image parts preserved in state. */
5
12
  export declare const openrouterDecisionsWire: JudgmentWire;
6
13
  /** Cloudflare Workers AI decision models (Clef) through the account-scoped `ai/run` route. */
7
14
  export declare const workersAiWire: JudgmentWire;
@@ -1,8 +1,10 @@
1
- import type { Usage } from "../types.js";
1
+ import type { ImageContent, Usage } from "../types.js";
2
2
  import { type Answer, type Judge, type JudgeOptions, type JudgmentRequest, type JudgmentResult, type JudgmentState, type Question, type Questions } from "./types.js";
3
3
  export interface TextPrompt {
4
4
  system: string;
5
5
  user: string;
6
+ /** Images are kept as distinct content blocks, never rendered into JSON text. */
7
+ images?: readonly ImageContent[];
6
8
  /** Format-correction attempt; chat backends may enforce tool suppression on the wire. */
7
9
  retry?: boolean;
8
10
  }
@@ -16,6 +18,8 @@ export interface TextBackend {
16
18
  readonly api: string;
17
19
  readonly provider: string;
18
20
  readonly model: string;
21
+ /** Whether this backend can send image content on the wire. */
22
+ readonly supportsImages?: boolean;
19
23
  /** State guard for agent-tuned chat models; on-device classifier workers may disable it. */
20
24
  readonly guardState?: boolean;
21
25
  /** Format-correction retries after a completion cannot be parsed. */
@@ -26,10 +30,11 @@ export interface TextBackend {
26
30
  export declare function renderJudgmentState(state: JudgmentState): string;
27
31
  /**
28
32
  * Render a request: the system prompt carries the preamble and question
29
- * definitions (constant across states, so prompt caches hit); the user message
30
- * carries XML-field state followed by the answer-format cue. Small models act
31
- * on a bare request (answer it, emit tool calls) instead of classifying it —
32
- * explicit tags and the trailing cue keep them on task.
33
+ * definitions (constant across states, so prompt caches hit); the user text
34
+ * carries XML-field state followed by the answer-format cue. Images remain
35
+ * separate content blocks. Small models act on a bare request (answer it,
36
+ * emit tool calls) instead of classifying it — explicit tags and the trailing
37
+ * cue keep them on task.
33
38
  */
34
39
  export declare function renderJudgmentPrompt(request: JudgmentRequest, options?: {
35
40
  guardState?: boolean;
@@ -53,6 +58,7 @@ export declare function splitAnswerLines(text: string, ids: readonly string[]):
53
58
  export declare function parseAnswer(id: string, question: Question, reply: string): Answer;
54
59
  export declare class TextJudge implements Judge {
55
60
  #private;
61
+ readonly supportsImages?: boolean;
56
62
  readonly label: string;
57
63
  constructor(backend: TextBackend);
58
64
  judge<Q extends Questions>(request: JudgmentRequest<Q>, options?: JudgeOptions): Promise<JudgmentResult<Q>>;
@@ -11,7 +11,7 @@
11
11
  * bridge ({@link TextJudge}) renders the same questions into keyword prompts
12
12
  * for an ordinary chat model.
13
13
  */
14
- import type { Usage } from "../types.js";
14
+ import type { ImageContent, Usage } from "../types.js";
15
15
  /** JSON-compatible value; readonly containers are accepted so `as const` state passes through. */
16
16
  export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
17
17
  readonly [key: string]: JsonValue;
@@ -73,6 +73,8 @@ export type AnswerFor<Q extends Question> = Q extends ChoiceQuestion<infer L> ?
73
73
  export interface JudgmentRequest<Q extends Questions = Questions> {
74
74
  state: JudgmentState;
75
75
  questions: Q;
76
+ /** Images evaluated alongside the state; never part of the JSON state itself. */
77
+ images?: readonly ImageContent[];
76
78
  }
77
79
  export interface JudgmentResult<Q extends Questions = Questions> {
78
80
  /** Transport that produced the answers (a judgment api such as `typesafe`, or the chat model's api). */
@@ -89,6 +91,8 @@ export interface JudgeOptions {
89
91
  }
90
92
  /** Answers typed questions about a state. Implementations are stateless and safe to share. */
91
93
  export interface Judge {
94
+ /** Whether this backend can consume separate image content blocks. */
95
+ readonly supportsImages?: boolean;
92
96
  /** Backend description for logs, e.g. `typesafe/jev-latest`. */
93
97
  readonly label: string;
94
98
  judge<Q extends Questions>(request: JudgmentRequest<Q>, options?: JudgeOptions): Promise<JudgmentResult<Q>>;
@@ -3,7 +3,8 @@
3
3
  * encodes, and decodes one request for each API it serves. Internal to the
4
4
  * judgment module; `native.ts` maps every judgment API to its dialect.
5
5
  */
6
- import type { Usage } from "../types.js";
6
+ import type { ImageContent, Usage } from "../types.js";
7
+ import type { JudgmentImageInput } from "@linxiraos/pi-catalog/compat/judgment-images";
7
8
  import type { JudgmentRequest } from "./types.js";
8
9
  /** Where one attempt posts and which bearer token it sends. */
9
10
  export interface JudgmentTarget {
@@ -20,6 +21,8 @@ export interface DecodedJudgment {
20
21
  refused: readonly string[];
21
22
  usage: Usage;
22
23
  }
24
+ /** Image assets kept separately from judgment state for asset-aware native wires. */
25
+ export type JudgmentImageAssets = Readonly<Record<string, ImageContent>>;
23
26
  /** How one judgment API addresses, encodes, and decodes a request. */
24
27
  export interface JudgmentWire {
25
28
  /**
@@ -27,8 +30,18 @@ export interface JudgmentWire {
27
30
  * Throws when the credential cannot address the endpoint (e.g. no account id).
28
31
  */
29
32
  target(baseUrl: string, model: string, key: string): JudgmentTarget;
30
- /** Request body for `model`. */
31
- encode(request: JudgmentRequest, model: string): unknown;
33
+ /** Request body for `model`, with optional asset context for dialects that preserve references. */
34
+ encode(request: JudgmentRequest, model: string, imageAssets?: JudgmentImageAssets): unknown;
35
+ /** Headers this dialect adds to requests that carry images. */
36
+ imageHeaders?: Readonly<Record<string, string>>;
37
+ /** Image input this dialect accepts; absent means text-only. */
38
+ images?: JudgmentImageInput;
39
+ /** Whether raw asset maps can be transported without loss by this dialect. */
40
+ supportsImageAssets?: boolean;
41
+ /** Whether image support must be enabled explicitly for direct NativeJudge construction. */
42
+ imagesRequireOptIn?: boolean;
43
+ /** Whether this wire drops per-image detail metadata. */
44
+ ignoresImageDetail?: boolean;
32
45
  /** The response's answers and usage; `undefined` when the payload is not this API's response shape. */
33
46
  decode(payload: unknown): DecodedJudgment | undefined;
34
47
  }
@@ -18,8 +18,8 @@
18
18
  * siblings are endpoint-scoped and stay: `strictToolsDisabled`
19
19
  * (grammar-too-large 400 for the model's tool schema),
20
20
  * `replayUnsignedThinkingDisabled` / `thinkingReplayDisabled` (the endpoint
21
- * is a signing proxy), `prefixDroppedThinkingBlocks` (blocks a prefix-binding
22
- * 400 made the client strip).
21
+ * is a signing proxy), `prefixDroppedThinkingBlocks` (blocks a rejected
22
+ * replay made the client strip).
23
23
  * - **OpenAI Responses** — the `previous_response_id` chain baselines are
24
24
  * account-scoped: a stored response belongs to the account that created it.
25
25
  * Strict-tools / reasoning-effort fallbacks, replay warmup and the chaining
@@ -0,0 +1,7 @@
1
+ import type { Context, Model } from "../types.js";
2
+ /**
3
+ * Clamp every image a Claude reader would receive to the host's contract.
4
+ * Limits the model's policy leaves unset fall back to the canonical Anthropic
5
+ * API. Returns `context` itself when nothing needed resizing.
6
+ */
7
+ export declare function prepareAnthropicImageContext(context: Context, model: Model<"anthropic-messages"> | Model<"google-gemini-cli">): Promise<Context>;
@@ -1,14 +1,17 @@
1
1
  /**
2
- * Recovers the serving model id Anthropic embeds in thinking-block signatures.
2
+ * Reads the cleartext header Anthropic embeds in thinking-block signatures.
3
3
  *
4
4
  * A signature is a base64 protobuf: outer `{1: version, 2: envelope}`, envelope
5
5
  * `{1: header, 2..5: nonce/ciphertext/tag}`, header
6
- * `{1: schema, 5: hash, 6: model, 8: "thinking", 11: org uuid, 15: profile, 21: unix ts}`.
6
+ * `{1: schema, 5: hash, 6: model, 8: kind, 11: org uuid, 15: profile, 21: unix ts}`.
7
7
  * Field 6 names the model that actually produced the block — a concrete
8
8
  * snapshot (`claude-haiku-4-5-20251001`) or an internal codename
9
9
  * (`numbat-v6-…`) — which lets a caller notice a gateway serving something other
10
10
  * than the requested model. The v4 outer format (`CAQS…`) ships a header
11
11
  * without field 6; the decoder then yields nothing.
12
+ * Field 8 names the block kind: `thinking` for reasoning, `narration` for the
13
+ * user-facing prose the API converts into a thinking block when the model
14
+ * writes it between tool calls (connector-text summarization).
12
15
  */
13
16
  /**
14
17
  * Model id embedded in an Anthropic thinking signature, or undefined when the
@@ -16,6 +19,13 @@
16
19
  * nothing resembling a model id.
17
20
  */
18
21
  export declare function servedModelFromAnthropicSignature(signature: string): string | undefined;
22
+ /**
23
+ * Whether an Anthropic thinking signature marks its block as narration: prose
24
+ * the model addressed to the user between tool calls, which the API returns as
25
+ * a thinking block. Only an exact `narration` kind matches; any other or
26
+ * unreadable header stays reasoning.
27
+ */
28
+ export declare function isAnthropicNarrationSignature(signature: string): boolean;
19
29
  /**
20
30
  * Model id from an OpenRouter reasoning item — a `reasoning_details` entry on
21
31
  * chat completions or a Responses `reasoning` output item — which forwards the
@@ -7,6 +7,13 @@ export declare const ANTHROPIC_OPAQUE_CLIENT_ANCHOR_KEY = "anthropic-opaque-clie
7
7
  export declare function normalizeAnthropicBaseUrl(baseUrl?: string): string | undefined;
8
8
  /** Resolve the endpoint for a first-party Anthropic model without loading its provider implementation. */
9
9
  export declare function resolveDirectAnthropicBaseUrl(model: Model<Api>): string;
10
+ /**
11
+ * Whether a request to `model` reaches the official endpoint its compat was built for. `compat.officialEndpoint`
12
+ * classifies the configured `baseUrl`, but a first-party Anthropic model is rerouted at request time by
13
+ * `ANTHROPIC_BASE_URL` or Foundry's `FOUNDRY_BASE_URL` without rebuilding compat. Gates official-only host
14
+ * limits such as the catalog's inline image byte and image count budgets.
15
+ */
16
+ export declare function reachesOfficialEndpoint(model: Model<Api>): boolean;
10
17
  /** Build the endpoint-and-model-scoped Anthropic session-state key. */
11
18
  export declare function anthropicProviderSessionStateKey(baseUrl: string, modelId: string): string;
12
19
  /** Re-arm fast mode across materialized Anthropic session-state entries. */
@@ -1,5 +1,5 @@
1
1
  import type { Effort } from "@linxiraos/pi-catalog/effort";
2
- import type { AnthropicCompactionRequest, AnthropicOutputEffort, FetchImpl, Message, Model, ServiceTier, SimpleStreamOptions, StreamFunction, StreamOptions, Usage } from "../types.js";
2
+ import type { AnthropicCompactionRequest, AnthropicOutputEffort, Api, FetchImpl, Message, Model, ServiceTier, SimpleStreamOptions, StreamFunction, StreamOptions, Usage } from "../types.js";
3
3
  import { type AnthropicFetchOptions, type AnthropicMessagesClientLike } from "./anthropic-client.js";
4
4
  import { type FallbackParam, type MessageParam, type TextBlockParam } from "./anthropic-wire.js";
5
5
  import { resolvesToOfficialAnthropicEndpoint, supportsAnthropicCompaction, supportsAnthropicCompactionOnClient } from "./anthropic-compaction.js";
@@ -118,7 +118,8 @@ export interface AnthropicOptions extends StreamOptions {
118
118
  * header is auto-attached; the response parser then honors mid-stream
119
119
  * `fallback` content blocks and `usage.iterations` for served-model
120
120
  * promotion and per-attempt pricing. Opt-in ONLY — leaving this
121
- * undefined preserves the pre-fallback behavior on every code path.
121
+ * undefined sends neither and keeps both off; a `fallback` block in the
122
+ * response (a gateway opted in) is still persisted as a replay boundary.
122
123
  */
123
124
  fallbacks?: FallbackParam[];
124
125
  }
@@ -132,6 +133,8 @@ export type AnthropicClientOptionsArgs = {
132
133
  dynamicHeaders?: Record<string, string>;
133
134
  isOAuth?: boolean;
134
135
  hasTools?: boolean;
136
+ /** Whether a tool in ANTHROPIC_BUFFERED_INPUT_TOOLS is present; suppresses the request-wide fine-grained streaming beta. */
137
+ hasBufferedInputTools?: boolean;
135
138
  thinkingEnabled?: boolean;
136
139
  thinkingDisplay?: AnthropicThinkingDisplay;
137
140
  disableStrictTools?: boolean;
@@ -240,6 +243,17 @@ type SystemBlockOptions = {
240
243
  export declare function buildAnthropicSystemBlocks(systemPrompt: readonly string[] | undefined, options?: SystemBlockOptions): AnthropicSystemBlock[] | undefined;
241
244
  export declare function normalizeExtraBetas(betas?: string[] | string): string[];
242
245
  export declare function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): AnthropicClientOptionsResult;
246
+ /**
247
+ * Warm-up target for a first-party `anthropic` model: the effective official
248
+ * API URL plus the TLS options real requests use (sockets pool per TLS
249
+ * configuration, so a warm-up made with other options is never reused).
250
+ * `undefined` when requests resolve elsewhere — `ANTHROPIC_BASE_URL` or
251
+ * Foundry gateways never receive unsolicited probes.
252
+ */
253
+ export declare function resolveAnthropicPrewarmTarget(model: Model<Api>): {
254
+ url: string;
255
+ init: RequestInit;
256
+ } | undefined;
243
257
  /**
244
258
  * True when enabled thinking on `model` is budget thinking
245
259
  * (`thinking.type: "enabled"` with `budget_tokens`) rather than adaptive.
@@ -283,10 +297,11 @@ export type AnthropicMessageParam = MessageParam;
283
297
  * Serialize zeta {@link Message}s to Anthropic wire messages.
284
298
  *
285
299
  * `opts.serverSideFallbackEnabled` — when the CURRENT request itself
286
- * opts into the server-side-fallback beta chain. Only then may a persisted
287
- * `fallback` content block from a prior turn be replayed on the wire;
288
- * otherwise the block is dropped to avoid a 400 on non-fallback requests
289
- * that don't send the beta.
300
+ * opts into the server-side-fallback beta chain. Only then, and only on the
301
+ * official endpoint, may a persisted `fallback` content block from a prior
302
+ * turn be replayed on the wire; otherwise the block is dropped to avoid a 400
303
+ * on requests that don't send the beta, and its turn keeps one model's
304
+ * reasoning (see {@link collapseFallbackSegments}).
290
305
  *
291
306
  * `opts.replayCompaction` — replay a user-role compaction summary that
292
307
  * carries an {@link AnthropicCompactionPayload} from this provider as a
@@ -0,0 +1,15 @@
1
+ import type { Api, FetchImpl, Model } from "../types.js";
2
+ /**
3
+ * Opens, or keeps alive, a pooled connection to {@link model}'s API host so
4
+ * the next turn skips DNS + TCP + TLS. Sends a credential-less `HEAD` (nothing
5
+ * is billed; hosts answer 401/404/405) through the same transport — proxy,
6
+ * extra CA, Cowork profile, TLS options — as real requests, because sockets
7
+ * pool per transport configuration and a warm-up made any other way (e.g.
8
+ * `fetch.preconnect`, which also throws on Windows) is never reused.
9
+ * Throttled per origin; best effort, never throws.
10
+ */
11
+ export declare function prewarmModelConnection(model: Model<Api>, options?: {
12
+ fetch?: FetchImpl;
13
+ }): Promise<void>;
14
+ /** Test hook: forgets every warm-up so the next call is not throttled. */
15
+ export declare function resetConnectionPrewarmForTesting(): void;
@@ -17,7 +17,7 @@ import type { ToolResultMessage } from "../../types.js";
17
17
  * virtual registry. Re-exported here because this is where the frame builders
18
18
  * and their translation are consumed together.
19
19
  */
20
- export { cursorExecReadPath, cursorRawReadPath, omitUndefinedArgs, piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piReadPathHasRange, piTimeout, shellTimeoutSeconds, } from "../cursor-pi-args.js";
20
+ export { cursorExecReadPath, cursorRawReadPath, omitUndefinedArgs, piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piReadPathHasRange, piTimeout, shellDeadlineSeconds, shellTimeoutSeconds, } from "../cursor-pi-args.js";
21
21
  /** Flatten a tool result's content into the single `output` string the Pi frames carry. */
22
22
  export declare function piOutputText(toolResult: ToolResultMessage): string;
23
23
  /**
@@ -129,6 +129,19 @@ export declare function piTimeout(timeout: number | undefined): number | undefin
129
129
  * budgets round up: 0 seconds means "no deadline" to the bash tool.
130
130
  */
131
131
  export declare function shellTimeoutSeconds(timeoutMs: number | undefined): number | undefined;
132
+ /**
133
+ * The bash-tool kill deadline, in seconds, for a Cursor `ShellArgs`/`ShellStreamArgs` frame.
134
+ *
135
+ * Cursor's `timeout` carries the model's `block_until_ms`, which Cursor's Shell tool documents as
136
+ * how long to block before moving the command to the background, not as a kill deadline; the kill
137
+ * deadline is `hard_timeout`. Without one, a short wait (`block_until_ms: 5000` on a build) must not
138
+ * kill the command: the deadline stays the bash default (`undefined`), raised to the wait only when
139
+ * the wait is longer.
140
+ */
141
+ export declare function shellDeadlineSeconds(args: {
142
+ timeout: number;
143
+ hardTimeout?: number;
144
+ }): number | undefined;
132
145
  /**
133
146
  * Drop keys whose value is `undefined` so optional local-tool kwargs stay
134
147
  * absent rather than present-as-undefined.
@@ -218,6 +218,13 @@ export declare function mergeCursorMcpToolCallArgs(streamed: Record<string, unkn
218
218
  * call handed to an external executor is only a handoff while the marker is
219
219
  * absent, and the consumer that runs it needs a runnable block.
220
220
  *
221
+ * A frame whose `toolCallId` already has a block in `output` is the server
222
+ * re-dispatching that call (a new `execId`, e.g. a `read` re-sent once a
223
+ * parallel shell created the file). It adds no block: a second block with the
224
+ * same id makes every rebuilt history replay the call twice, which Cursor
225
+ * rejects (issue #15749). The re-run's result supersedes the first one in the
226
+ * host's result buffer.
227
+ *
221
228
  * Exported for tests to exercise ordering with adjacent text/thinking blocks.
222
229
  */
223
230
  export declare function synthesizeCursorExecToolCall(output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, toolCallId: string, toolName: string, args: Record<string, unknown>, { executed }?: {
@@ -6,6 +6,12 @@ export type { CodexAttestationProvider } from "./openai-codex-attestation.js";
6
6
  import type { ResponseInput } from "./openai-responses-wire.js";
7
7
  export interface OpenAICodexResponsesOptions extends StreamOptions {
8
8
  reasoning?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
9
+ /**
10
+ * Run with the least reasoning the model allows; takes precedence over
11
+ * `reasoning`. Sends the `none` tier where the catalog's disable mode is
12
+ * `none-effort`, else the model's lowest supported effort.
13
+ */
14
+ disableReasoning?: boolean;
9
15
  reasoningSummary?: "auto" | "concise" | "detailed" | null;
10
16
  /** Explicit `reasoning.context` replay scope. Omitted by default so Codex applies its native request policy. */
11
17
  reasoningContext?: CodexReasoningContext;
@@ -114,6 +114,15 @@ interface OpenAIResponsesChainState {
114
114
  /** Set once chaining is judged unsupported for this session (circuit breaker). */
115
115
  disabled: boolean;
116
116
  }
117
+ /**
118
+ * Whether the next request for `model` replays native Responses history items
119
+ * (encrypted reasoning, item ids), as {@link buildParams} decides it. A session
120
+ * is cold, rebuilding turns from message content, from the first request on a
121
+ * connection-bound host (`compat.connectionBoundNativeHistory`, GitHub Copilot)
122
+ * and after `close()` on any host, until a response succeeds. Without session
123
+ * state every request replays.
124
+ */
125
+ export declare function openAIResponsesReplaysNativeHistory(model: Pick<Model, "api" | "provider" | "compat"> | null | undefined, providerSessionState: Map<string, ProviderSessionState> | undefined): boolean;
117
126
  /**
118
127
  * Set the process-wide storage default per provider id, used when a request
119
128
  * leaves `storeResponses` unset. Hosts call this from their settings so every
@@ -399,6 +399,22 @@ interface StrictToolsRetryContext {
399
399
  strictToolsApplied: boolean;
400
400
  tools: Tool[] | undefined;
401
401
  }
402
+ /**
403
+ * Read OpenRouter's `error.metadata.raw` from a captured non-2xx body.
404
+ *
405
+ * OpenRouter routes to a different upstream per request, so its top-level
406
+ * `error.message` is a generic policy sentence ("Access denied by security
407
+ * policy") and the only place the routed provider's own words appear is
408
+ * `error.metadata.raw`. Providers that proxy through OpenRouter also hang the
409
+ * upstream provider name there.
410
+ *
411
+ * The detail lives in the decoded body captured on the thrown
412
+ * {@link AIError.OpenAIHttpError}, not on the error object itself: no error
413
+ * class in this package carries an `error` property, so reading
414
+ * `error.error.metadata.raw` unconditionally yielded `undefined` and the
415
+ * upstream detail was dropped from every surfaced message.
416
+ */
417
+ export declare function readOpenRouterRawMetadata(capturedErrorResponse: CapturedHttpErrorResponse | undefined): string | undefined;
402
418
  /** Decide whether an OpenAI-family request should retry once with non-strict tools. */
403
419
  export declare function shouldRetryWithoutStrictTools(error: unknown, capturedErrorResponse: CapturedHttpErrorResponse | undefined, context: StrictToolsRetryContext): boolean;
404
420
  export declare const OPENAI_RESPONSES_PROGRESS_EVENT_TYPES: ReadonlySet<string>;
@@ -525,6 +541,20 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
525
541
  * Callers gate on {@link isHarmonyDialectModel}. Items are copied, not mutated.
526
542
  */
527
543
  export declare function escapeReplayedControlTokens(items: ResponseInput): ResponseInput;
544
+ /**
545
+ * Whether a same-provider assistant turn may replay its native items while the
546
+ * provider session is still cold (#489). A cold session rebuilds turns from
547
+ * message content because some backends bind native items to one connection
548
+ * (GitHub Copilot: `401 input item does not belong to this connection`, #488).
549
+ * Binding needs server-issued state that survives replay sanitization: an
550
+ * `encrypted_content` blob or an item id. A turn with neither, whose every
551
+ * reasoning item carries plaintext `reasoning_text`, has nothing to bind and is
552
+ * exactly what the server receives once the session warms; rebuilding it would
553
+ * drop that reasoning and change the prompt prefix the server cached.
554
+ * Summary-only reasoning keeps the rebuild: it is no evidence of a server that
555
+ * returns plaintext reasoning.
556
+ */
557
+ export declare function isColdReplayableResponsesTurn(items: ResponseInput): boolean;
528
558
  export declare function buildResponsesInput<TApi extends Api>(options: BuildResponsesInputOptions<TApi>): ResponseInput;
529
559
  /**
530
560
  * Non-empty `reasoning_text` shipped for a synthesized reasoning item when no