Skip to content

Commit 1ce4837

Browse files
committed
fix: patch tAnthropicMessageCreate
1 parent cddecff commit 1ce4837

1 file changed

Lines changed: 74 additions & 17 deletions

File tree

backend/src/api/v1/messages.ts

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import {
1313
} from "@/adapters";
1414
import { getModelsWithProviderBySystemName } from "@/db";
1515
import { apiKeyPlugin, type ApiKey } from "@/plugins/apiKeyPlugin";
16-
import { apiKeyRateLimitPlugin, consumeTokens } from "@/plugins/apiKeyRateLimitPlugin";
16+
import {
17+
apiKeyRateLimitPlugin,
18+
consumeTokens,
19+
} from "@/plugins/apiKeyRateLimitPlugin";
1720
import { rateLimitPlugin } from "@/plugins/rateLimitPlugin";
1821
import {
1922
extractUpstreamHeaders,
@@ -78,11 +81,18 @@ const tAnthropicToolResultBlock = t.Object({
7881
is_error: t.Optional(t.Boolean()),
7982
});
8083

84+
const tAnthropicThinkingBlock = t.Object({
85+
type: t.Literal("thinking"),
86+
thinking: t.String(),
87+
signature: t.String(),
88+
});
89+
8190
const tAnthropicContentBlock = t.Union([
8291
tAnthropicTextBlock,
8392
tAnthropicImageBlock,
8493
tAnthropicToolUseBlock,
8594
tAnthropicToolResultBlock,
95+
tAnthropicThinkingBlock,
8696
]);
8797

8898
// Anthropic tool definition
@@ -241,7 +251,11 @@ async function processNonStreamingResponse(
241251

242252
// Consume tokens for TPM rate limiting (post-flight)
243253
// Only consume if token counts are valid (not -1 which indicates parsing failure)
244-
if (apiKeyRecord && completion.promptTokens > 0 && completion.completionTokens > 0) {
254+
if (
255+
apiKeyRecord &&
256+
completion.promptTokens > 0 &&
257+
completion.completionTokens > 0
258+
) {
245259
const totalTokens = completion.promptTokens + completion.completionTokens;
246260
await consumeTokens(apiKeyRecord.id, apiKeyRecord.tpmLimit, totalTokens);
247261
}
@@ -305,7 +319,10 @@ async function* processStreamingResponse(
305319
id: `msg-cache-${reqIdContext.preCreatedCompletionId}`,
306320
type: "message",
307321
role: "assistant",
308-
content: contentBlocks.length > 0 ? contentBlocks : [{ type: "text", text: "" }],
322+
content:
323+
contentBlocks.length > 0
324+
? contentBlocks
325+
: [{ type: "text", text: "" }],
309326
model: comp.model,
310327
stop_reason: hasToolUse ? "tool_use" : "end_turn",
311328
usage: {
@@ -320,7 +337,14 @@ async function* processStreamingResponse(
320337
: undefined;
321338

322339
// Create streaming context with abort handling
323-
const ctx = new StreamingContext(completion, bearer, apiKeyRecord, begin, signal, streamingReqIdContext);
340+
const ctx = new StreamingContext(
341+
completion,
342+
bearer,
343+
apiKeyRecord,
344+
begin,
345+
signal,
346+
streamingReqIdContext,
347+
);
324348

325349
// Track whether we've logged the client abort (to avoid duplicate logs)
326350
let loggedAbort = false;
@@ -335,7 +359,9 @@ async function* processStreamingResponse(
335359
// Log client disconnect once when first detected
336360
if (clientAborted && !loggedAbort) {
337361
loggedAbort = true;
338-
logger.info("Client disconnected during streaming, continuing to collect upstream data");
362+
logger.info(
363+
"Client disconnected during streaming, continuing to collect upstream data",
364+
);
339365
}
340366

341367
ctx.recordTTFT();
@@ -365,7 +391,10 @@ async function* processStreamingResponse(
365391
chunk.delta.thinking
366392
) {
367393
ctx.thinkingParts.push(chunk.delta.thinking);
368-
} else if (chunk.delta?.type === "input_json_delta" && chunk.delta.partialJson) {
394+
} else if (
395+
chunk.delta?.type === "input_json_delta" &&
396+
chunk.delta.partialJson
397+
) {
369398
// Collect tool call arguments - lookup by index to get tool ID
370399
// Skip if index is missing to avoid data corruption
371400
if (chunk.index !== undefined) {
@@ -434,7 +463,10 @@ async function* processStreamingResponse(
434463
if (!ctx.isSaved()) {
435464
if (ctx.isAborted()) {
436465
// If client aborted and we got an error, still save as aborted with the error info
437-
await ctx.saveCompletion("aborted", `Client disconnected, stream error: ${String(error)}`);
466+
await ctx.saveCompletion(
467+
"aborted",
468+
`Client disconnected, stream error: ${String(error)}`,
469+
);
438470
} else {
439471
await ctx.saveCompletion("failed", String(error));
440472
}
@@ -545,10 +577,15 @@ export const messagesApi = new Elysia({
545577
model: body.model,
546578
modelId: candidates[0]?.model.id,
547579
prompt: {
548-
messages: body.messages.map((m: { role: string; content: unknown }) => ({
549-
role: m.role,
550-
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
551-
})),
580+
messages: body.messages.map(
581+
(m: { role: string; content: unknown }) => ({
582+
role: m.role,
583+
content:
584+
typeof m.content === "string"
585+
? m.content
586+
: JSON.stringify(m.content),
587+
}),
588+
),
552589
extraHeaders,
553590
},
554591
apiFormat,
@@ -619,7 +656,12 @@ export const messagesApi = new Elysia({
619656
extraHeaders,
620657
);
621658

622-
const errorResult = await processFailoverError(result, completion, bearer, "streaming");
659+
const errorResult = await processFailoverError(
660+
result,
661+
completion,
662+
bearer,
663+
"streaming",
664+
);
623665

624666
// Finalize pre-created completion if ReqId was used
625667
await finalizeReqIdOnError(reqIdContext, begin);
@@ -718,7 +760,12 @@ export const messagesApi = new Elysia({
718760
extraHeaders,
719761
);
720762

721-
const errorResult = await processFailoverError(result, completion, bearer, "non-streaming");
763+
const errorResult = await processFailoverError(
764+
result,
765+
completion,
766+
bearer,
767+
"non-streaming",
768+
);
722769

723770
// Finalize pre-created completion if ReqId was used
724771
await finalizeReqIdOnError(reqIdContext, begin);
@@ -782,7 +829,8 @@ export const messagesApi = new Elysia({
782829
return JSON.parse(response) as Record<string, unknown>;
783830
} catch (error) {
784831
// Handle error based on whether client aborted
785-
const errorMsg = error instanceof Error ? error.message : String(error);
832+
const errorMsg =
833+
error instanceof Error ? error.message : String(error);
786834
// Only save if completion wasn't already saved in processNonStreamingResponse
787835
const alreadySaved = completion.status !== "pending";
788836
if (request.signal.aborted) {
@@ -799,7 +847,10 @@ export const messagesApi = new Elysia({
799847
},
800848
});
801849
} catch (logError: unknown) {
802-
logger.error("Failed to log aborted completion after processing error", logError);
850+
logger.error(
851+
"Failed to log aborted completion after processing error",
852+
logError,
853+
);
803854
}
804855
}
805856
// Return nothing for aborted requests
@@ -818,13 +869,19 @@ export const messagesApi = new Elysia({
818869
},
819870
});
820871
} catch (logError: unknown) {
821-
logger.error("Failed to log completion after processing error", logError);
872+
logger.error(
873+
"Failed to log completion after processing error",
874+
logError,
875+
);
822876
}
823877
}
824878
set.status = 500;
825879
return {
826880
type: "error",
827-
error: { type: "api_error", message: "Failed to process response" },
881+
error: {
882+
type: "api_error",
883+
message: "Failed to process response",
884+
},
828885
};
829886
}
830887
}

0 commit comments

Comments
 (0)