Merge remote-tracking branch 'origin/main' into pico

This commit is contained in:
Mario Zechner 2026-09-15 17:17:35 +02:00
commit ea7f84678c
5 changed files with 86 additions and 1 deletions

View file

@ -391,3 +391,5 @@ edverma pr
Charlie0113-T pr
RedBeard0531 pr
metaist pr

View file

@ -8,6 +8,7 @@
### Fixed
- Fixed Amazon Bedrock one-hour cache writes being priced at the five-minute rate ([#9457](https://github.com/earendil-works/pi/issues/9457)).
- Added `RetryPolicy.maxAgentDelayMs` support to cap shared assistant retry backoff for summarization calls ([#8826](https://github.com/earendil-works/pi/issues/8826)).
- Fixed quadratic CPU usage when draining buffered `EventStream` events ([#9055](https://github.com/earendil-works/pi/issues/9055)).
- Fixed Mistral Medium reasoning requests to use `reasoning_effort` for all reasoning-capable `mistral-medium-*` model IDs instead of the unsupported `prompt_mode` ([#8700](https://github.com/earendil-works/pi/issues/8700)).

View file

@ -1591,7 +1591,7 @@ Built-in login and refresh flows are private provider implementations. Use provi
Provider notes:
**OpenAI Codex**: Requires a ChatGPT Plus or Pro subscription. Provides access to GPT-5.x Codex models with extended context windows and reasoning capabilities. The library automatically handles session-based prompt caching when `sessionId` is provided in stream options unless `cacheRetention` is `"none"`. You can set `transport` in stream options to `"sse"`, `"websocket"`, or `"auto"` for Codex Responses transport selection. When using WebSocket with a `sessionId` and cache retention enabled, connections are reused per session and expire after 5 minutes of inactivity.
**OpenAI Codex**: Requires a ChatGPT Plus or Pro subscription. Provides access to GPT-5.x Codex models with extended context windows and reasoning capabilities. The library automatically handles session-based prompt caching when `sessionId` is provided in stream options unless `cacheRetention` is `"none"`. You can set `transport` in stream options to `"sse"`, `"websocket"`, or `"auto"` for Codex Responses transport selection. When using WebSocket with a `sessionId` and cache retention enabled, connections are reused per session and expire after 5 minutes of inactivity. Call `cleanupSessionResources(sessionId)` when finished so the pooled connection does not keep the process alive.
**Azure OpenAI (Responses)**: Uses the Responses API only. Set `AZURE_OPENAI_API_KEY` and either `AZURE_OPENAI_BASE_URL` or `AZURE_OPENAI_RESOURCE_NAME`. `AZURE_OPENAI_BASE_URL` supports both `https://<resource>.openai.azure.com` and `https://<resource>.cognitiveservices.azure.com`; root endpoints are normalized to `.../openai/v1` automatically. Use `AZURE_OPENAI_API_VERSION` (defaults to `v1`) to override the API version if needed. Deployment names are treated as model IDs by default, override with `azureDeploymentName` or `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` using comma-separated `model-id=deployment` pairs (for example `gpt-4o-mini=my-deployment,gpt-4o=prod`). Legacy deployment-based URLs are intentionally unsupported.

View file

@ -692,6 +692,10 @@ function handleMetadata(
output.usage.output = event.usage.outputTokens || 0;
output.usage.cacheRead = event.usage.cacheReadInputTokens || 0;
output.usage.cacheWrite = event.usage.cacheWriteInputTokens || 0;
output.usage.cacheWrite1h = event.usage.cacheDetails?.reduce(
(total, detail) => total + (detail.ttl === CacheTTL.ONE_HOUR ? (detail.inputTokens ?? 0) : 0),
0,
);
output.usage.totalTokens = event.usage.totalTokens || output.usage.input + output.usage.output;
calculateCost(model, output.usage);
}

View file

@ -0,0 +1,78 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("@aws-sdk/client-bedrock-runtime", () => {
class BedrockRuntimeServiceException extends Error {}
class BedrockRuntimeClient {
middlewareStack = { add: () => undefined };
async send(): Promise<unknown> {
return {
$metadata: { httpStatusCode: 200 },
stream: (async function* () {
yield { messageStart: { role: "assistant" } };
yield {
metadata: {
usage: {
inputTokens: 100,
outputTokens: 5,
totalTokens: 1_000_105,
cacheWriteInputTokens: 1_000_000,
cacheDetails: [
{ ttl: "1h", inputTokens: 150_000 },
{ ttl: "5m", inputTokens: 600_000 },
{ ttl: "1h", inputTokens: 250_000 },
],
},
},
};
yield { messageStop: { stopReason: "end_turn" } };
})(),
};
}
}
class ConverseStreamCommand {
readonly input: unknown;
constructor(input: unknown) {
this.input = input;
}
}
return {
BedrockRuntimeClient,
BedrockRuntimeServiceException,
ConverseStreamCommand,
StopReason: {
END_TURN: "end_turn",
STOP_SEQUENCE: "stop_sequence",
MAX_TOKENS: "max_tokens",
MODEL_CONTEXT_WINDOW_EXCEEDED: "model_context_window_exceeded",
TOOL_USE: "tool_use",
},
CachePointType: { DEFAULT: "default" },
CacheTTL: { FIVE_MINUTES: "5m", ONE_HOUR: "1h" },
ConversationRole: { ASSISTANT: "assistant", USER: "user" },
ImageFormat: { JPEG: "jpeg", PNG: "png", GIF: "gif", WEBP: "webp" },
ToolResultStatus: { ERROR: "error", SUCCESS: "success" },
};
});
import { stream as streamBedrock } from "../src/api/bedrock-converse-stream.ts";
import { getModel } from "../src/compat.ts";
import type { Context } from "../src/types.ts";
const model = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-8");
const context: Context = { messages: [{ role: "user", content: "hi", timestamp: Date.now() }] };
describe("Bedrock 1h cache write cost", () => {
it("prices the 1h cache details at 2x while preserving the total cache write", async () => {
// Regression test for https://github.com/earendil-works/pi/issues/9457
const result = await streamBedrock(model, context, { cacheRetention: "none" }).result();
expect(result.usage.cacheWrite).toBe(1_000_000);
expect(result.usage.cacheWrite1h).toBe(400_000);
// 600k * 6.25/Mtok + 400k * 10/Mtok = 3.75 + 4.0 = 7.75
expect(result.usage.cost.cacheWrite).toBeCloseTo(7.75, 10);
});
});