@oh-my-pi/pi-ai 18.14.0 → 18.14.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 +20 -24
- package/dist/types/auth/rotation.d.ts +2 -1
- package/dist/types/auth/types.d.ts +3 -1
- package/dist/types/providers/cursor.d.ts +33 -4
- package/dist/types/providers/factory-droid/gemini.d.ts +24 -1
- package/dist/types/providers/openai-codex/access-programs.d.ts +11 -3
- package/dist/types/providers/openai-codex/live-steering.d.ts +2 -0
- package/dist/types/types.d.ts +8 -1
- package/package.json +6 -6
- package/src/auth/rotation.ts +10 -5
- package/src/auth/types.ts +3 -1
- package/src/judgment/native.ts +5 -0
- package/src/providers/anthropic.ts +32 -4
- package/src/providers/cursor/proto/agent.proto +5 -0
- package/src/providers/cursor.ts +569 -65
- package/src/providers/factory-droid/gemini.ts +2 -2
- package/src/providers/gitlab-duo.ts +1 -0
- package/src/providers/openai-anthropic-shim.ts +1 -0
- package/src/providers/openai-codex/access-programs.ts +21 -4
- package/src/providers/openai-codex/live-steering.ts +10 -1
- package/src/providers/openai-codex-responses.ts +31 -11
- package/src/providers/pi-native-server.ts +1 -0
- package/src/stream.ts +2 -0
- package/src/types.ts +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.14.1] - 2026-10-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added the `codexAccessPrograms` stream option and `configureCodexAccessPrograms()` process default to turn off the automatic Daybreak Blue `access_programs` request on Codex ([#17508](https://github.com/can1357/oh-my-pi/pull/17508) by [@H4vC](https://github.com/H4vC))
|
|
10
|
+
- Added `waitForSibling` to `RotateCredentialOptions`; set it to `false` to skip the short sibling-unblock wait in `limits.rotate()` while still blocking the failed credential ([#17439](https://github.com/can1357/oh-my-pi/pull/17439) by [@wolfiesch](https://github.com/wolfiesch))
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Cursor runs served through `omp auth-gateway` are limited to the client's own tools, all advertised as MCP; Cursor's built-in tools (file, shell, edit, todo, web fetch, SCM) are no longer offered on that path ([#17382](https://github.com/can1357/oh-my-pi/pull/17382) by [@eggpeat](https://github.com/eggpeat))
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed native judgment requests (TypeSafe, OpenRouter Decisions, OpenAI Decisions, Workers AI) sending credentials in their state unredacted while credential redaction was on ([#17443](https://github.com/can1357/oh-my-pi/pull/17443))
|
|
19
|
+
- Fixed every new Codex session, including each subagent, losing a response and retrying when the native turn lane refuses live steering; once refused, later sessions on the same account, endpoint, and model queue follow-ups for four hours ([#14158](https://github.com/can1357/oh-my-pi/issues/14158), [#17438](https://github.com/can1357/oh-my-pi/pull/17438) by [@wolfiesch](https://github.com/wolfiesch))
|
|
20
|
+
- Fixed Cursor models served through `omp auth-gateway` being told their file and shell tools were unavailable while the client ran the calls anyway, in Cursor's own argument shape (edits failed validation); each call now reaches the client as one of its own tools and the turn stops with `toolUse` until the result comes back ([#17382](https://github.com/can1357/oh-my-pi/pull/17382) by [@eggpeat](https://github.com/eggpeat))
|
|
21
|
+
- Fixed Cursor models served through `omp auth-gateway` being told each tool call was handed off, which left Composer issuing calls blind and answering their results with empty stops; the gateway now keeps the Cursor run open while the client runs a step's calls and answers them in-band with the client's results on its next request (up to 30 minutes; otherwise the turn is rebuilt from history as before) ([#17382](https://github.com/can1357/oh-my-pi/pull/17382) by [@eggpeat](https://github.com/eggpeat))
|
|
22
|
+
- Fixed Anthropic-compatible endpoints behind a relay opening a new connection for every turn: the response body is now drained for up to 2.5 s after `message_stop` instead of being cancelled, so the kept-alive connection is reused ([#17432](https://github.com/can1357/oh-my-pi/pull/17432) by [@flosrn](https://github.com/flosrn))
|
|
23
|
+
|
|
5
24
|
## [18.14.0] - 2026-10-29
|
|
6
25
|
|
|
7
26
|
### Breaking Changes
|
|
@@ -2300,27 +2319,4 @@
|
|
|
2300
2319
|
- Enabled freeform tool patch support for Azure OpenAI and Codex models.
|
|
2301
2320
|
- Fixed an issue where the `/usage show` command returned "No usage data available" when using a custom proxy base URL for Codex.
|
|
2302
2321
|
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
### Added
|
|
2306
|
-
|
|
2307
|
-
- Added a comprehensive, public-facing error module exported via the "./error" path, featuring structured error classification, provider-specific HTTP error classes (e.g., Anthropic, OpenAI, Gemini), OAuth/Auth-specific errors, rate-limit utilities, and retryability predicates.
|
|
2308
|
-
|
|
2309
|
-
### Changed
|
|
2310
|
-
|
|
2311
|
-
- Updated OpenAI Codex defaults to increase default text verbosity to medium, enable detailed reasoning summaries by default, and include all turns in the reasoning context by default.
|
|
2312
|
-
- Updated the OpenAI Codex WebSocket transport to resolve its configuration (via PI_CODEX_WEBSOCKET_* environment variables) once at startup rather than re-parsing on every request.
|
|
2313
|
-
- Enhanced cross-model reasoning recovery and preservation to render demoted reasoning in the target model's canonical inline thinking dialect (such as Gemini's thinking fence or standard think tags) to prevent leaking inert context or control tokens into history.
|
|
2314
|
-
- Broadened the leaked-thinking stream healer to recover reasoning emitted in any dialect's canonical idiom (including Gemini, Gemma, Harmony, and scratchpads) and route them to thinking events instead of raw markup.
|
|
2315
|
-
- Implemented automatic retry logic for detected thinking-loop stalls to improve response reliability.
|
|
2316
|
-
- Hardened stateful delta chaining to ignore transient streaming bookkeeping symbols during structural equality checks, preventing unnecessary full-transcript replays.
|
|
2317
|
-
|
|
2318
|
-
### Fixed
|
|
2319
|
-
|
|
2320
|
-
- Fixed preservation of OpenAI Responses assistant message phase values across auth-gateway parsing, streaming, and history replay, ensuring GPT-5.4/GPT-5.5 intermediate updates and final answers retain their original phase labels.
|
|
2321
|
-
|
|
2322
|
-
### Removed
|
|
2323
|
-
|
|
2324
|
-
- Removed Pi dialect support and related serialization/parsing logic.
|
|
2325
|
-
|
|
2326
|
-
Older entries are archived in [packages/ai/CHANGELOG.md@ef4bc35e85d8](https://github.com/can1357/oh-my-pi/blob/ef4bc35e85d88932d92b01ae7121c9f47208cf40/packages/ai/CHANGELOG.md).
|
|
2322
|
+
Older entries are archived in [packages/ai/CHANGELOG.md@cef4a452836c](https://github.com/can1357/oh-my-pi/blob/cef4a452836c7a8d547dd14470c34c9ed2dbb5f8/packages/ai/CHANGELOG.md).
|
|
@@ -63,7 +63,8 @@ export declare class RateLimits implements LimitsApi {
|
|
|
63
63
|
* For usage-limit and account-policy failures with no free sibling, sleeps
|
|
64
64
|
* until the earliest sibling unblocks when that is at most
|
|
65
65
|
* {@link SIBLING_UNBLOCK_WAIT_MAX_MS} away, then reports `afterSiblingWait`.
|
|
66
|
-
*
|
|
66
|
+
* Setting `options.waitForSibling` to false skips that wait without skipping
|
|
67
|
+
* the failed credential's block. Aborting `options.signal` during the wait rejects.
|
|
67
68
|
*/
|
|
68
69
|
rotate(provider: string, sessionId: string | undefined, options?: RotateCredentialOptions): Promise<CredentialRotation>;
|
|
69
70
|
}
|
|
@@ -698,6 +698,8 @@ export type RotateCredentialOptions = {
|
|
|
698
698
|
apiKey?: string;
|
|
699
699
|
credentialId?: number;
|
|
700
700
|
signal?: AbortSignal;
|
|
701
|
+
/** Whether to wait for a briefly blocked sibling; defaults to true. The failed credential is blocked either way. */
|
|
702
|
+
waitForSibling?: boolean;
|
|
701
703
|
};
|
|
702
704
|
/**
|
|
703
705
|
* Outcome of {@link LimitsApi.rotate}.
|
|
@@ -1275,7 +1277,7 @@ export interface LimitsApi {
|
|
|
1275
1277
|
*
|
|
1276
1278
|
* For usage-limit and account-policy failures with no free sibling, waits
|
|
1277
1279
|
* (abortable via `options.signal`) when a sibling's block expires within a
|
|
1278
|
-
* few seconds, then reports `afterSiblingWait
|
|
1280
|
+
* few seconds, then reports `afterSiblingWait`, unless `options.waitForSibling` is false.
|
|
1279
1281
|
*/
|
|
1280
1282
|
rotate(provider: string, sessionId: string | undefined, options?: RotateCredentialOptions): Promise<CredentialRotation>;
|
|
1281
1283
|
/** Invalidate a credential matching an API key after authentication failure. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { CursorRule, McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
2
2
|
import { CURSOR_CLIENT_VERSION } from "@oh-my-pi/pi-catalog/wire/cursor";
|
|
3
|
-
import { type AgentServerMessage, type ConversationStateStructure } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
3
|
+
import { type AgentServerMessage, type ConversationStateStructure, type ExecServerMessage } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
4
4
|
import { type JsonValue } from "@oh-my-pi/pi-catalog/discovery/protobuf";
|
|
5
|
-
import type { AssistantMessage, Context, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorTodoSyncHandler, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
|
|
5
|
+
import type { AssistantMessage, Context, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorMcpCall, CursorTodoSyncHandler, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
|
|
6
6
|
import { kCursorExecResolved, kStreamingBlockIndex, kStreamingBlockKind, kStreamingEnvelopeId, kStreamingLastParseLen, kStreamingPartialJson } from "../utils/block-symbols.js";
|
|
7
7
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
8
8
|
export declare const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
@@ -12,7 +12,14 @@ export interface CursorOptions extends StreamOptions {
|
|
|
12
12
|
conversationId?: string;
|
|
13
13
|
execHandlers?: CursorExecHandlers;
|
|
14
14
|
onToolResult?: CursorToolResultHandler;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* The caller's client runs every tool (auth-gateway). Cursor is limited to
|
|
17
|
+
* MCP calls, every context tool is advertised as one, and a turn that hands
|
|
18
|
+
* the client a call stops with `toolUse`. With a `providerSessionState` to
|
|
19
|
+
* park in, the run stays open for the next request to answer its calls
|
|
20
|
+
* in-band (`CursorParkedRun`); without one, each call is handed off and
|
|
21
|
+
* Cursor told its result comes with the next request.
|
|
22
|
+
*/
|
|
16
23
|
externalToolExecutor?: boolean;
|
|
17
24
|
/** Wire model id selected after thinking-effort routing (`resolveWireModelId`). */
|
|
18
25
|
wireModelId?: string;
|
|
@@ -76,6 +83,22 @@ export interface BlockState {
|
|
|
76
83
|
openToolCalls: Map<string, ToolCallState>;
|
|
77
84
|
/** MCP call IDs synthesized from exec frames before their redundant streamed block arrives. */
|
|
78
85
|
resolvedMcpToolCallIds: Set<string>;
|
|
86
|
+
/**
|
|
87
|
+
* Calls a relayed run handed its client in an earlier response
|
|
88
|
+
* (`CursorParkedRun`). Each response fills its own message, so a streamed
|
|
89
|
+
* announcement that trails the handoff must not open the call again there.
|
|
90
|
+
* Optional so existing test harnesses stay valid.
|
|
91
|
+
*/
|
|
92
|
+
handedOffToolCallIds?: Set<string>;
|
|
93
|
+
/**
|
|
94
|
+
* MCP calls a relayed run (`CursorParkedRun`) has seen announced but holds
|
|
95
|
+
* no exec frame for, by call id. A streaming client runs every call it is
|
|
96
|
+
* sent and a streamed event cannot be taken back, while a response may hand
|
|
97
|
+
* the client only held calls; so a call stays off the stream and out of the
|
|
98
|
+
* message until its exec frame is held (`streamUnheldExternalCall`).
|
|
99
|
+
* Set only for a relayed run.
|
|
100
|
+
*/
|
|
101
|
+
unheldExternalCalls?: Map<string, ToolCallState>;
|
|
79
102
|
/**
|
|
80
103
|
* Native `editToolCall` (StrReplace) ids whose materialization `readArgs` /
|
|
81
104
|
* `writeArgs` must stay raw and must not synthesize extra transcript blocks.
|
|
@@ -104,6 +127,12 @@ export interface BlockState {
|
|
|
104
127
|
* matching result and every transcript rebuild strips it as dangling.
|
|
105
128
|
*/
|
|
106
129
|
onToolResult?: CursorToolResultHandler;
|
|
130
|
+
/**
|
|
131
|
+
* Keep an external executor's MCP exec frame open instead of handing the
|
|
132
|
+
* call off: a relayed run answers it in-band once the client's next request
|
|
133
|
+
* brings the result (`CursorParkedRun`).
|
|
134
|
+
*/
|
|
135
|
+
holdExternalCall?: (execMsg: ExecServerMessage, call: CursorMcpCall) => void;
|
|
107
136
|
}
|
|
108
137
|
export interface UsageState {
|
|
109
138
|
sawTokenDelta: boolean;
|
|
@@ -240,7 +269,7 @@ export declare function processInteractionUpdate(update: any, output: AssistantM
|
|
|
240
269
|
* history; later turns rely on {@link buildCursorRulesEntryJson}.
|
|
241
270
|
*/
|
|
242
271
|
export declare function buildCursorRequestContextRules(systemPrompt: readonly string[] | undefined): CursorRule[];
|
|
243
|
-
export declare function buildMcpToolDefinitions(tools: Tool[] | undefined, requiresCursorToolSchemaProjection?: boolean): McpToolDefinition[];
|
|
272
|
+
export declare function buildMcpToolDefinitions(tools: Tool[] | undefined, requiresCursorToolSchemaProjection?: boolean, externalToolExecutor?: boolean): McpToolDefinition[];
|
|
244
273
|
/**
|
|
245
274
|
* Build `ConversationStateStructure.rootPromptMessagesJson` blob IDs for the
|
|
246
275
|
* system prompt plus prior conversation history, as JSON blobs matching
|
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
import type { RequestPolicy } from "@oh-my-pi/pi-catalog/compat/types";
|
|
2
2
|
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
|
|
3
|
-
import type { Context, Model, StreamOptions } from "../../types.js";
|
|
3
|
+
import type { Context, Message, Model, StreamOptions, Tool } from "../../types.js";
|
|
4
4
|
import { AssistantMessageEventStream } from "../../utils/event-stream.js";
|
|
5
|
+
/**
|
|
6
|
+
* Apply Factory's replay provenance before canonical normalization: only
|
|
7
|
+
* signed, Google-origin thinking and Google-origin call signatures survive;
|
|
8
|
+
* everything else is DROPPED (never demoted to visible text). A text block
|
|
9
|
+
* keeps its signature only when this model signed it.
|
|
10
|
+
*
|
|
11
|
+
* Tool-call names are encoded to their wire form here, before
|
|
12
|
+
* `transformMessages` drops calls whose names no provider could declare: the
|
|
13
|
+
* proxy accepts any caller tool name once sanitized, so a declared
|
|
14
|
+
* `my.tool/name` call must survive replay.
|
|
15
|
+
*
|
|
16
|
+
* Only Google-origin turns are stamped as the target, so `transformMessages`
|
|
17
|
+
* keeps their surviving signatures verbatim instead of demoting/stripping them
|
|
18
|
+
* as cross-model. Their ids are plain in either stamp, so pairing is
|
|
19
|
+
* unaffected. Every other turn keeps its real provenance: tool-result pairing
|
|
20
|
+
* reads it (Responses `call_id|item_id` results pair only with a
|
|
21
|
+
* Responses-origin call), and with its reasoning already gone there is
|
|
22
|
+
* nothing left for a cross-model pass to demote.
|
|
23
|
+
*/
|
|
24
|
+
export declare function adoptFactoryReplay(message: Message, model: Model<"factory-droid-agent">): Message;
|
|
25
|
+
export declare function toGeminiTools(tools: Tool[] | undefined): Array<{
|
|
26
|
+
functionDeclarations: unknown[];
|
|
27
|
+
}> | undefined;
|
|
5
28
|
export interface FactoryDroidGeminiOptions extends StreamOptions {
|
|
6
29
|
/** Base URL including the `/api/llm/g/v1` namespace. */
|
|
7
30
|
baseUrl: string;
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { Model } from "../../types.js";
|
|
2
2
|
import type { RequestBody } from "./request-transformer.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Set the process-wide default for requests that leave `codexAccessPrograms`
|
|
5
|
+
* unset. Hosts call this from their settings so side calls that bypass a
|
|
6
|
+
* session stream function still follow it; session requests pass their own
|
|
7
|
+
* value, so concurrent sessions with different settings stay independent.
|
|
6
8
|
*/
|
|
7
|
-
export declare function
|
|
9
|
+
export declare function configureCodexAccessPrograms(enabled: boolean): void;
|
|
10
|
+
/**
|
|
11
|
+
* Request Daybreak Blue on `body` when `requested` (else the process default)
|
|
12
|
+
* allows it, `accountId` may use it on `model`, and no earlier rejection
|
|
13
|
+
* dropped it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyCodexAccessPrograms(body: RequestBody, model: Model<"openai-codex-responses">, accountId: string | undefined, requested: boolean | undefined): void;
|
|
8
16
|
/**
|
|
9
17
|
* Strip `access_programs` from `body` when `error` is the backend rejecting it,
|
|
10
18
|
* remembering the drop for (account, model). Returns `true` when the caller
|
|
@@ -23,6 +23,8 @@ export interface CodexAcceptedSteer {
|
|
|
23
23
|
export interface CodexSteerOutcome {
|
|
24
24
|
responseId: string | undefined;
|
|
25
25
|
accepted: CodexAcceptedSteer[];
|
|
26
|
+
/** A steer was submitted, whether or not the server accepted it. */
|
|
27
|
+
submitted: boolean;
|
|
26
28
|
/**
|
|
27
29
|
* A submission ended without an acknowledgement, so the server may or may
|
|
28
30
|
* not hold it. The caller must drop the socket rather than chain from it.
|
package/dist/types/types.d.ts
CHANGED
|
@@ -372,6 +372,13 @@ export interface StreamOptions {
|
|
|
372
372
|
* without the capability.
|
|
373
373
|
*/
|
|
374
374
|
storeResponses?: boolean;
|
|
375
|
+
/**
|
|
376
|
+
* Request the Daybreak Blue cyber access program on Codex when the account
|
|
377
|
+
* is eligible (see `openai-codex/access-programs.ts`). `false` omits
|
|
378
|
+
* `access_programs`. Unset falls back to the host's
|
|
379
|
+
* `configureCodexAccessPrograms` default, else on. Ignored elsewhere.
|
|
380
|
+
*/
|
|
381
|
+
codexAccessPrograms?: boolean;
|
|
375
382
|
/**
|
|
376
383
|
* Disable native reasoning when the caller supplies an external scratchpad.
|
|
377
384
|
* OpenAI Responses emits `reasoning: { effort: "none" }`; Anthropic and
|
|
@@ -572,7 +579,7 @@ export interface SimpleStreamOptions extends Omit<StreamOptions, "apiKey"> {
|
|
|
572
579
|
* A rejecting transformer is swallowed and the reserved payload stands in.
|
|
573
580
|
*/
|
|
574
581
|
cursorOnToolResult?: CursorToolResultHandler;
|
|
575
|
-
/** Cursor
|
|
582
|
+
/** The caller's client runs every tool: Cursor offers them all as MCP tools and hands each call off to it. */
|
|
576
583
|
cursorExternalToolExecutor?: boolean;
|
|
577
584
|
/**
|
|
578
585
|
* Amazon Bedrock Guardrail settings forwarded through transports that do not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.14.
|
|
3
|
+
"version": "18.14.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.14.
|
|
159
|
-
"@oh-my-pi/pi-catalog": "18.14.
|
|
160
|
-
"@oh-my-pi/pi-natives": "18.14.
|
|
161
|
-
"@oh-my-pi/pi-utils": "18.14.
|
|
162
|
-
"@oh-my-pi/pi-wire": "18.14.
|
|
158
|
+
"@oh-my-pi/omptype": "18.14.1",
|
|
159
|
+
"@oh-my-pi/pi-catalog": "18.14.1",
|
|
160
|
+
"@oh-my-pi/pi-natives": "18.14.1",
|
|
161
|
+
"@oh-my-pi/pi-utils": "18.14.1",
|
|
162
|
+
"@oh-my-pi/pi-wire": "18.14.1"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@types/bun": "^1.3.14"
|
package/src/auth/rotation.ts
CHANGED
|
@@ -395,7 +395,8 @@ export class RateLimits implements LimitsApi {
|
|
|
395
395
|
* For usage-limit and account-policy failures with no free sibling, sleeps
|
|
396
396
|
* until the earliest sibling unblocks when that is at most
|
|
397
397
|
* {@link SIBLING_UNBLOCK_WAIT_MAX_MS} away, then reports `afterSiblingWait`.
|
|
398
|
-
*
|
|
398
|
+
* Setting `options.waitForSibling` to false skips that wait without skipping
|
|
399
|
+
* the failed credential's block. Aborting `options.signal` during the wait rejects.
|
|
399
400
|
*/
|
|
400
401
|
async rotate(
|
|
401
402
|
provider: string,
|
|
@@ -421,7 +422,7 @@ export class RateLimits implements LimitsApi {
|
|
|
421
422
|
credentialId: options?.credentialId,
|
|
422
423
|
signal: options?.signal,
|
|
423
424
|
});
|
|
424
|
-
return awaitSiblingUnblock(mark, options?.signal);
|
|
425
|
+
return awaitSiblingUnblock(mark, options?.signal, options?.waitForSibling);
|
|
425
426
|
}
|
|
426
427
|
|
|
427
428
|
const deniedModel = AIError.codexChatGPTAccountPolicyModel(error);
|
|
@@ -467,7 +468,7 @@ export class RateLimits implements LimitsApi {
|
|
|
467
468
|
routing,
|
|
468
469
|
false,
|
|
469
470
|
);
|
|
470
|
-
return awaitSiblingUnblock(mark, options?.signal);
|
|
471
|
+
return awaitSiblingUnblock(mark, options?.signal, options?.waitForSibling);
|
|
471
472
|
}
|
|
472
473
|
|
|
473
474
|
const providerKey = providerTypeKey(provider, sessionCredential.type);
|
|
@@ -533,9 +534,13 @@ export class RateLimits implements LimitsApi {
|
|
|
533
534
|
* Turn a no-sibling mark into a rotation by sleeping out the soonest sibling
|
|
534
535
|
* block when it expires within {@link SIBLING_UNBLOCK_WAIT_MAX_MS}.
|
|
535
536
|
*/
|
|
536
|
-
async function awaitSiblingUnblock(
|
|
537
|
+
async function awaitSiblingUnblock(
|
|
538
|
+
mark: UsageLimitMarkResult,
|
|
539
|
+
signal?: AbortSignal,
|
|
540
|
+
waitForSibling = true,
|
|
541
|
+
): Promise<CredentialRotation> {
|
|
537
542
|
if (mark.switched) return { switched: true };
|
|
538
|
-
if (mark.retryAtMs === undefined) return { switched: false };
|
|
543
|
+
if (!waitForSibling || mark.retryAtMs === undefined) return { switched: false };
|
|
539
544
|
const waitMs = mark.retryAtMs - Date.now() + SIBLING_UNBLOCK_SLACK_MS;
|
|
540
545
|
if (waitMs > SIBLING_UNBLOCK_WAIT_MAX_MS) return { switched: false };
|
|
541
546
|
if (waitMs > 0) await scheduler.wait(waitMs, { signal });
|
package/src/auth/types.ts
CHANGED
|
@@ -778,6 +778,8 @@ export type RotateCredentialOptions = {
|
|
|
778
778
|
apiKey?: string;
|
|
779
779
|
credentialId?: number;
|
|
780
780
|
signal?: AbortSignal;
|
|
781
|
+
/** Whether to wait for a briefly blocked sibling; defaults to true. The failed credential is blocked either way. */
|
|
782
|
+
waitForSibling?: boolean;
|
|
781
783
|
};
|
|
782
784
|
|
|
783
785
|
/**
|
|
@@ -1379,7 +1381,7 @@ export interface LimitsApi {
|
|
|
1379
1381
|
*
|
|
1380
1382
|
* For usage-limit and account-policy failures with no free sibling, waits
|
|
1381
1383
|
* (abortable via `options.signal`) when a sibling's block expires within a
|
|
1382
|
-
* few seconds, then reports `afterSiblingWait
|
|
1384
|
+
* few seconds, then reports `afterSiblingWait`, unless `options.waitForSibling` is false.
|
|
1383
1385
|
*/
|
|
1384
1386
|
rotate(
|
|
1385
1387
|
provider: string,
|
package/src/judgment/native.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { type ApiKey, withAuth } from "../auth-retry";
|
|
|
28
28
|
import * as AIError from "../error";
|
|
29
29
|
import { validateImageInput } from "../images/shared";
|
|
30
30
|
import { withInferenceUserAgent } from "../providers/inference-headers";
|
|
31
|
+
import { redactSensitiveInObject } from "../providers/transform-messages";
|
|
31
32
|
import type { ImageContent, Model, Usage } from "../types";
|
|
32
33
|
import { getRetryAfterMsFromHeaders } from "../utils/retry-after";
|
|
33
34
|
import { openaiDecisionsWire } from "./openai-decisions";
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
type JudgeOptions,
|
|
39
40
|
type JudgmentRequest,
|
|
40
41
|
type JudgmentResult,
|
|
42
|
+
type JudgmentState,
|
|
41
43
|
type Questions,
|
|
42
44
|
tokenUsage,
|
|
43
45
|
} from "./types";
|
|
@@ -357,6 +359,9 @@ export class NativeJudge implements Judge {
|
|
|
357
359
|
request: JudgmentRequest<Q>,
|
|
358
360
|
options?: NativeJudgeCallOptions,
|
|
359
361
|
): Promise<JudgmentResult<Q>> {
|
|
362
|
+
// The state goes to the wire as is, past the message transforms that redact chat requests.
|
|
363
|
+
const redacted = redactSensitiveInObject(request.state);
|
|
364
|
+
if (redacted.changed) request = { ...request, state: redacted.result as JudgmentState };
|
|
360
365
|
checkJudgmentImages(
|
|
361
366
|
this.label,
|
|
362
367
|
this.#wire,
|
|
@@ -89,7 +89,12 @@ import {
|
|
|
89
89
|
import { withReplaySafeStreamRetry } from "../utils/empty-completion-retry";
|
|
90
90
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
91
91
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
92
|
-
import {
|
|
92
|
+
import {
|
|
93
|
+
getStreamFirstEventTimeoutMs,
|
|
94
|
+
getStreamIdleTimeoutMs,
|
|
95
|
+
iterateWithIdleTimeout,
|
|
96
|
+
iterateWithTerminalGrace,
|
|
97
|
+
} from "../utils/idle-iterator";
|
|
93
98
|
import {
|
|
94
99
|
ANTHROPIC_SLOW_USAGE_LIMIT,
|
|
95
100
|
ANTHROPIC_USAGE_LIMIT_HEADER,
|
|
@@ -1115,6 +1120,16 @@ const PROVIDER_MAX_RETRIES = 10;
|
|
|
1115
1120
|
*/
|
|
1116
1121
|
const PING_PROGRESS_MAX_IDLE_MULTIPLIER = 3;
|
|
1117
1122
|
|
|
1123
|
+
/**
|
|
1124
|
+
* How long to keep reading the HTTP body after `message_stop`. The turn is
|
|
1125
|
+
* complete at that frame, but cancelling a body whose terminal chunk has not
|
|
1126
|
+
* arrived yet makes Bun close the socket instead of returning it to the pool,
|
|
1127
|
+
* so every turn behind a relay that flushes `message_stop` separately pays a
|
|
1128
|
+
* fresh connection (TCP, TLS, slow start). The drain runs after the turn
|
|
1129
|
+
* completes; the window only elapses on hosts that hold the body open.
|
|
1130
|
+
*/
|
|
1131
|
+
const ANTHROPIC_POST_STOP_GRACE_MS = 2_500;
|
|
1132
|
+
|
|
1118
1133
|
/**
|
|
1119
1134
|
* Log a malformed-stream-envelope anomaly without aborting the turn. The strict
|
|
1120
1135
|
* parser would `throw new AnthropicStreamEnvelopeError(...)` here; we instead
|
|
@@ -2619,6 +2634,7 @@ const streamAnthropicOnce = (
|
|
|
2619
2634
|
let sawMessageStart = false;
|
|
2620
2635
|
let sawTerminalEnvelope = false;
|
|
2621
2636
|
let sawMessageStop = false;
|
|
2637
|
+
let messageStopAtMs: number | undefined;
|
|
2622
2638
|
// Set when a duplicate message_start splices a second envelope onto
|
|
2623
2639
|
// the stream; closed indexes then refuse to reopen so replayed
|
|
2624
2640
|
// content cannot duplicate (see content_block_start guard).
|
|
@@ -2676,10 +2692,20 @@ const streamAnthropicOnce = (
|
|
|
2676
2692
|
return true;
|
|
2677
2693
|
},
|
|
2678
2694
|
});
|
|
2695
|
+
const drainRequestAbortController = activeAbortTracker.requestAbortController;
|
|
2696
|
+
const drainedAnthropicStream = iterateWithTerminalGrace(timedAnthropicStream, {
|
|
2697
|
+
finishedAtMs: () => messageStopAtMs,
|
|
2698
|
+
graceMs: ANTHROPIC_POST_STOP_GRACE_MS,
|
|
2699
|
+
// The idle-timeout generator is parked mid-`next()` when the window
|
|
2700
|
+
// closes; aborting the transport settles that read. A bare abort sets
|
|
2701
|
+
// no local reason, so the completed turn is not reported as an error.
|
|
2702
|
+
onGraceEnd: () => drainRequestAbortController.abort(),
|
|
2703
|
+
awaitDrainOnReturn: options?.waitForTerminalDrain,
|
|
2704
|
+
});
|
|
2679
2705
|
const observedAnthropicStream =
|
|
2680
2706
|
rawSseObserver && !recordsRawSseEvents
|
|
2681
|
-
? observeDecodedAnthropicEvents(
|
|
2682
|
-
:
|
|
2707
|
+
? observeDecodedAnthropicEvents(drainedAnthropicStream, rawSseObserver)
|
|
2708
|
+
: drainedAnthropicStream;
|
|
2683
2709
|
for await (const event of observedAnthropicStream) {
|
|
2684
2710
|
sawEvent = true;
|
|
2685
2711
|
|
|
@@ -3094,7 +3120,9 @@ const streamAnthropicOnce = (
|
|
|
3094
3120
|
} else if (event.type === "message_stop") {
|
|
3095
3121
|
sawTerminalEnvelope = true;
|
|
3096
3122
|
sawMessageStop = true;
|
|
3097
|
-
// The protocol is complete even if a broken keep-alive leaves the HTTP
|
|
3123
|
+
// The protocol is complete even if a broken keep-alive leaves the HTTP
|
|
3124
|
+
// body open; the terminal-grace wrapper drains the rest in the background.
|
|
3125
|
+
messageStopAtMs = Date.now();
|
|
3098
3126
|
break;
|
|
3099
3127
|
}
|
|
3100
3128
|
}
|
|
@@ -857,6 +857,10 @@ message StepCompletedUpdate {
|
|
|
857
857
|
int64 step_duration_ms = 2;
|
|
858
858
|
}
|
|
859
859
|
|
|
860
|
+
message ToolRequestsListedUpdate {
|
|
861
|
+
uint32 call_count = 1;
|
|
862
|
+
}
|
|
863
|
+
|
|
860
864
|
message InteractionUpdate {
|
|
861
865
|
oneof message {
|
|
862
866
|
TextDeltaUpdate text_delta = 1;
|
|
@@ -876,6 +880,7 @@ message InteractionUpdate {
|
|
|
876
880
|
TurnEndedUpdate turn_ended = 14;
|
|
877
881
|
StepStartedUpdate step_started = 16;
|
|
878
882
|
StepCompletedUpdate step_completed = 17;
|
|
883
|
+
ToolRequestsListedUpdate tool_requests_listed = 27;
|
|
879
884
|
}
|
|
880
885
|
}
|
|
881
886
|
|