@oh-my-pi/pi-ai 18.10.4 → 18.11.1
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/CHANGELOG.md +38 -0
- package/dist/types/auth/usage-cache.d.ts +3 -1
- package/dist/types/error/flags.d.ts +12 -0
- package/dist/types/images/shared.d.ts +26 -2
- package/dist/types/judgment/native.d.ts +16 -1
- package/dist/types/judgment/text.d.ts +11 -5
- package/dist/types/judgment/types.d.ts +5 -1
- package/dist/types/judgment/wire.d.ts +18 -0
- package/dist/types/provider-session-state.d.ts +2 -2
- package/dist/types/providers/anthropic.d.ts +9 -5
- package/dist/types/providers/cursor.d.ts +7 -0
- package/dist/types/providers/openai-codex-responses.d.ts +6 -0
- package/dist/types/providers/systemone-server.d.ts +4 -3
- package/dist/types/providers/transform-messages.d.ts +8 -0
- package/dist/types/types.d.ts +15 -7
- package/dist/types/utils/dsml-leak.d.ts +4 -2
- package/dist/types/utils/tool-call-arguments.d.ts +2 -1
- package/package.json +6 -6
- package/src/auth/usage-cache.ts +23 -7
- package/src/auth/usage.ts +2 -4
- package/src/auth-broker/remote-store.ts +31 -6
- package/src/auth-gateway/routes/images.ts +2 -0
- package/src/auth-gateway/routes/systemone.ts +17 -2
- package/src/error/flags.ts +20 -2
- package/src/images/codex-format-directive.md +3 -0
- package/src/images/google-antigravity.ts +2 -1
- package/src/images/google-generative-ai.ts +4 -2
- package/src/images/openai-hosted.ts +25 -2
- package/src/images/openai-images.ts +2 -0
- package/src/images/shared.ts +77 -3
- package/src/judgment/chat.ts +17 -1
- package/src/judgment/native.ts +79 -6
- package/src/judgment/openai-decisions.ts +27 -6
- package/src/judgment/systemone.ts +17 -5
- package/src/judgment/text.ts +20 -7
- package/src/judgment/types.ts +5 -1
- package/src/judgment/wire.ts +19 -0
- package/src/provider-session-state.ts +2 -2
- package/src/providers/anthropic-messages-server.ts +84 -1
- package/src/providers/anthropic.ts +266 -110
- package/src/providers/cursor.ts +49 -9
- package/src/providers/images-server.ts +6 -1
- package/src/providers/openai-codex-responses.ts +27 -2
- package/src/providers/openai-completions.ts +35 -9
- package/src/providers/systemone-server.ts +34 -10
- package/src/providers/transform-messages.ts +23 -12
- package/src/stream.ts +1 -0
- package/src/types.ts +15 -7
- package/src/usage/openai-codex.ts +11 -0
- package/src/usage/opencode-go.ts +7 -8
- package/src/utils/dsml-leak.ts +30 -14
- package/src/utils/tool-call-arguments.ts +2 -1
- package/src/utils.ts +37 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.11.1] - 2026-10-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added image support for native OpenAI Decisions and Workers AI judgments, prompted vision chat, and auth-gateway System One requests; images exceeding a judgment API's documented caps (e.g. Clef's 4 images, 4 MiB each) are rejected locally ([#15471](https://github.com/can1357/oh-my-pi/pull/15471) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
|
|
10
|
+
- `isSurvivingAssistant` in `providers/transform-messages`: whether an assistant turn reaches a replay, malformed tool calls dropped first, so callers outside `transformMessages` pick the same latest turn ([#15791](https://github.com/can1357/oh-my-pi/pull/15791) by [@will-bogusz](https://github.com/will-bogusz))
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed Codex-hosted image generation ignoring the requested `aspect_ratio`/`image_size`, and `3:2`/`2:3` requests sending no size to OpenAI image models ([#15601](https://github.com/can1357/oh-my-pi/pull/15601) by [@chulmin-dev](https://github.com/chulmin-dev))
|
|
15
|
+
- Fixed Claude subagents failing to return structured results when `yield` arguments arrived as invalid JSON ([#15605](https://github.com/can1357/oh-my-pi/pull/15605) by [@farnoy](https://github.com/farnoy))
|
|
16
|
+
- Fixed turns stopping instead of retrying when a gateway reports its upstream WebSocket closed before `response.completed` ([#15628](https://github.com/can1357/oh-my-pi/pull/15628) by [@nqrwhal](https://github.com/nqrwhal)).
|
|
17
|
+
- Fixed Claude on Google Vertex ignoring `cacheRetention: "long"`: prompt caches now get the 1h TTL instead of expiring after 5 minutes, and the `extended-cache-ttl` beta (which Vertex rejects) is never sent there ([#15505](https://github.com/can1357/oh-my-pi/pull/15505) by [@hpost](https://github.com/hpost))
|
|
18
|
+
- Fixed Gemini tool continuations failing on OpenAI-compatible gateways that return `reasoning_signature`, including restored sessions ([#15678](https://github.com/can1357/oh-my-pi/pull/15678) by [@Zzz141592653](https://github.com/Zzz141592653))
|
|
19
|
+
- Fixed Cursor sessions failing every rebuilt request with `PROVIDER_ERROR` after the server re-sent a tool call: the call is recorded once, and histories that already repeat it replay it once with its latest result ([#15749](https://github.com/can1357/oh-my-pi/issues/15749))
|
|
20
|
+
- Fixed Cursor turns cut off mid-stream failing every retry with `PROVIDER_ERROR` until you sent a new message; the retry now starts a fresh Cursor conversation ([#15830](https://github.com/can1357/oh-my-pi/issues/15830))
|
|
21
|
+
- Fixed malformed image-generation inputs being forwarded to providers or reported as credential/upstream errors ([#15765](https://github.com/can1357/oh-my-pi/pull/15765) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
|
|
22
|
+
- Fixed broker-backed auth-gateway processes retaining a cache entry for every distinct client session indefinitely under sustained traffic ([#15841](https://github.com/can1357/oh-my-pi/issues/15841)).
|
|
23
|
+
- Fixed turns ending on a pinned error when a provider reports "temporarily unavailable" or finishes with an `unknown`, `other` or `unspecified` finish reason: these now retry automatically ([#15424](https://github.com/can1357/oh-my-pi/pull/15424) by [@lockwo](https://github.com/lockwo))
|
|
24
|
+
|
|
25
|
+
## [18.11.0] - 2026-10-14
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed parallel tool calls failing with "Tool call arguments are not valid JSON" on some OpenAI-compatible gateways, such as Gemini ([#15682](https://github.com/can1357/oh-my-pi/pull/15682) by [@Zzz141592653](https://github.com/Zzz141592653))
|
|
30
|
+
- Fixed Anthropic fallback-credit redemption losing its credit on errors that only mention `400` in their text, such as an overload message ([#15702](https://github.com/can1357/oh-my-pi/pull/15702) by [@H4vC](https://github.com/H4vC))
|
|
31
|
+
- Fixed Anthropic fallback-credit redemption sending an extra `fallback-credit-2026-06-01` beta when the refused request used a newer one ([#15702](https://github.com/can1357/oh-my-pi/pull/15702) by [@H4vC](https://github.com/H4vC))
|
|
32
|
+
- Fixed Anthropic fallback-credit redemptions failing when the refused turn included unsigned thinking ([#15702](https://github.com/can1357/oh-my-pi/pull/15702) by [@H4vC](https://github.com/H4vC))
|
|
33
|
+
- Fixed turns ending without automatic retry when a gateway forwards Go's `use of closed network connection` transport error; it now retries like other dropped connections ([#15385](https://github.com/can1357/oh-my-pi/issues/15385))
|
|
34
|
+
- Fixed Codex SSE streams failing with an abort instead of retrying when the first event stalls ([#15542](https://github.com/can1357/oh-my-pi/issues/15542))
|
|
35
|
+
- Fixed Codex (ChatGPT subscription) requests with reasoning disabled, such as judges, titles, commit messages, and thinking `off`, leaving effort to the server default; they now send the lowest supported effort, or `none` where the model accepts it ([#15514](https://github.com/can1357/oh-my-pi/issues/15514))
|
|
36
|
+
- Fixed a Codex account whose token OpenAI rejects (401/403) continuing to show its last quota as healthy; `omp usage` now drops the stale report and `omp auth-gateway check` reports the credential as failing ([#15439](https://github.com/can1357/oh-my-pi/issues/15439))
|
|
37
|
+
- Fixed usage reports being served indefinitely while their provider keeps failing; the last successful report is now shown for at most 24 hours after it was fetched ([#15439](https://github.com/can1357/oh-my-pi/issues/15439))
|
|
38
|
+
- Fixed Responses requests 400ing (DeepSeek: "The `reasoning_text` in the thinking mode must be passed back to the API") after a tool call whose arguments were invalid JSON: the call is now replayed with its parse diagnostic instead of being dropped and leaving a trailing `[Orphan tool result]` note ([#15568](https://github.com/can1357/oh-my-pi/issues/15568))
|
|
39
|
+
- Fixed DeepSeek on OpenAI-compatible gateways leaving doubled-pipe DSML markup (`</||DSML||parameter>`) in assistant text and reasoning ([#15485](https://github.com/can1357/oh-my-pi/issues/15485))
|
|
40
|
+
- Fixed sessions failing every request with `400 … thinking or redacted_thinking blocks in the latest assistant message cannot be modified` after a response fell back to another model mid-turn (e.g. through a gateway that opts into Anthropic server-side fallback); the turn now replays one model's reasoning, and a session already stuck on such a turn recovers by dropping that turn's reasoning and retrying ([#15654](https://github.com/can1357/oh-my-pi/pull/15654) by [@abilliontokens](https://github.com/abilliontokens))
|
|
41
|
+
- Fixed `omp auth-gateway`'s Anthropic `/v1/messages` stream misnumbering content blocks after a redacted reasoning block, which misrouted every later delta in index-addressed clients ([#15654](https://github.com/can1357/oh-my-pi/pull/15654) by [@abilliontokens](https://github.com/abilliontokens))
|
|
42
|
+
|
|
5
43
|
## [18.10.4] - 2026-10-13
|
|
6
44
|
|
|
7
45
|
### Fixed
|
|
@@ -2,7 +2,7 @@ import type { AuthCredentialStore } from "./store.js";
|
|
|
2
2
|
import type { AuthCredential, OAuthCredential } from "./types.js";
|
|
3
3
|
import type { CredentialPool } from "./pool.js";
|
|
4
4
|
import type { Provider } from "../types.js";
|
|
5
|
-
import type { UsageCredential, UsageProvider } from "../usage.js";
|
|
5
|
+
import type { UsageCredential, UsageProvider, UsageReport } from "../usage.js";
|
|
6
6
|
/** Minimum interval between non-exhausted header snapshots; used by UsageService. */
|
|
7
7
|
export declare const USAGE_HEADER_INGEST_INTERVAL_MS = 60000;
|
|
8
8
|
/**
|
|
@@ -56,6 +56,8 @@ export declare class UsageCache {
|
|
|
56
56
|
failureKey(reportKey: string): string;
|
|
57
57
|
get<T>(key: string): UsageCacheEntry<T> | undefined;
|
|
58
58
|
getStale<T>(key: string): UsageCacheEntry<T> | undefined;
|
|
59
|
+
/** Last successful report under `key` while inside its retention window; the failure-path fallback. */
|
|
60
|
+
lastGood(key: string): UsageReport | null;
|
|
59
61
|
set<T>(key: string, entry: UsageCacheEntry<T>): void;
|
|
60
62
|
deletePrefix(prefix: string): boolean;
|
|
61
63
|
markForceRefresh(provider?: Provider): void;
|
|
@@ -68,10 +68,22 @@ 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).
|
|
76
|
+
*/
|
|
71
77
|
export declare const TRANSIENT_TRANSPORT_PATTERN: RegExp;
|
|
72
78
|
export declare const ANTHROPIC_ACCOUNT_POLICY_PATTERN: RegExp;
|
|
73
79
|
/** Whether an error message represents an Anthropic account-scoped permission/policy denial. */
|
|
74
80
|
export declare function isAnthropicAccountPolicyText(text: string, provider?: string, statusArg?: number): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Anthropic's rejection of a replayed latest assistant turn whose `thinking` or
|
|
83
|
+
* `redacted_thinking` blocks no longer verify. Resending the same turn fails the
|
|
84
|
+
* same way; only a request that changes that turn's reasoning can succeed.
|
|
85
|
+
*/
|
|
86
|
+
export declare function isImmutableLatestThinkingText(text: string): boolean;
|
|
75
87
|
/**
|
|
76
88
|
* Local llama.cpp / Ollama deterministic tool-call argument JSON parse failure.
|
|
77
89
|
* The model emitted invalid JSON in a tool call and the server returned HTTP 500
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { FetchImpl, Model, Usage } from "@oh-my-pi/pi-catalog/types";
|
|
2
2
|
import type { ApiKey } from "../auth-retry.js";
|
|
3
3
|
import * as AIError from "../error/index.js";
|
|
4
|
-
import type {
|
|
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
|
-
|
|
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 {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Api, FetchImpl } from "@oh-my-pi/pi-catalog/types";
|
|
2
2
|
import { type ApiKey } from "../auth-retry.js";
|
|
3
3
|
import * as AIError from "../error/index.js";
|
|
4
|
-
import type { Model, Usage } from "../types.js";
|
|
4
|
+
import type { ImageContent, Model, Usage } from "../types.js";
|
|
5
5
|
import { type Judge, type JudgeOptions, type JudgmentRequest, type JudgmentResult, type Questions } from "./types.js";
|
|
6
6
|
import type { JudgmentWire } from "./wire.js";
|
|
7
7
|
export declare const TYPESAFE_PROVIDER = "typesafe";
|
|
@@ -16,6 +16,14 @@ declare const JUDGMENT_WIRES: {
|
|
|
16
16
|
export type JudgmentApi = keyof typeof JUDGMENT_WIRES;
|
|
17
17
|
/** Whether a catalog API answers judgments natively. */
|
|
18
18
|
export declare function isJudgmentApi(api: Api): api is JudgmentApi;
|
|
19
|
+
/** Whether both the native protocol and catalog model support image input. */
|
|
20
|
+
export declare function nativeJudgmentSupportsImages(model: Model): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Reject `images` that `model`'s native judgment API cannot take — no image support, or a
|
|
23
|
+
* documented per-dialect cap exceeded — before any credential or upstream work.
|
|
24
|
+
* @throws {AIError.ValidationError} naming the reason.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkNativeJudgmentImages(model: Model, images: readonly ImageContent[]): void;
|
|
19
27
|
/** `TYPESAFE_BASE_URL` when set, else the public API root; trailing slashes stripped. */
|
|
20
28
|
export declare function typesafeBaseUrl(): string;
|
|
21
29
|
/** `TYPESAFE_DEFAULT_MODEL` when set, else {@link TYPESAFE_DEFAULT_MODEL}. */
|
|
@@ -43,6 +51,11 @@ export interface NativeJudgeOptions extends NativeJudgeConnection {
|
|
|
43
51
|
* requests are split. Unset: no cap.
|
|
44
52
|
*/
|
|
45
53
|
maxQuestions?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the catalogued model accepts images. `false` disables image input;
|
|
56
|
+
* a `true` value cannot enable a wire dialect that does not support images.
|
|
57
|
+
*/
|
|
58
|
+
supportsImages?: boolean;
|
|
46
59
|
}
|
|
47
60
|
/** Per-call options for {@link NativeJudge.judge}. */
|
|
48
61
|
export interface NativeJudgeCallOptions extends JudgeOptions {
|
|
@@ -60,6 +73,8 @@ export declare class JudgmentApiError extends AIError.ProviderHttpError {
|
|
|
60
73
|
export declare class NativeJudge implements Judge {
|
|
61
74
|
#private;
|
|
62
75
|
readonly label: string;
|
|
76
|
+
/** True when the selected native wire and model both accept image inputs. */
|
|
77
|
+
readonly supportsImages?: boolean;
|
|
63
78
|
readonly api: JudgmentApi;
|
|
64
79
|
readonly provider: string;
|
|
65
80
|
/** Wire model id sent with every request. */
|
|
@@ -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
|
|
30
|
-
* carries XML-field state followed by the answer-format cue.
|
|
31
|
-
* on a bare request (answer it,
|
|
32
|
-
* explicit tags and the trailing
|
|
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>>;
|
|
@@ -20,6 +20,22 @@ export interface DecodedJudgment {
|
|
|
20
20
|
refused: readonly string[];
|
|
21
21
|
usage: Usage;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Image input one judgment dialect accepts. Every cap is optional; {@link NativeJudge}
|
|
25
|
+
* rejects a request exceeding one locally instead of spending an upstream call on it.
|
|
26
|
+
*/
|
|
27
|
+
export interface JudgmentImageInput {
|
|
28
|
+
/** Accepted lowercase MIME types; unset accepts any image type. */
|
|
29
|
+
mimeTypes?: readonly string[];
|
|
30
|
+
/** Images per request. */
|
|
31
|
+
maxCount?: number;
|
|
32
|
+
/** Decoded bytes per image. */
|
|
33
|
+
maxBytes?: number;
|
|
34
|
+
/** Decoded bytes across all images. */
|
|
35
|
+
maxTotalBytes?: number;
|
|
36
|
+
/** Pixels per image, when the image header reports dimensions. */
|
|
37
|
+
maxPixels?: number;
|
|
38
|
+
}
|
|
23
39
|
/** How one judgment API addresses, encodes, and decodes a request. */
|
|
24
40
|
export interface JudgmentWire {
|
|
25
41
|
/**
|
|
@@ -29,6 +45,8 @@ export interface JudgmentWire {
|
|
|
29
45
|
target(baseUrl: string, model: string, key: string): JudgmentTarget;
|
|
30
46
|
/** Request body for `model`. */
|
|
31
47
|
encode(request: JudgmentRequest, model: string): unknown;
|
|
48
|
+
/** Image input this dialect accepts; absent means text-only. */
|
|
49
|
+
images?: JudgmentImageInput;
|
|
32
50
|
/** The response's answers and usage; `undefined` when the payload is not this API's response shape. */
|
|
33
51
|
decode(payload: unknown): DecodedJudgment | undefined;
|
|
34
52
|
}
|
|
@@ -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
|
|
22
|
-
*
|
|
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
|
|
@@ -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
|
|
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;
|
|
@@ -283,10 +286,11 @@ export type AnthropicMessageParam = MessageParam;
|
|
|
283
286
|
* Serialize omp {@link Message}s to Anthropic wire messages.
|
|
284
287
|
*
|
|
285
288
|
* `opts.serverSideFallbackEnabled` — when the CURRENT request itself
|
|
286
|
-
* opts into the server-side-fallback beta chain. Only then
|
|
287
|
-
* `fallback` content block from a prior
|
|
288
|
-
* otherwise the block is dropped to avoid a 400
|
|
289
|
-
* that don't send the beta
|
|
289
|
+
* opts into the server-side-fallback beta chain. Only then, and only on the
|
|
290
|
+
* official endpoint, may a persisted `fallback` content block from a prior
|
|
291
|
+
* turn be replayed on the wire; otherwise the block is dropped to avoid a 400
|
|
292
|
+
* on requests that don't send the beta, and its turn keeps one model's
|
|
293
|
+
* reasoning (see {@link collapseFallbackSegments}).
|
|
290
294
|
*
|
|
291
295
|
* `opts.replayCompaction` — replay a user-role compaction summary that
|
|
292
296
|
* carries an {@link AnthropicCompactionPayload} from this provider as a
|
|
@@ -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;
|
|
@@ -14,10 +14,11 @@ export interface SystemOneResponseBody {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Validate the routing-relevant parts of a System One request body
|
|
17
|
+
* Validate the routing-relevant parts of a System One request body, including
|
|
18
|
+
* optional embedded base64 images.
|
|
18
19
|
*
|
|
19
|
-
* @throws {AIError.ValidationError}
|
|
20
|
-
*
|
|
20
|
+
* @throws {AIError.ValidationError} when required routing fields are missing
|
|
21
|
+
* or malformed, or a supplied image is malformed.
|
|
21
22
|
*/
|
|
22
23
|
export declare function parseRequest(body: unknown): SystemOneParsedRequest;
|
|
23
24
|
/** Re-encode a judgment result in TypeSafe's response shape. */
|
|
@@ -36,6 +36,14 @@ export declare function isMalformedToolCallName(name: unknown): boolean;
|
|
|
36
36
|
* off by `length` / `error` / `aborted` that carries no text and no tool call.
|
|
37
37
|
*/
|
|
38
38
|
export declare function shouldDropTruncatedThinkingOnlyAssistant(msg: AssistantMessage): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Whether `msg` reaches a replay as an assistant turn, so it can be the latest
|
|
41
|
+
* surviving one: {@link sanitizeMalformedToolCalls} drops its malformed tool
|
|
42
|
+
* calls, then {@link shouldDropTruncatedThinkingOnlyAssistant} a truncated
|
|
43
|
+
* turn left with no text and no tool call. On an already sanitized list this
|
|
44
|
+
* is `!shouldDropTruncatedThinkingOnlyAssistant(msg)`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isSurvivingAssistant(msg: AssistantMessage): boolean;
|
|
39
47
|
/**
|
|
40
48
|
* Normalize tool call ID for cross-provider compatibility.
|
|
41
49
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
package/dist/types/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { ApiKey } from "./auth-retry.js";
|
|
|
9
9
|
import type { OAuthRequestIdentity } from "./auth/types.js";
|
|
10
10
|
import type { BedrockOptions } from "./providers/amazon-bedrock.js";
|
|
11
11
|
import type { AnthropicOptions } from "./providers/anthropic.js";
|
|
12
|
-
import type { FallbackParam, StopDetails } from "./providers/anthropic-wire.js";
|
|
12
|
+
import type { FallbackParam, MessageCreateParamsStreaming, StopDetails } from "./providers/anthropic-wire.js";
|
|
13
13
|
import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses.js";
|
|
14
14
|
import type { CursorOptions } from "./providers/cursor.js";
|
|
15
15
|
import type { AppleFoundationModelsOptions } from "./providers/apple-foundation-models.js";
|
|
@@ -650,11 +650,12 @@ export interface RedactedThinkingContent {
|
|
|
650
650
|
}
|
|
651
651
|
/**
|
|
652
652
|
* Anthropic server-side-fallback boundary marker persisted on assistant
|
|
653
|
-
* turns whose
|
|
654
|
-
* `AnthropicOptions.fallbacks
|
|
655
|
-
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
653
|
+
* turns whose response switched models mid-stream, whether the request
|
|
654
|
+
* opted into `AnthropicOptions.fallbacks` or a gateway opted in on its
|
|
655
|
+
* behalf. Consumers other than the Anthropic provider MUST ignore it —
|
|
656
|
+
* `transformMessages` strips the block on any non-Anthropic hop, and
|
|
657
|
+
* `convertAnthropicMessages` only puts it on the wire for an opted-in
|
|
658
|
+
* request to the official endpoint.
|
|
658
659
|
*/
|
|
659
660
|
export interface AnthropicFallbackContent {
|
|
660
661
|
type: "fallback";
|
|
@@ -981,6 +982,12 @@ export type AssistantRetryRecovery = {
|
|
|
981
982
|
export interface ContextSnapshot {
|
|
982
983
|
promptTokens: number;
|
|
983
984
|
nonMessageTokens: number;
|
|
985
|
+
/**
|
|
986
|
+
* Estimated tool-schema tokens included in `nonMessageTokens` (0 when the
|
|
987
|
+
* model defers schemas server-side). Lets a later model whose schema policy
|
|
988
|
+
* differs rebase the provider anchor instead of inheriting its schemas.
|
|
989
|
+
*/
|
|
990
|
+
toolSchemaTokens?: number;
|
|
984
991
|
/** Estimated prompt tokens removed by local history rewrites after this provider snapshot was recorded. */
|
|
985
992
|
historyRewriteTokensRemoved?: number;
|
|
986
993
|
/**
|
|
@@ -1422,7 +1429,8 @@ export type AssistantMessageEvent = {
|
|
|
1422
1429
|
export interface AnthropicFallbackCreditHandle {
|
|
1423
1430
|
token: string;
|
|
1424
1431
|
prefillClaim?: boolean | null;
|
|
1425
|
-
|
|
1432
|
+
/** The refused request's body, replayed with the credit token on the fallback model. */
|
|
1433
|
+
params: MessageCreateParamsStreaming;
|
|
1426
1434
|
betas?: readonly string[];
|
|
1427
1435
|
betaHeader?: string;
|
|
1428
1436
|
expiresAt: number;
|
|
@@ -23,7 +23,9 @@ export declare function isDsmlLeakRecoveryTarget(model: Model): boolean;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function stripDsmlToolMarkup(text: string): string | undefined;
|
|
25
25
|
/**
|
|
26
|
-
* Remove leaked DSML tool-call markup from
|
|
27
|
-
*
|
|
26
|
+
* Remove leaked DSML tool-call markup from an assistant message before commit,
|
|
27
|
+
* including reasoning blocks and turns with valid structured calls. Rewritten
|
|
28
|
+
* thinking with an opaque signature is discarded; OpenAI-compatible reasoning
|
|
29
|
+
* field names are replay markers, not signatures, and survive cleanup.
|
|
28
30
|
*/
|
|
29
31
|
export declare function removeDsmlToolMarkupLeak(message: AssistantMessage): boolean;
|
|
@@ -5,7 +5,8 @@ export declare function parseToolCallArguments(json: string | undefined): ToolCa
|
|
|
5
5
|
* Native Responses `function_call.arguments` to persist for replay.
|
|
6
6
|
*
|
|
7
7
|
* {@link parseToolCallArguments} repairs some invalid JSON (e.g. `"name": ,`) and the tool runs with the
|
|
8
|
-
* repaired value; replay
|
|
8
|
+
* repaired value; replay must carry what was executed (#14155), not the {@link invalidToolCallArguments}
|
|
9
|
+
* diagnostic it substitutes for stored arguments that do not parse (#15568).
|
|
9
10
|
* Returns `raw` when it already parses or the arguments were unrepairable, else the executed arguments.
|
|
10
11
|
*/
|
|
11
12
|
export declare function replayableToolCallArguments(raw: string, executed: ToolCall["arguments"]): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.11.1",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -155,11 +155,11 @@
|
|
|
155
155
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
156
156
|
},
|
|
157
157
|
"dependencies": {
|
|
158
|
-
"@oh-my-pi/omptype": "18.
|
|
159
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
160
|
-
"@oh-my-pi/pi-natives": "18.
|
|
161
|
-
"@oh-my-pi/pi-utils": "18.
|
|
162
|
-
"@oh-my-pi/pi-wire": "18.
|
|
158
|
+
"@oh-my-pi/omptype": "18.11.1",
|
|
159
|
+
"@oh-my-pi/pi-catalog": "18.11.1",
|
|
160
|
+
"@oh-my-pi/pi-natives": "18.11.1",
|
|
161
|
+
"@oh-my-pi/pi-utils": "18.11.1",
|
|
162
|
+
"@oh-my-pi/pi-wire": "18.11.1"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@types/bun": "^1.3.14"
|
package/src/auth/usage-cache.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { AuthCredential, OAuthCredential } from "./types";
|
|
|
3
3
|
import type { CredentialPool } from "./pool";
|
|
4
4
|
import type { Provider } from "../types";
|
|
5
5
|
import type { UsageCredential, UsageProvider, UsageReport } from "../usage";
|
|
6
|
+
import { isRecord } from "../utils";
|
|
6
7
|
|
|
7
8
|
const USAGE_CACHE_PREFIX = "usage_cache:";
|
|
8
9
|
const USAGE_FORCE_REFRESH_CACHE_PREFIX = "force-refresh:";
|
|
@@ -53,6 +54,17 @@ function withoutRawPayload(value: unknown): unknown {
|
|
|
53
54
|
return rest;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* End of a cached value's durable retention. A report is retained for 24h past
|
|
59
|
+
* its last successful fetch, so re-storing it as a failure fallback never
|
|
60
|
+
* extends that window; other values are retained for 24h from now.
|
|
61
|
+
*/
|
|
62
|
+
function retentionDeadline(value: unknown): number {
|
|
63
|
+
const fetchedAt = isRecord(value) ? value.fetchedAt : undefined;
|
|
64
|
+
const start = typeof fetchedAt === "number" && Number.isFinite(fetchedAt) && fetchedAt > 0 ? fetchedAt : Date.now();
|
|
65
|
+
return start + USAGE_LAST_GOOD_RETENTION_MS;
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
/** Convert a stored credential for usage providers; used by usage and health services. */
|
|
57
69
|
export function buildUsageCredential(credential: AuthCredential): UsageCredential {
|
|
58
70
|
if (credential.type === "api_key") {
|
|
@@ -203,15 +215,19 @@ export class UsageCache {
|
|
|
203
215
|
return parseUsageCacheEntry<T>(raw);
|
|
204
216
|
}
|
|
205
217
|
|
|
218
|
+
/** Last successful report under `key` while inside its retention window; the failure-path fallback. */
|
|
219
|
+
lastGood(key: string): UsageReport | null {
|
|
220
|
+
const report = this.getStale<UsageReport | null>(key)?.value;
|
|
221
|
+
return report && Date.now() < retentionDeadline(report) ? report : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
206
224
|
set<T>(key: string, entry: UsageCacheEntry<T>): void {
|
|
207
225
|
const value = withoutRawPayload(entry.value);
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
value === null ? entry.expiresAt : Math.max(entry.expiresAt, Date.now() + USAGE_LAST_GOOD_RETENTION_MS);
|
|
214
|
-
this.#store.setCache(`${USAGE_CACHE_PREFIX}${key}`, payload, Math.floor(durableExpiresAt / 1000));
|
|
226
|
+
const deadline = value === null ? undefined : retentionDeadline(value);
|
|
227
|
+
// Failure cooldowns must not make a last-good report fresh beyond retention.
|
|
228
|
+
const expiresAt = deadline === undefined ? entry.expiresAt : Math.min(entry.expiresAt, deadline);
|
|
229
|
+
const payload = JSON.stringify({ value, expiresAt });
|
|
230
|
+
this.#store.setCache(`${USAGE_CACHE_PREFIX}${key}`, payload, Math.floor((deadline ?? expiresAt) / 1000));
|
|
215
231
|
}
|
|
216
232
|
|
|
217
233
|
deletePrefix(prefix: string): boolean {
|
package/src/auth/usage.ts
CHANGED
|
@@ -334,7 +334,7 @@ export class UsageService implements UsageApi {
|
|
|
334
334
|
|
|
335
335
|
const failure = this.#deps.cache.get<null>(failureKey);
|
|
336
336
|
if (failure && failure.expiresAt > now) {
|
|
337
|
-
return this.#deps.cache.
|
|
337
|
+
return this.#deps.cache.lastGood(cacheKey);
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
// Invalidation must not fork an already-running credential probe. Share it;
|
|
@@ -378,9 +378,7 @@ export class UsageService implements UsageApi {
|
|
|
378
378
|
// so an expired login does not display stale quota indefinitely.
|
|
379
379
|
const providerImpl = this.providerFor(request.provider);
|
|
380
380
|
const retainLastGood = !invalidated && providerImpl?.retainLastGoodOnFailure !== false;
|
|
381
|
-
const lastGood = retainLastGood
|
|
382
|
-
? (this.#deps.cache.getStale<UsageReport | null>(cacheKey)?.value ?? null)
|
|
383
|
-
: null;
|
|
381
|
+
const lastGood = retainLastGood ? this.#deps.cache.lastGood(cacheKey) : null;
|
|
384
382
|
const failureBackoffMs = providerImpl?.failureBackoffMs ?? USAGE_FAILURE_BACKOFF_MS;
|
|
385
383
|
const backoffJitter = failureBackoffMs * (Math.random() * 0.5 - 0.25);
|
|
386
384
|
const coolDown = Date.now() + failureBackoffMs + backoffJitter;
|