@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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.5] - 2026-05-23
|
||||||
|
|
||||||
## [0.75.4] - 2026-05-20
|
## [0.75.4] - 2026-05-20
|
||||||
|
|||||||
@@ -198,22 +198,33 @@ export function shouldCompact(contextTokens: number, contextWindow: number, sett
|
|||||||
return contextTokens > contextWindow - settings.reserveTokens;
|
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. */
|
/** Estimate token count for one message using a conservative character heuristic. */
|
||||||
export function estimateTokens(message: AgentMessage): number {
|
export function estimateTokens(message: AgentMessage): number {
|
||||||
let chars = 0;
|
let chars = 0;
|
||||||
|
|
||||||
switch (message.role) {
|
switch (message.role) {
|
||||||
case "user": {
|
case "user": {
|
||||||
const content = (message as { content: string | Array<{ type: string; text?: string }> }).content;
|
chars = estimateTextAndImageContentChars(
|
||||||
if (typeof content === "string") {
|
(message as { content: string | Array<{ type: string; text?: string }> }).content,
|
||||||
chars = content.length;
|
);
|
||||||
} else if (Array.isArray(content)) {
|
|
||||||
for (const block of content) {
|
|
||||||
if (block.type === "text" && block.text) {
|
|
||||||
chars += block.text.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.ceil(chars / 4);
|
return Math.ceil(chars / 4);
|
||||||
}
|
}
|
||||||
case "assistant": {
|
case "assistant": {
|
||||||
@@ -231,18 +242,7 @@ export function estimateTokens(message: AgentMessage): number {
|
|||||||
}
|
}
|
||||||
case "custom":
|
case "custom":
|
||||||
case "toolResult": {
|
case "toolResult": {
|
||||||
if (typeof message.content === "string") {
|
chars = estimateTextAndImageContentChars(message.content);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.ceil(chars / 4);
|
return Math.ceil(chars / 4);
|
||||||
}
|
}
|
||||||
case "bashExecution": {
|
case "bashExecution": {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### 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 `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 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)).
|
- 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
|
// 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.
|
* Estimate token count for a message using chars/4 heuristic.
|
||||||
* This is conservative (overestimates tokens).
|
* This is conservative (overestimates tokens).
|
||||||
@@ -234,16 +252,9 @@ export function estimateTokens(message: AgentMessage): number {
|
|||||||
|
|
||||||
switch (message.role) {
|
switch (message.role) {
|
||||||
case "user": {
|
case "user": {
|
||||||
const content = (message as { content: string | Array<{ type: string; text?: string }> }).content;
|
chars = estimateTextAndImageContentChars(
|
||||||
if (typeof content === "string") {
|
(message as { content: string | Array<{ type: string; text?: string }> }).content,
|
||||||
chars = content.length;
|
);
|
||||||
} else if (Array.isArray(content)) {
|
|
||||||
for (const block of content) {
|
|
||||||
if (block.type === "text" && block.text) {
|
|
||||||
chars += block.text.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.ceil(chars / 4);
|
return Math.ceil(chars / 4);
|
||||||
}
|
}
|
||||||
case "assistant": {
|
case "assistant": {
|
||||||
@@ -261,18 +272,7 @@ export function estimateTokens(message: AgentMessage): number {
|
|||||||
}
|
}
|
||||||
case "custom":
|
case "custom":
|
||||||
case "toolResult": {
|
case "toolResult": {
|
||||||
if (typeof message.content === "string") {
|
chars = estimateTextAndImageContentChars(message.content);
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Math.ceil(chars / 4);
|
return Math.ceil(chars / 4);
|
||||||
}
|
}
|
||||||
case "bashExecution": {
|
case "bashExecution": {
|
||||||
|
|||||||
Reference in New Issue
Block a user