@@ -703,25 +703,27 @@ export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = (
|
|||||||
}
|
}
|
||||||
// Only update usage fields if present (not null).
|
// Only update usage fields if present (not null).
|
||||||
// Preserves input_tokens from message_start when proxies omit it in message_delta.
|
// Preserves input_tokens from message_start when proxies omit it in message_delta.
|
||||||
if (event.usage.input_tokens != null) {
|
if (event.usage) {
|
||||||
output.usage.input = event.usage.input_tokens;
|
if (event.usage.input_tokens != null) {
|
||||||
}
|
output.usage.input = event.usage.input_tokens;
|
||||||
if (event.usage.output_tokens != null) {
|
}
|
||||||
output.usage.output = event.usage.output_tokens;
|
if (event.usage.output_tokens != null) {
|
||||||
}
|
output.usage.output = event.usage.output_tokens;
|
||||||
if (event.usage.cache_read_input_tokens != null) {
|
}
|
||||||
output.usage.cacheRead = event.usage.cache_read_input_tokens;
|
if (event.usage.cache_read_input_tokens != null) {
|
||||||
}
|
output.usage.cacheRead = event.usage.cache_read_input_tokens;
|
||||||
if (event.usage.cache_creation_input_tokens != null) {
|
}
|
||||||
output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
|
if (event.usage.cache_creation_input_tokens != null) {
|
||||||
}
|
output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
|
||||||
// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the
|
}
|
||||||
// final message_delta usage (a subset of output_tokens). SDK 0.91.1 omits the field from
|
// Anthropic reports reasoning tokens in `output_tokens_details.thinking_tokens` on the
|
||||||
// its Usage type, so read it through a narrow cast. Verified against the live API.
|
// final message_delta usage (a subset of output_tokens). SDK 0.91.1 omits the field from
|
||||||
const thinkingTokens = (event.usage as { output_tokens_details?: { thinking_tokens?: number } })
|
// its Usage type, so read it through a narrow cast. Verified against the live API.
|
||||||
.output_tokens_details?.thinking_tokens;
|
const thinkingTokens = (event.usage as { output_tokens_details?: { thinking_tokens?: number } })
|
||||||
if (thinkingTokens != null) {
|
.output_tokens_details?.thinking_tokens;
|
||||||
output.usage.reasoning = thinkingTokens;
|
if (thinkingTokens != null) {
|
||||||
|
output.usage.reasoning = thinkingTokens;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Anthropic doesn't provide total_tokens, compute from components
|
// Anthropic doesn't provide total_tokens, compute from components
|
||||||
output.usage.totalTokens =
|
output.usage.totalTokens =
|
||||||
|
|||||||
@@ -224,6 +224,34 @@ describe("Anthropic raw SSE parsing", () => {
|
|||||||
expect(result.errorMessage).toBe(explanation);
|
expect(result.errorMessage).toBe(explanation);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("treats message_delta without usage as a no-op for usage accumulation", async () => {
|
||||||
|
const model = getModel("anthropic", "claude-haiku-4-5");
|
||||||
|
const context: Context = {
|
||||||
|
messages: [{ role: "user", content: "Say hello.", timestamp: Date.now() }],
|
||||||
|
};
|
||||||
|
const response = createSseResponse(
|
||||||
|
minimalAnthropicEvents.map((event) =>
|
||||||
|
event.event === "message_delta"
|
||||||
|
? {
|
||||||
|
event: "message_delta",
|
||||||
|
data: JSON.stringify({ type: "message_delta", delta: { stop_reason: "end_turn" } }),
|
||||||
|
}
|
||||||
|
: event,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const stream = streamAnthropic(model, context, {
|
||||||
|
client: createFakeAnthropicClient(response),
|
||||||
|
});
|
||||||
|
const result = await stream.result();
|
||||||
|
|
||||||
|
expect(result.stopReason).toBe("stop");
|
||||||
|
expect(result.errorMessage).toBeUndefined();
|
||||||
|
expect(result.content).toEqual([{ type: "text", text: "Hello" }]);
|
||||||
|
expect(result.usage.input).toBe(12);
|
||||||
|
expect(result.usage.totalTokens).toBe(12);
|
||||||
|
});
|
||||||
|
|
||||||
it("ignores unknown SSE events after message_stop", async () => {
|
it("ignores unknown SSE events after message_stop", async () => {
|
||||||
const model = getModel("anthropic", "claude-haiku-4-5");
|
const model = getModel("anthropic", "claude-haiku-4-5");
|
||||||
const context: Context = {
|
const context: Context = {
|
||||||
|
|||||||
Reference in New Issue
Block a user