66} from "~/lib/config"
77import {
88 type ResponsesPayload ,
9+ type ResponseInputCompaction ,
910 type ResponseInputContent ,
1011 type ResponseInputImage ,
1112 type ResponseInputItem ,
@@ -14,6 +15,7 @@ import {
1415 type ResponseInputText ,
1516 type ResponsesResult ,
1617 type ResponseOutputContentBlock ,
18+ type ResponseOutputCompaction ,
1719 type ResponseOutputFunctionCall ,
1820 type ResponseOutputItem ,
1921 type ResponseOutputReasoning ,
@@ -44,6 +46,8 @@ import {
4446} from "./anthropic-types"
4547
4648const MESSAGE_TYPE = "message"
49+ const COMPACTION_SIGNATURE_PREFIX = "cm1#"
50+ const COMPACTION_SIGNATURE_SEPARATOR = "@"
4751
4852export const THINKING_TEXT = "Thinking..."
4953
@@ -89,6 +93,44 @@ export const translateAnthropicMessagesToResponsesPayload = (
8993 return responsesPayload
9094}
9195
96+ type CompactionCarrier = {
97+ id : string
98+ encrypted_content : string
99+ }
100+
101+ export const encodeCompactionCarrierSignature = (
102+ compaction : CompactionCarrier ,
103+ ) : string => {
104+ return `${ COMPACTION_SIGNATURE_PREFIX } ${ compaction . encrypted_content } ${ COMPACTION_SIGNATURE_SEPARATOR } ${ compaction . id } `
105+ }
106+
107+ export const decodeCompactionCarrierSignature = (
108+ signature : string ,
109+ ) : CompactionCarrier | undefined => {
110+ if ( signature . startsWith ( COMPACTION_SIGNATURE_PREFIX ) ) {
111+ const raw = signature . slice ( COMPACTION_SIGNATURE_PREFIX . length )
112+ const separatorIndex = raw . indexOf ( COMPACTION_SIGNATURE_SEPARATOR )
113+
114+ if ( separatorIndex <= 0 || separatorIndex === raw . length - 1 ) {
115+ return undefined
116+ }
117+
118+ const encrypted_content = raw . slice ( 0 , separatorIndex )
119+ const id = raw . slice ( separatorIndex + 1 )
120+
121+ if ( ! encrypted_content ) {
122+ return undefined
123+ }
124+
125+ return {
126+ id,
127+ encrypted_content,
128+ }
129+ }
130+
131+ return undefined
132+ }
133+
92134const translateMessage = (
93135 message : AnthropicMessage ,
94136 model : string ,
@@ -165,17 +207,25 @@ const translateAssistantMessage = (
165207 continue
166208 }
167209
168- if (
169- block . type === "thinking"
170- && block . signature
171- && block . signature . includes ( "@" )
172- ) {
173- flushPendingContent ( pendingContent , items , {
174- role : "assistant" ,
175- phase : assistantPhase ,
176- } )
177- items . push ( createReasoningContent ( block ) )
178- continue
210+ if ( block . type === "thinking" && block . signature ) {
211+ const compactionContent = createCompactionContent ( block )
212+ if ( compactionContent ) {
213+ flushPendingContent ( pendingContent , items , {
214+ role : "assistant" ,
215+ phase : assistantPhase ,
216+ } )
217+ items . push ( compactionContent )
218+ continue
219+ }
220+
221+ if ( block . signature . includes ( "@" ) ) {
222+ flushPendingContent ( pendingContent , items , {
223+ role : "assistant" ,
224+ phase : assistantPhase ,
225+ } )
226+ items . push ( createReasoningContent ( block ) )
227+ continue
228+ }
179229 }
180230
181231 const converted = translateAssistantContentBlock ( block )
@@ -302,15 +352,43 @@ const createReasoningContent = (
302352 // align with vscode-copilot-chat extractThinkingData, should add id, otherwise it will cause miss cache occasionally —— the usage input cached tokens to be 0
303353 // https://github.com/microsoft/vscode-copilot-chat/blob/main/src/platform/endpoint/node/responsesApi.ts#L162
304354 // when use in codex cli, reasoning id is empty, so it will cause miss cache occasionally
305- const array = block . signature . split ( "@" )
306- const signature = array [ 0 ]
307- const id = array [ 1 ]
355+ const { encryptedContent, id } = parseReasoningSignature ( block . signature )
308356 const thinking = block . thinking === THINKING_TEXT ? "" : block . thinking
309357 return {
310358 id,
311359 type : "reasoning" ,
312360 summary : thinking ? [ { type : "summary_text" , text : thinking } ] : [ ] ,
313- encrypted_content : signature ,
361+ encrypted_content : encryptedContent ,
362+ }
363+ }
364+
365+ const createCompactionContent = (
366+ block : AnthropicThinkingBlock ,
367+ ) : ResponseInputCompaction | undefined => {
368+ const compaction = decodeCompactionCarrierSignature ( block . signature )
369+ if ( ! compaction ) {
370+ return undefined
371+ }
372+
373+ return {
374+ id : compaction . id ,
375+ type : "compaction" ,
376+ encrypted_content : compaction . encrypted_content ,
377+ }
378+ }
379+
380+ const parseReasoningSignature = (
381+ signature : string ,
382+ ) : { encryptedContent : string ; id : string } => {
383+ const splitIndex = signature . lastIndexOf ( "@" )
384+
385+ if ( splitIndex <= 0 || splitIndex === signature . length - 1 ) {
386+ return { encryptedContent : signature , id : "" }
387+ }
388+
389+ return {
390+ encryptedContent : signature . slice ( 0 , splitIndex ) ,
391+ id : signature . slice ( splitIndex + 1 ) ,
314392 }
315393}
316394
@@ -456,6 +534,13 @@ const mapOutputToAnthropicContent = (
456534 }
457535 break
458536 }
537+ case "compaction" : {
538+ const compactionBlock = createCompactionThinkingBlock ( item )
539+ if ( compactionBlock ) {
540+ contentBlocks . push ( compactionBlock )
541+ }
542+ break
543+ }
459544 default : {
460545 // Future compatibility for unrecognized output item types.
461546 const combinedText = combineMessageTextContent (
@@ -549,6 +634,23 @@ const createToolUseContentBlock = (
549634 }
550635}
551636
637+ const createCompactionThinkingBlock = (
638+ item : ResponseOutputCompaction ,
639+ ) : AnthropicAssistantContentBlock | null => {
640+ if ( ! item . id || ! item . encrypted_content ) {
641+ return null
642+ }
643+
644+ return {
645+ type : "thinking" ,
646+ thinking : THINKING_TEXT ,
647+ signature : encodeCompactionCarrierSignature ( {
648+ id : item . id ,
649+ encrypted_content : item . encrypted_content ,
650+ } ) ,
651+ }
652+ }
653+
552654const parseFunctionCallArguments = (
553655 rawArguments : string ,
554656) : Record < string , unknown > => {
0 commit comments