@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Amazon Bedrock requests to replace blank required user/tool-result text with a placeholder and skip blank replay text blocks ([#4975](https://github.com/earendil-works/pi/issues/4975)).
|
||||||
- Fixed OpenAI GPT-5.5 generated metadata to omit unsupported minimal thinking ([#5243](https://github.com/earendil-works/pi/issues/5243)).
|
- Fixed OpenAI GPT-5.5 generated metadata to omit unsupported minimal thinking ([#5243](https://github.com/earendil-works/pi/issues/5243)).
|
||||||
|
|
||||||
## [0.78.0] - 2026-05-29
|
## [0.78.0] - 2026-05-29
|
||||||
|
|||||||
@@ -394,8 +394,8 @@ export const MODELS = {
|
|||||||
thinkingLevelMap: {"xhigh":"max"},
|
thinkingLevelMap: {"xhigh":"max"},
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 5,
|
input: 5.5,
|
||||||
output: 25,
|
output: 27.5,
|
||||||
cacheRead: 0.5,
|
cacheRead: 0.5,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.25,
|
||||||
},
|
},
|
||||||
@@ -412,10 +412,10 @@ export const MODELS = {
|
|||||||
thinkingLevelMap: {"xhigh":"xhigh"},
|
thinkingLevelMap: {"xhigh":"xhigh"},
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 5,
|
input: 5.5,
|
||||||
output: 25,
|
output: 27.5,
|
||||||
cacheRead: 0.5,
|
cacheRead: 0.55,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.875,
|
||||||
},
|
},
|
||||||
contextWindow: 1000000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
@@ -430,10 +430,10 @@ export const MODELS = {
|
|||||||
thinkingLevelMap: {"xhigh":"xhigh"},
|
thinkingLevelMap: {"xhigh":"xhigh"},
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 5,
|
input: 5.5,
|
||||||
output: 25,
|
output: 27.5,
|
||||||
cacheRead: 0.5,
|
cacheRead: 0.55,
|
||||||
cacheWrite: 6.25,
|
cacheWrite: 6.875,
|
||||||
},
|
},
|
||||||
contextWindow: 1000000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
@@ -447,10 +447,10 @@ export const MODELS = {
|
|||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 3,
|
input: 3.3,
|
||||||
output: 15,
|
output: 16.5,
|
||||||
cacheRead: 0.3,
|
cacheRead: 0.33,
|
||||||
cacheWrite: 3.75,
|
cacheWrite: 4.125,
|
||||||
},
|
},
|
||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
@@ -464,10 +464,10 @@ export const MODELS = {
|
|||||||
reasoning: true,
|
reasoning: true,
|
||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: {
|
cost: {
|
||||||
input: 3,
|
input: 3.3,
|
||||||
output: 15,
|
output: 16.5,
|
||||||
cacheRead: 0.3,
|
cacheRead: 0.33,
|
||||||
cacheWrite: 3.75,
|
cacheWrite: 4.125,
|
||||||
},
|
},
|
||||||
contextWindow: 1000000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 64000,
|
maxTokens: 64000,
|
||||||
@@ -2990,25 +2990,6 @@ export const MODELS = {
|
|||||||
contextWindow: 1000000,
|
contextWindow: 1000000,
|
||||||
maxTokens: 128000,
|
maxTokens: 128000,
|
||||||
} satisfies Model<"anthropic-messages">,
|
} satisfies Model<"anthropic-messages">,
|
||||||
"claude-opus-4-8": {
|
|
||||||
id: "claude-opus-4-8",
|
|
||||||
name: "Claude Opus 4.8",
|
|
||||||
api: "anthropic-messages",
|
|
||||||
provider: "cloudflare-ai-gateway",
|
|
||||||
baseUrl: "https://gateway.ai.cloudflare.com/v1/{CLOUDFLARE_ACCOUNT_ID}/{CLOUDFLARE_GATEWAY_ID}/anthropic",
|
|
||||||
compat: {"forceAdaptiveThinking":true},
|
|
||||||
reasoning: true,
|
|
||||||
thinkingLevelMap: {"xhigh":"xhigh"},
|
|
||||||
input: ["text", "image"],
|
|
||||||
cost: {
|
|
||||||
input: 5,
|
|
||||||
output: 25,
|
|
||||||
cacheRead: 0.5,
|
|
||||||
cacheWrite: 6.25,
|
|
||||||
},
|
|
||||||
contextWindow: 1000000,
|
|
||||||
maxTokens: 128000,
|
|
||||||
} satisfies Model<"anthropic-messages">,
|
|
||||||
"claude-sonnet-4": {
|
"claude-sonnet-4": {
|
||||||
id: "claude-sonnet-4",
|
id: "claude-sonnet-4",
|
||||||
name: "Claude Sonnet 4 (latest)",
|
name: "Claude Sonnet 4 (latest)",
|
||||||
@@ -12066,6 +12047,23 @@ export const MODELS = {
|
|||||||
contextWindow: 32768,
|
contextWindow: 32768,
|
||||||
maxTokens: 32768,
|
maxTokens: 32768,
|
||||||
} satisfies Model<"openai-completions">,
|
} satisfies Model<"openai-completions">,
|
||||||
|
"upstage/solar-pro-3": {
|
||||||
|
id: "upstage/solar-pro-3",
|
||||||
|
name: "Upstage: Solar Pro 3",
|
||||||
|
api: "openai-completions",
|
||||||
|
provider: "openrouter",
|
||||||
|
baseUrl: "https://openrouter.ai/api/v1",
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0.15,
|
||||||
|
output: 0.6,
|
||||||
|
cacheRead: 0.015,
|
||||||
|
cacheWrite: 0,
|
||||||
|
},
|
||||||
|
contextWindow: 128000,
|
||||||
|
maxTokens: 4096,
|
||||||
|
} satisfies Model<"openai-completions">,
|
||||||
"x-ai/grok-4.20": {
|
"x-ai/grok-4.20": {
|
||||||
id: "x-ai/grok-4.20",
|
id: "x-ai/grok-4.20",
|
||||||
name: "xAI: Grok 4.20",
|
name: "xAI: Grok 4.20",
|
||||||
@@ -15131,6 +15129,23 @@ export const MODELS = {
|
|||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxTokens: 8000,
|
maxTokens: 8000,
|
||||||
} satisfies Model<"anthropic-messages">,
|
} satisfies Model<"anthropic-messages">,
|
||||||
|
"stepfun/step-3.5-flash": {
|
||||||
|
id: "stepfun/step-3.5-flash",
|
||||||
|
name: "StepFun 3.5 Flash",
|
||||||
|
api: "anthropic-messages",
|
||||||
|
provider: "vercel-ai-gateway",
|
||||||
|
baseUrl: "https://ai-gateway.vercel.sh",
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: {
|
||||||
|
input: 0.09,
|
||||||
|
output: 0.3,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0.02,
|
||||||
|
},
|
||||||
|
contextWindow: 262114,
|
||||||
|
maxTokens: 262114,
|
||||||
|
} satisfies Model<"anthropic-messages">,
|
||||||
"stepfun/step-3.7-flash": {
|
"stepfun/step-3.7-flash": {
|
||||||
id: "stepfun/step-3.7-flash",
|
id: "stepfun/step-3.7-flash",
|
||||||
name: "Step 3.7 Flash",
|
name: "Step 3.7 Flash",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
type SystemContentBlock,
|
type SystemContentBlock,
|
||||||
type ToolChoice,
|
type ToolChoice,
|
||||||
type ToolConfiguration,
|
type ToolConfiguration,
|
||||||
|
type ToolResultContentBlock,
|
||||||
ToolResultStatus,
|
ToolResultStatus,
|
||||||
} from "@aws-sdk/client-bedrock-runtime";
|
} from "@aws-sdk/client-bedrock-runtime";
|
||||||
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
||||||
@@ -28,6 +29,7 @@ import type {
|
|||||||
AssistantMessage,
|
AssistantMessage,
|
||||||
CacheRetention,
|
CacheRetention,
|
||||||
Context,
|
Context,
|
||||||
|
ImageContent,
|
||||||
Model,
|
Model,
|
||||||
SimpleStreamOptions,
|
SimpleStreamOptions,
|
||||||
StopReason,
|
StopReason,
|
||||||
@@ -86,6 +88,8 @@ export interface BedrockOptions extends StreamOptions {
|
|||||||
|
|
||||||
type Block = (TextContent | ThinkingContent | ToolCall) & { index?: number; partialJson?: string };
|
type Block = (TextContent | ThinkingContent | ToolCall) & { index?: number; partialJson?: string };
|
||||||
|
|
||||||
|
const EMPTY_TEXT_PLACEHOLDER = "<empty>";
|
||||||
|
|
||||||
export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOptions> = (
|
export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOptions> = (
|
||||||
model: Model<"bedrock-converse-stream">,
|
model: Model<"bedrock-converse-stream">,
|
||||||
context: Context,
|
context: Context,
|
||||||
@@ -650,6 +654,29 @@ function normalizeToolCallId(id: string): string {
|
|||||||
return sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
|
return sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createNonBlankTextBlock(text: string): ContentBlock.TextMember | undefined {
|
||||||
|
const sanitized = sanitizeSurrogates(text);
|
||||||
|
return sanitized.trim().length === 0 ? undefined : { text: sanitized };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRequiredTextBlock(text: string): ContentBlock.TextMember {
|
||||||
|
return createNonBlankTextBlock(text) ?? { text: EMPTY_TEXT_PLACEHOLDER };
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToolResultContent(content: (TextContent | ImageContent)[]): ToolResultContentBlock[] {
|
||||||
|
const result: ToolResultContentBlock[] = [];
|
||||||
|
for (const c of content) {
|
||||||
|
if (c.type === "image") {
|
||||||
|
result.push({ image: createImageBlock(c.mimeType, c.data) });
|
||||||
|
} else {
|
||||||
|
const textBlock = createNonBlankTextBlock(c.text);
|
||||||
|
if (textBlock) result.push(textBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.length === 0) result.push({ text: EMPTY_TEXT_PLACEHOLDER });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function convertMessages(
|
function convertMessages(
|
||||||
context: Context,
|
context: Context,
|
||||||
model: Model<"bedrock-converse-stream">,
|
model: Model<"bedrock-converse-stream">,
|
||||||
@@ -665,13 +692,15 @@ function convertMessages(
|
|||||||
case "user": {
|
case "user": {
|
||||||
const content: ContentBlock[] = [];
|
const content: ContentBlock[] = [];
|
||||||
if (typeof m.content === "string") {
|
if (typeof m.content === "string") {
|
||||||
content.push({ text: sanitizeSurrogates(m.content) });
|
content.push(createRequiredTextBlock(m.content));
|
||||||
} else {
|
} else {
|
||||||
for (const c of m.content) {
|
for (const c of m.content) {
|
||||||
switch (c.type) {
|
switch (c.type) {
|
||||||
case "text":
|
case "text": {
|
||||||
content.push({ text: sanitizeSurrogates(c.text) });
|
const textBlock = createNonBlankTextBlock(c.text);
|
||||||
|
if (textBlock) content.push(textBlock);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "image":
|
case "image":
|
||||||
content.push({ image: createImageBlock(c.mimeType, c.data) });
|
content.push({ image: createImageBlock(c.mimeType, c.data) });
|
||||||
break;
|
break;
|
||||||
@@ -679,8 +708,8 @@ function convertMessages(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (content.length === 0) content.push({ text: EMPTY_TEXT_PLACEHOLDER });
|
||||||
}
|
}
|
||||||
if (content.length === 0) continue;
|
|
||||||
result.push({
|
result.push({
|
||||||
role: ConversationRole.USER,
|
role: ConversationRole.USER,
|
||||||
content,
|
content,
|
||||||
@@ -696,19 +725,22 @@ function convertMessages(
|
|||||||
const contentBlocks: ContentBlock[] = [];
|
const contentBlocks: ContentBlock[] = [];
|
||||||
for (const c of m.content) {
|
for (const c of m.content) {
|
||||||
switch (c.type) {
|
switch (c.type) {
|
||||||
case "text":
|
case "text": {
|
||||||
// Skip empty text blocks
|
// Skip empty text blocks
|
||||||
if (c.text.trim().length === 0) continue;
|
const textBlock = createNonBlankTextBlock(c.text);
|
||||||
contentBlocks.push({ text: sanitizeSurrogates(c.text) });
|
if (!textBlock) continue;
|
||||||
|
contentBlocks.push(textBlock);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "toolCall":
|
case "toolCall":
|
||||||
contentBlocks.push({
|
contentBlocks.push({
|
||||||
toolUse: { toolUseId: c.id, name: c.name, input: c.arguments },
|
toolUse: { toolUseId: c.id, name: c.name, input: c.arguments },
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "thinking":
|
case "thinking": {
|
||||||
// Skip empty thinking blocks
|
// Skip empty thinking blocks
|
||||||
if (c.thinking.trim().length === 0) continue;
|
const thinking = sanitizeSurrogates(c.thinking);
|
||||||
|
if (thinking.trim().length === 0) continue;
|
||||||
// Only Anthropic models support the signature field in reasoningText.
|
// Only Anthropic models support the signature field in reasoningText.
|
||||||
// For other models, we omit the signature to avoid errors like:
|
// For other models, we omit the signature to avoid errors like:
|
||||||
// "This model doesn't support the reasoningContent.reasoningText.signature field"
|
// "This model doesn't support the reasoningContent.reasoningText.signature field"
|
||||||
@@ -717,12 +749,12 @@ function convertMessages(
|
|||||||
// persisted message lacks a signature, Bedrock rejects the replayed
|
// persisted message lacks a signature, Bedrock rejects the replayed
|
||||||
// reasoning block. Fall back to plain text, matching Anthropic.
|
// reasoning block. Fall back to plain text, matching Anthropic.
|
||||||
if (!c.thinkingSignature || c.thinkingSignature.trim().length === 0) {
|
if (!c.thinkingSignature || c.thinkingSignature.trim().length === 0) {
|
||||||
contentBlocks.push({ text: sanitizeSurrogates(c.thinking) });
|
contentBlocks.push({ text: thinking });
|
||||||
} else {
|
} else {
|
||||||
contentBlocks.push({
|
contentBlocks.push({
|
||||||
reasoningContent: {
|
reasoningContent: {
|
||||||
reasoningText: {
|
reasoningText: {
|
||||||
text: sanitizeSurrogates(c.thinking),
|
text: thinking,
|
||||||
signature: c.thinkingSignature,
|
signature: c.thinkingSignature,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -731,11 +763,12 @@ function convertMessages(
|
|||||||
} else {
|
} else {
|
||||||
contentBlocks.push({
|
contentBlocks.push({
|
||||||
reasoningContent: {
|
reasoningContent: {
|
||||||
reasoningText: { text: sanitizeSurrogates(c.thinking) },
|
reasoningText: { text: thinking },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -759,11 +792,7 @@ function convertMessages(
|
|||||||
toolResults.push({
|
toolResults.push({
|
||||||
toolResult: {
|
toolResult: {
|
||||||
toolUseId: m.toolCallId,
|
toolUseId: m.toolCallId,
|
||||||
content: m.content.map((c) =>
|
content: convertToolResultContent(m.content),
|
||||||
c.type === "image"
|
|
||||||
? { image: createImageBlock(c.mimeType, c.data) }
|
|
||||||
: { text: sanitizeSurrogates(c.text) },
|
|
||||||
),
|
|
||||||
status: m.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
|
status: m.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -775,11 +804,7 @@ function convertMessages(
|
|||||||
toolResults.push({
|
toolResults.push({
|
||||||
toolResult: {
|
toolResult: {
|
||||||
toolUseId: nextMsg.toolCallId,
|
toolUseId: nextMsg.toolCallId,
|
||||||
content: nextMsg.content.map((c) =>
|
content: convertToolResultContent(nextMsg.content),
|
||||||
c.type === "image"
|
|
||||||
? { image: createImageBlock(c.mimeType, c.data) }
|
|
||||||
: { text: sanitizeSurrogates(c.text) },
|
|
||||||
),
|
|
||||||
status: nextMsg.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
|
status: nextMsg.isError ? ToolResultStatus.ERROR : ToolResultStatus.SUCCESS,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ describe("bedrock convertMessages skips unknown content types", () => {
|
|||||||
expect(p.messages[0].content[0]).toEqual({ text: "hello" });
|
expect(p.messages[0].content[0]).toEqual({ text: "hello" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips user messages with only unknown content blocks", async () => {
|
it("replaces user messages with only unknown content blocks with a placeholder", async () => {
|
||||||
const messages: Message[] = [
|
const messages: Message[] = [
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -128,9 +128,95 @@ describe("bedrock convertMessages skips unknown content types", () => {
|
|||||||
const payload = await capturePayload({ messages });
|
const payload = await capturePayload({ messages });
|
||||||
expect(payload).toBeDefined();
|
expect(payload).toBeDefined();
|
||||||
const p = payload as { messages: Array<{ role: string; content: unknown[] }> };
|
const p = payload as { messages: Array<{ role: string; content: unknown[] }> };
|
||||||
|
expect(p.messages).toHaveLength(1);
|
||||||
|
expect(p.messages[0].content).toEqual([{ text: "<empty>" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces blank user string content with a placeholder", async () => {
|
||||||
|
const payload = await capturePayload({
|
||||||
|
messages: [{ role: "user", content: " ", timestamp: Date.now() }],
|
||||||
|
});
|
||||||
|
expect(payload).toBeDefined();
|
||||||
|
const p = payload as { messages: Array<{ role: string; content: unknown[] }> };
|
||||||
|
expect(p.messages).toHaveLength(1);
|
||||||
|
expect(p.messages[0].content).toEqual([{ text: "<empty>" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters blank user text blocks when other content remains", async () => {
|
||||||
|
const payload = await capturePayload({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: [
|
||||||
|
{ type: "text", text: "" },
|
||||||
|
{ type: "text", text: "hello" },
|
||||||
|
],
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(payload).toBeDefined();
|
||||||
|
const p = payload as { messages: Array<{ role: string; content: unknown[] }> };
|
||||||
|
expect(p.messages).toHaveLength(1);
|
||||||
|
expect(p.messages[0].content).toEqual([{ text: "hello" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaces user content emptied by surrogate sanitization with a placeholder", async () => {
|
||||||
|
const payload = await capturePayload({
|
||||||
|
messages: [{ role: "user", content: String.fromCharCode(0xd83d), timestamp: Date.now() }],
|
||||||
|
});
|
||||||
|
expect(payload).toBeDefined();
|
||||||
|
const p = payload as { messages: Array<{ role: string; content: unknown[] }> };
|
||||||
|
expect(p.messages).toHaveLength(1);
|
||||||
|
expect(p.messages[0].content).toEqual([{ text: "<empty>" }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips assistant text blocks emptied by surrogate sanitization", async () => {
|
||||||
|
const messages: Message[] = [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: String.fromCharCode(0xd83d) }],
|
||||||
|
api: "bedrock-converse-stream",
|
||||||
|
provider: "amazon-bedrock",
|
||||||
|
model: baseModel.id,
|
||||||
|
usage: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||||
|
},
|
||||||
|
stopReason: "stop",
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const payload = await capturePayload({ messages });
|
||||||
|
expect(payload).toBeDefined();
|
||||||
|
const p = payload as { messages: Array<{ role: string; content: unknown[] }> };
|
||||||
expect(p.messages).toHaveLength(0);
|
expect(p.messages).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("replaces blank tool result content with a placeholder", async () => {
|
||||||
|
const messages: Message[] = [
|
||||||
|
{
|
||||||
|
role: "toolResult",
|
||||||
|
toolCallId: "tool-1",
|
||||||
|
toolName: "tool",
|
||||||
|
content: [{ type: "text", text: "" }],
|
||||||
|
isError: false,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const payload = await capturePayload({ messages });
|
||||||
|
expect(payload).toBeDefined();
|
||||||
|
const p = payload as {
|
||||||
|
messages: Array<{ role: string; content: Array<{ toolResult: { content: unknown[] } }> }>;
|
||||||
|
};
|
||||||
|
expect(p.messages).toHaveLength(1);
|
||||||
|
expect(p.messages[0].content[0].toolResult.content).toEqual([{ text: "<empty>" }]);
|
||||||
|
});
|
||||||
|
|
||||||
it("skips assistant messages with only unknown content blocks", async () => {
|
it("skips assistant messages with only unknown content blocks", async () => {
|
||||||
const messages: Message[] = [
|
const messages: Message[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user