@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed context token estimates to count user image attachments consistently with tool result images ([#4983](https://github.com/earendil-works/pi/issues/4983)).
|
||||
|
||||
## [0.75.5] - 2026-05-23
|
||||
|
||||
## [0.75.4] - 2026-05-20
|
||||
|
||||
@@ -198,22 +198,33 @@ export function shouldCompact(contextTokens: number, contextWindow: number, sett
|
||||
return contextTokens > contextWindow - settings.reserveTokens;
|
||||
}
|
||||
|
||||
const ESTIMATED_IMAGE_CHARS = 4800;
|
||||
|
||||
function estimateTextAndImageContentChars(content: string | Array<{ type: string; text?: string }>): number {
|
||||
if (typeof content === "string") {
|
||||
return content.length;
|
||||
}
|
||||
|
||||
let chars = 0;
|
||||
for (const block of content) {
|
||||
if (block.type === "text" && block.text) {
|
||||
chars += block.text.length;
|
||||
} else if (block.type === "image") {
|
||||
chars += ESTIMATED_IMAGE_CHARS;
|
||||
}
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
/** Estimate token count for one message using a conservative character heuristic. */
|
||||
export function estimateTokens(message: AgentMessage): number {
|
||||
let chars = 0;
|
||||
|
||||
switch (message.role) {
|
||||
case "user": {
|
||||
const content = (message as { content: string | Array<{ type: string; text?: string }> }).content;
|
||||
if (typeof content === "string") {
|
||||
chars = content.length;
|
||||
} else if (Array.isArray(content)) {
|
||||
for (const block of content) {
|
||||
if (block.type === "text" && block.text) {
|
||||
chars += block.text.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
chars = estimateTextAndImageContentChars(
|
||||
(message as { content: string | Array<{ type: string; text?: string }> }).content,
|
||||
);
|
||||
return Math.ceil(chars / 4);
|
||||
}
|
||||
case "assistant": {
|
||||
@@ -231,18 +242,7 @@ export function estimateTokens(message: AgentMessage): number {
|
||||
}
|
||||
case "custom":
|
||||
case "toolResult": {
|
||||
if (typeof message.content === "string") {
|
||||
chars = message.content.length;
|
||||
} else {
|
||||
for (const block of message.content) {
|
||||
if (block.type === "text" && block.text) {
|
||||
chars += block.text.length;
|
||||
}
|
||||
if (block.type === "image") {
|
||||
chars += 4800;
|
||||
}
|
||||
}
|
||||
}
|
||||
chars = estimateTextAndImageContentChars(message.content);
|
||||
return Math.ceil(chars / 4);
|
||||
}
|
||||
case "bashExecution": {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed context token estimates to count user image attachments consistently with tool result images ([#4983](https://github.com/earendil-works/pi/issues/4983)).
|
||||
- Fixed `RpcClient` to reject pending requests and consume stdin pipe errors when the child process exits unexpectedly ([#4764](https://github.com/earendil-works/pi/issues/4764)).
|
||||
- Fixed managed npm extension updates to avoid package managers installing or resolving pi host packages as peer dependencies ([#4907](https://github.com/earendil-works/pi/issues/4907)).
|
||||
- Fixed RPC mode raw stdout writes to retry transient backpressure errors and flush queued protocol output during shutdown ([#4897](https://github.com/earendil-works/pi/issues/4897)).
|
||||
|
||||
@@ -225,6 +225,24 @@ export function shouldCompact(contextTokens: number, contextWindow: number, sett
|
||||
// Cut point detection
|
||||
// ============================================================================
|
||||
|
||||
const ESTIMATED_IMAGE_CHARS = 4800;
|
||||
|
||||
function estimateTextAndImageContentChars(content: string | Array<{ type: string; text?: string }>): number {
|
||||
if (typeof content === "string") {
|
||||
return content.length;
|
||||
}
|
||||
|
||||
let chars = 0;
|
||||
for (const block of content) {
|
||||
if (block.type === "text" && block.text) {
|
||||
chars += block.text.length;
|
||||
} else if (block.type === "image") {
|
||||
chars += ESTIMATED_IMAGE_CHARS;
|
||||
}
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate token count for a message using chars/4 heuristic.
|
||||
* This is conservative (overestimates tokens).
|
||||
@@ -234,16 +252,9 @@ export function estimateTokens(message: AgentMessage): number {
|
||||
|
||||
switch (message.role) {
|
||||
case "user": {
|
||||
const content = (message as { content: string | Array<{ type: string; text?: string }> }).content;
|
||||
if (typeof content === "string") {
|
||||
chars = content.length;
|
||||
} else if (Array.isArray(content)) {
|
||||
for (const block of content) {
|
||||
if (block.type === "text" && block.text) {
|
||||
chars += block.text.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
chars = estimateTextAndImageContentChars(
|
||||
(message as { content: string | Array<{ type: string; text?: string }> }).content,
|
||||
);
|
||||
return Math.ceil(chars / 4);
|
||||
}
|
||||
case "assistant": {
|
||||
@@ -261,18 +272,7 @@ export function estimateTokens(message: AgentMessage): number {
|
||||
}
|
||||
case "custom":
|
||||
case "toolResult": {
|
||||
if (typeof message.content === "string") {
|
||||
chars = message.content.length;
|
||||
} else {
|
||||
for (const block of message.content) {
|
||||
if (block.type === "text" && block.text) {
|
||||
chars += block.text.length;
|
||||
}
|
||||
if (block.type === "image") {
|
||||
chars += 4800; // Estimate images as 4000 chars, or 1200 tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
chars = estimateTextAndImageContentChars(message.content);
|
||||
return Math.ceil(chars / 4);
|
||||
}
|
||||
case "bashExecution": {
|
||||
|
||||
Reference in New Issue
Block a user