responses_to_anthropic.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. package apicompat
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "strings"
  6. "time"
  7. )
  8. // ---------------------------------------------------------------------------
  9. // Non-streaming: ResponsesResponse → AnthropicResponse
  10. // ---------------------------------------------------------------------------
  11. // ResponsesToAnthropic converts a Responses API response directly into an
  12. // Anthropic Messages response. Reasoning output items are mapped to thinking
  13. // blocks; function_call items become tool_use blocks.
  14. func ResponsesToAnthropic(resp *ResponsesResponse, model string) *AnthropicResponse {
  15. out := &AnthropicResponse{
  16. ID: resp.ID,
  17. Type: "message",
  18. Role: "assistant",
  19. Model: model,
  20. }
  21. var blocks []AnthropicContentBlock
  22. for _, item := range resp.Output {
  23. switch item.Type {
  24. case "reasoning":
  25. summaryText := ""
  26. for _, s := range item.Summary {
  27. if s.Type == "summary_text" && s.Text != "" {
  28. summaryText += s.Text
  29. }
  30. }
  31. // Always surface encrypted_content as thinking.signature so Claude
  32. // Code / multi-turn clients can send it back. Signature-only
  33. // thinking blocks are valid when the model omits a visible summary.
  34. if summaryText != "" || strings.TrimSpace(item.EncryptedContent) != "" {
  35. blocks = append(blocks, AnthropicContentBlock{
  36. Type: "thinking",
  37. Thinking: summaryText,
  38. Signature: item.EncryptedContent,
  39. })
  40. }
  41. case "message":
  42. for _, part := range item.Content {
  43. if part.Type == "output_text" && part.Text != "" {
  44. blocks = append(blocks, AnthropicContentBlock{
  45. Type: "text",
  46. Text: part.Text,
  47. })
  48. }
  49. }
  50. case "function_call":
  51. blocks = append(blocks, AnthropicContentBlock{
  52. Type: "tool_use",
  53. ID: fromResponsesCallID(item.CallID),
  54. Name: item.Name,
  55. Input: sanitizeAnthropicToolUseInput(item.Name, item.Arguments),
  56. })
  57. case "web_search_call":
  58. toolUseID := "srvtoolu_" + item.ID
  59. query := ""
  60. if item.Action != nil {
  61. query = item.Action.Query
  62. }
  63. inputJSON, _ := json.Marshal(map[string]string{"query": query})
  64. blocks = append(blocks, AnthropicContentBlock{
  65. Type: "server_tool_use",
  66. ID: toolUseID,
  67. Name: "web_search",
  68. Input: inputJSON,
  69. })
  70. emptyResults, _ := json.Marshal([]struct{}{})
  71. blocks = append(blocks, AnthropicContentBlock{
  72. Type: "web_search_tool_result",
  73. ToolUseID: toolUseID,
  74. Content: emptyResults,
  75. })
  76. }
  77. }
  78. if len(blocks) == 0 {
  79. blocks = append(blocks, AnthropicContentBlock{Type: "text", Text: ""})
  80. }
  81. out.Content = blocks
  82. out.StopReason = AnthropicStopReasonPtr(responsesStatusToAnthropicStopReason(resp.Status, resp.IncompleteDetails, blocks))
  83. if resp.Usage != nil {
  84. out.Usage = anthropicUsageFromResponsesUsage(resp.Usage)
  85. }
  86. return out
  87. }
  88. func anthropicUsageFromResponsesUsage(usage *ResponsesUsage) AnthropicUsage {
  89. if usage == nil {
  90. return AnthropicUsage{}
  91. }
  92. cachedTokens := 0
  93. if usage.InputTokensDetails != nil {
  94. cachedTokens = usage.InputTokensDetails.CachedTokens
  95. }
  96. inputTokens := usage.InputTokens - cachedTokens - usage.CacheCreationInputTokens
  97. if inputTokens < 0 {
  98. inputTokens = 0
  99. }
  100. return AnthropicUsage{
  101. InputTokens: inputTokens,
  102. OutputTokens: usage.OutputTokens,
  103. CacheReadInputTokens: cachedTokens,
  104. CacheCreationInputTokens: usage.CacheCreationInputTokens,
  105. }
  106. }
  107. func responsesStatusToAnthropicStopReason(status string, details *ResponsesIncompleteDetails, blocks []AnthropicContentBlock) string {
  108. switch status {
  109. case "incomplete":
  110. if details != nil && details.Reason == "max_output_tokens" {
  111. return "max_tokens"
  112. }
  113. return "end_turn"
  114. case "completed":
  115. if containsAnthropicToolUseBlock(blocks) {
  116. return "tool_use"
  117. }
  118. return "end_turn"
  119. default:
  120. return "end_turn"
  121. }
  122. }
  123. func containsAnthropicToolUseBlock(blocks []AnthropicContentBlock) bool {
  124. for _, block := range blocks {
  125. if block.Type == "tool_use" {
  126. return true
  127. }
  128. }
  129. return false
  130. }
  131. func sanitizeAnthropicToolUseInput(name string, raw string) json.RawMessage {
  132. if name != "Read" || raw == "" {
  133. return json.RawMessage(raw)
  134. }
  135. var input map[string]json.RawMessage
  136. if err := json.Unmarshal([]byte(raw), &input); err != nil {
  137. return json.RawMessage(raw)
  138. }
  139. if pages, ok := input["pages"]; !ok || string(pages) != `""` {
  140. return json.RawMessage(raw)
  141. }
  142. delete(input, "pages")
  143. sanitized, err := json.Marshal(input)
  144. if err != nil {
  145. return json.RawMessage(raw)
  146. }
  147. return sanitized
  148. }
  149. // ---------------------------------------------------------------------------
  150. // Streaming: ResponsesStreamEvent → []AnthropicStreamEvent (stateful converter)
  151. // ---------------------------------------------------------------------------
  152. // ResponsesEventToAnthropicState tracks state for converting a sequence of
  153. // Responses SSE events directly into Anthropic SSE events.
  154. type ResponsesEventToAnthropicState struct {
  155. MessageStartSent bool
  156. MessageStopSent bool
  157. ContentBlockIndex int
  158. ContentBlockOpen bool
  159. CurrentBlockType string // "text" | "thinking" | "tool_use"
  160. CurrentToolName string
  161. CurrentToolArgs string
  162. CurrentToolHadDelta bool
  163. // PendingThinkingSignature is filled from reasoning.encrypted_content and
  164. // emitted as signature_delta before the thinking block is closed.
  165. PendingThinkingSignature string
  166. HasToolCall bool
  167. // OutputIndexToBlockIdx maps Responses output_index → Anthropic content block index.
  168. OutputIndexToBlockIdx map[int]int
  169. InputTokens int
  170. OutputTokens int
  171. CacheReadInputTokens int
  172. CacheCreationInputTokens int
  173. ResponseID string
  174. Model string
  175. Created int64
  176. }
  177. // NewResponsesEventToAnthropicState returns an initialised stream state.
  178. func NewResponsesEventToAnthropicState() *ResponsesEventToAnthropicState {
  179. return &ResponsesEventToAnthropicState{
  180. OutputIndexToBlockIdx: make(map[int]int),
  181. Created: time.Now().Unix(),
  182. }
  183. }
  184. // ResponsesEventToAnthropicEvents converts a single Responses SSE event into
  185. // zero or more Anthropic SSE events, updating state as it goes.
  186. func ResponsesEventToAnthropicEvents(
  187. evt *ResponsesStreamEvent,
  188. state *ResponsesEventToAnthropicState,
  189. ) []AnthropicStreamEvent {
  190. switch evt.Type {
  191. case "response.created":
  192. return resToAnthHandleCreated(evt, state)
  193. case "response.output_item.added":
  194. return resToAnthHandleOutputItemAdded(evt, state)
  195. case "response.output_text.delta":
  196. return resToAnthHandleTextDelta(evt, state)
  197. case "response.output_text.done":
  198. return resToAnthHandleBlockDone(state)
  199. case "response.function_call_arguments.delta",
  200. // custom/freeform 工具的输入增量与 function_call 参数增量同形。
  201. "response.custom_tool_call_input.delta":
  202. return resToAnthHandleFuncArgsDelta(evt, state)
  203. case "response.function_call_arguments.done":
  204. return resToAnthHandleFuncArgsDone(evt, state)
  205. case "response.output_item.done":
  206. return resToAnthHandleOutputItemDone(evt, state)
  207. case "response.reasoning_summary_text.delta",
  208. // 原始推理文本增量,与 reasoning summary 一样映射为 thinking。
  209. "response.reasoning_text.delta":
  210. return resToAnthHandleReasoningDelta(evt, state)
  211. case "response.reasoning_summary_text.done":
  212. // Keep the thinking block open until response.output_item.done.
  213. // Grok/Codex attach encrypted_content on the finished reasoning item;
  214. // closing early would drop signature_delta and break multi-turn cache.
  215. return nil
  216. // response.done 是 Realtime/WS 与项目透传路径使用的终止别名;
  217. // 普通 Responses HTTP SSE 的公开终止事件仍以 response.completed 为主。
  218. case "response.completed", "response.done", "response.incomplete", "response.failed":
  219. return resToAnthHandleCompleted(evt, state)
  220. default:
  221. return nil
  222. }
  223. }
  224. // FinalizeResponsesAnthropicStream emits synthetic termination events if the
  225. // stream ended without a proper completion event.
  226. func FinalizeResponsesAnthropicStream(state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  227. if !state.MessageStartSent || state.MessageStopSent {
  228. return nil
  229. }
  230. var events []AnthropicStreamEvent
  231. events = append(events, closeCurrentBlock(state)...)
  232. stopReason := "end_turn"
  233. if state.HasToolCall {
  234. stopReason = "tool_use"
  235. }
  236. events = append(events,
  237. AnthropicStreamEvent{
  238. Type: "message_delta",
  239. Delta: &AnthropicDelta{
  240. StopReason: stopReason,
  241. },
  242. Usage: &AnthropicUsage{
  243. InputTokens: state.InputTokens,
  244. OutputTokens: state.OutputTokens,
  245. CacheReadInputTokens: state.CacheReadInputTokens,
  246. CacheCreationInputTokens: state.CacheCreationInputTokens,
  247. },
  248. },
  249. AnthropicStreamEvent{Type: "message_stop"},
  250. )
  251. state.MessageStopSent = true
  252. return events
  253. }
  254. // ResponsesAnthropicEventToSSE formats an AnthropicStreamEvent as an SSE line pair.
  255. func ResponsesAnthropicEventToSSE(evt AnthropicStreamEvent) (string, error) {
  256. data, err := json.Marshal(evt)
  257. if err != nil {
  258. return "", err
  259. }
  260. return fmt.Sprintf("event: %s\ndata: %s\n\n", evt.Type, data), nil
  261. }
  262. // --- internal handlers ---
  263. func resToAnthHandleCreated(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  264. if evt.Response != nil {
  265. state.ResponseID = evt.Response.ID
  266. // Only use upstream model if no override was set (e.g. originalModel)
  267. if state.Model == "" {
  268. state.Model = evt.Response.Model
  269. }
  270. }
  271. if state.MessageStartSent {
  272. return nil
  273. }
  274. state.MessageStartSent = true
  275. // Official Anthropic message_start uses stop_reason: null and usage with
  276. // input_tokens when known. We leave StopReason nil (JSON null) and usage
  277. // zeros until response.completed; never emit stop_reason:"" which breaks
  278. // strict clients' turn-finalization / session usage accounting.
  279. return []AnthropicStreamEvent{{
  280. Type: "message_start",
  281. Message: &AnthropicResponse{
  282. ID: state.ResponseID,
  283. Type: "message",
  284. Role: "assistant",
  285. Content: []AnthropicContentBlock{},
  286. Model: state.Model,
  287. StopReason: nil,
  288. Usage: AnthropicUsage{
  289. InputTokens: 0,
  290. OutputTokens: 0,
  291. },
  292. },
  293. }}
  294. }
  295. func resToAnthHandleOutputItemAdded(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  296. if evt.Item == nil {
  297. return nil
  298. }
  299. switch evt.Item.Type {
  300. // function_call 与 custom_tool_call(custom/freeform 工具,如新版 apply_patch)
  301. // 同样映射为 Anthropic 的 tool_use 块。
  302. case "function_call", "custom_tool_call":
  303. var events []AnthropicStreamEvent
  304. events = append(events, closeCurrentBlock(state)...)
  305. idx := state.ContentBlockIndex
  306. state.OutputIndexToBlockIdx[evt.OutputIndex] = idx
  307. state.ContentBlockOpen = true
  308. state.CurrentBlockType = "tool_use"
  309. state.CurrentToolName = evt.Item.Name
  310. state.CurrentToolArgs = ""
  311. state.CurrentToolHadDelta = false
  312. state.HasToolCall = true
  313. events = append(events, AnthropicStreamEvent{
  314. Type: "content_block_start",
  315. Index: &idx,
  316. ContentBlock: &AnthropicContentBlock{
  317. Type: "tool_use",
  318. ID: fromResponsesCallID(evt.Item.CallID),
  319. Name: evt.Item.Name,
  320. Input: json.RawMessage("{}"),
  321. },
  322. })
  323. return events
  324. case "reasoning":
  325. var events []AnthropicStreamEvent
  326. events = append(events, closeCurrentBlock(state)...)
  327. idx := state.ContentBlockIndex
  328. state.OutputIndexToBlockIdx[evt.OutputIndex] = idx
  329. state.ContentBlockOpen = true
  330. state.CurrentBlockType = "thinking"
  331. state.PendingThinkingSignature = strings.TrimSpace(evt.Item.EncryptedContent)
  332. events = append(events, AnthropicStreamEvent{
  333. Type: "content_block_start",
  334. Index: &idx,
  335. ContentBlock: &AnthropicContentBlock{
  336. Type: "thinking",
  337. Thinking: "",
  338. },
  339. })
  340. return events
  341. case "message":
  342. return nil
  343. }
  344. return nil
  345. }
  346. func resToAnthHandleTextDelta(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  347. if evt.Delta == "" {
  348. return nil
  349. }
  350. var events []AnthropicStreamEvent
  351. if !state.ContentBlockOpen || state.CurrentBlockType != "text" {
  352. events = append(events, closeCurrentBlock(state)...)
  353. idx := state.ContentBlockIndex
  354. state.ContentBlockOpen = true
  355. state.CurrentBlockType = "text"
  356. events = append(events, AnthropicStreamEvent{
  357. Type: "content_block_start",
  358. Index: &idx,
  359. ContentBlock: &AnthropicContentBlock{
  360. Type: "text",
  361. Text: "",
  362. },
  363. })
  364. }
  365. idx := state.ContentBlockIndex
  366. events = append(events, AnthropicStreamEvent{
  367. Type: "content_block_delta",
  368. Index: &idx,
  369. Delta: &AnthropicDelta{
  370. Type: "text_delta",
  371. Text: evt.Delta,
  372. },
  373. })
  374. return events
  375. }
  376. func resToAnthHandleFuncArgsDelta(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  377. if evt.Delta == "" {
  378. return nil
  379. }
  380. if state.CurrentBlockType == "tool_use" && state.CurrentToolName == "Read" {
  381. state.CurrentToolArgs += evt.Delta
  382. if state.CurrentToolHadDelta || !json.Valid([]byte(state.CurrentToolArgs)) {
  383. return nil
  384. }
  385. blockIdx, ok := state.OutputIndexToBlockIdx[evt.OutputIndex]
  386. if !ok {
  387. return nil
  388. }
  389. state.CurrentToolHadDelta = true
  390. sanitized := sanitizeAnthropicToolUseInput(state.CurrentToolName, state.CurrentToolArgs)
  391. return []AnthropicStreamEvent{{
  392. Type: "content_block_delta",
  393. Index: &blockIdx,
  394. Delta: &AnthropicDelta{
  395. Type: "input_json_delta",
  396. PartialJSON: string(sanitized),
  397. },
  398. }}
  399. }
  400. if state.CurrentBlockType == "tool_use" {
  401. state.CurrentToolHadDelta = true
  402. }
  403. blockIdx, ok := state.OutputIndexToBlockIdx[evt.OutputIndex]
  404. if !ok {
  405. return nil
  406. }
  407. return []AnthropicStreamEvent{{
  408. Type: "content_block_delta",
  409. Index: &blockIdx,
  410. Delta: &AnthropicDelta{
  411. Type: "input_json_delta",
  412. PartialJSON: evt.Delta,
  413. },
  414. }}
  415. }
  416. func resToAnthHandleFuncArgsDone(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  417. if !state.ContentBlockOpen {
  418. return nil
  419. }
  420. if state.CurrentBlockType != "tool_use" {
  421. return resToAnthHandleBlockDone(state)
  422. }
  423. raw := evt.Arguments
  424. if raw == "" {
  425. raw = state.CurrentToolArgs
  426. }
  427. if raw == "" || state.CurrentToolHadDelta {
  428. return closeCurrentBlock(state)
  429. }
  430. if state.CurrentToolName == "Read" {
  431. sanitized := sanitizeAnthropicToolUseInput(state.CurrentToolName, raw)
  432. if len(sanitized) == 0 {
  433. return closeCurrentBlock(state)
  434. }
  435. raw = string(sanitized)
  436. }
  437. // 从事件的 OutputIndex 解析正确的 block index,与 resToAnthHandleFuncArgsDelta 对齐
  438. blockIdx, ok := state.OutputIndexToBlockIdx[evt.OutputIndex]
  439. if !ok {
  440. blockIdx = state.ContentBlockIndex
  441. }
  442. // 如果 block 已关闭(ContentBlockIndex 已越过它),说明 arguments 已通过 delta 流式发完,不再补发
  443. if !state.ContentBlockOpen || blockIdx != state.ContentBlockIndex {
  444. return nil
  445. }
  446. events := []AnthropicStreamEvent{{
  447. Type: "content_block_delta",
  448. Index: &blockIdx,
  449. Delta: &AnthropicDelta{
  450. Type: "input_json_delta",
  451. PartialJSON: raw,
  452. },
  453. }}
  454. events = append(events, closeCurrentBlock(state)...)
  455. return events
  456. }
  457. func resToAnthHandleReasoningDelta(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  458. if evt.Delta == "" {
  459. return nil
  460. }
  461. blockIdx, ok := state.OutputIndexToBlockIdx[evt.OutputIndex]
  462. if !ok {
  463. return nil
  464. }
  465. return []AnthropicStreamEvent{{
  466. Type: "content_block_delta",
  467. Index: &blockIdx,
  468. Delta: &AnthropicDelta{
  469. Type: "thinking_delta",
  470. Thinking: evt.Delta,
  471. },
  472. }}
  473. }
  474. func resToAnthHandleBlockDone(state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  475. if !state.ContentBlockOpen {
  476. return nil
  477. }
  478. return closeCurrentBlock(state)
  479. }
  480. func resToAnthHandleOutputItemDone(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  481. if evt.Item == nil {
  482. return nil
  483. }
  484. // Handle web_search_call → synthesize server_tool_use + web_search_tool_result blocks.
  485. if evt.Item.Type == "web_search_call" && evt.Item.Status == "completed" {
  486. return resToAnthHandleWebSearchDone(evt, state)
  487. }
  488. // Capture encrypted_content on reasoning item done (often only present here).
  489. if evt.Item.Type == "reasoning" {
  490. if sig := strings.TrimSpace(evt.Item.EncryptedContent); sig != "" {
  491. state.PendingThinkingSignature = sig
  492. }
  493. }
  494. if state.ContentBlockOpen {
  495. return closeCurrentBlock(state)
  496. }
  497. return nil
  498. }
  499. // resToAnthHandleWebSearchDone converts an OpenAI web_search_call output item
  500. // into Anthropic server_tool_use + web_search_tool_result content block pairs.
  501. // This allows Claude Code to count the searches performed.
  502. func resToAnthHandleWebSearchDone(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  503. var events []AnthropicStreamEvent
  504. events = append(events, closeCurrentBlock(state)...)
  505. toolUseID := "srvtoolu_" + evt.Item.ID
  506. query := ""
  507. if evt.Item.Action != nil {
  508. query = evt.Item.Action.Query
  509. }
  510. inputJSON, _ := json.Marshal(map[string]string{"query": query})
  511. // Emit server_tool_use block (start + stop).
  512. idx1 := state.ContentBlockIndex
  513. events = append(events, AnthropicStreamEvent{
  514. Type: "content_block_start",
  515. Index: &idx1,
  516. ContentBlock: &AnthropicContentBlock{
  517. Type: "server_tool_use",
  518. ID: toolUseID,
  519. Name: "web_search",
  520. Input: inputJSON,
  521. },
  522. })
  523. events = append(events, AnthropicStreamEvent{
  524. Type: "content_block_stop",
  525. Index: &idx1,
  526. })
  527. state.ContentBlockIndex++
  528. // Emit web_search_tool_result block (start + stop).
  529. // Content is empty because OpenAI does not expose individual search results;
  530. // the model consumes them internally and produces text output.
  531. emptyResults, _ := json.Marshal([]struct{}{})
  532. idx2 := state.ContentBlockIndex
  533. events = append(events, AnthropicStreamEvent{
  534. Type: "content_block_start",
  535. Index: &idx2,
  536. ContentBlock: &AnthropicContentBlock{
  537. Type: "web_search_tool_result",
  538. ToolUseID: toolUseID,
  539. Content: emptyResults,
  540. },
  541. })
  542. events = append(events, AnthropicStreamEvent{
  543. Type: "content_block_stop",
  544. Index: &idx2,
  545. })
  546. state.ContentBlockIndex++
  547. return events
  548. }
  549. func resToAnthHandleCompleted(evt *ResponsesStreamEvent, state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  550. if state.MessageStopSent {
  551. return nil
  552. }
  553. var events []AnthropicStreamEvent
  554. events = append(events, closeCurrentBlock(state)...)
  555. stopReason := "end_turn"
  556. if evt.Usage != nil {
  557. usage := anthropicUsageFromResponsesUsage(evt.Usage)
  558. state.InputTokens = usage.InputTokens
  559. state.OutputTokens = usage.OutputTokens
  560. state.CacheReadInputTokens = usage.CacheReadInputTokens
  561. state.CacheCreationInputTokens = usage.CacheCreationInputTokens
  562. }
  563. if evt.Response != nil {
  564. if evt.Response.Usage != nil {
  565. usage := anthropicUsageFromResponsesUsage(evt.Response.Usage)
  566. state.InputTokens = usage.InputTokens
  567. state.OutputTokens = usage.OutputTokens
  568. state.CacheReadInputTokens = usage.CacheReadInputTokens
  569. state.CacheCreationInputTokens = usage.CacheCreationInputTokens
  570. }
  571. switch evt.Response.Status {
  572. case "incomplete":
  573. if evt.Response.IncompleteDetails != nil && evt.Response.IncompleteDetails.Reason == "max_output_tokens" {
  574. stopReason = "max_tokens"
  575. }
  576. case "completed":
  577. if state.HasToolCall {
  578. stopReason = "tool_use"
  579. }
  580. }
  581. }
  582. events = append(events,
  583. AnthropicStreamEvent{
  584. Type: "message_delta",
  585. Delta: &AnthropicDelta{
  586. StopReason: stopReason,
  587. },
  588. Usage: &AnthropicUsage{
  589. InputTokens: state.InputTokens,
  590. OutputTokens: state.OutputTokens,
  591. CacheReadInputTokens: state.CacheReadInputTokens,
  592. CacheCreationInputTokens: state.CacheCreationInputTokens,
  593. },
  594. },
  595. AnthropicStreamEvent{Type: "message_stop"},
  596. )
  597. state.MessageStopSent = true
  598. return events
  599. }
  600. func closeCurrentBlock(state *ResponsesEventToAnthropicState) []AnthropicStreamEvent {
  601. if !state.ContentBlockOpen {
  602. return nil
  603. }
  604. idx := state.ContentBlockIndex
  605. var events []AnthropicStreamEvent
  606. // Emit signature_delta before stop so Claude clients retain encrypted
  607. // reasoning for the next turn (required for Grok multi-turn cache).
  608. if state.CurrentBlockType == "thinking" {
  609. if sig := strings.TrimSpace(state.PendingThinkingSignature); sig != "" {
  610. events = append(events, AnthropicStreamEvent{
  611. Type: "content_block_delta",
  612. Index: &idx,
  613. Delta: &AnthropicDelta{
  614. Type: "signature_delta",
  615. Signature: sig,
  616. },
  617. })
  618. }
  619. state.PendingThinkingSignature = ""
  620. }
  621. state.ContentBlockOpen = false
  622. state.ContentBlockIndex++
  623. state.CurrentToolName = ""
  624. state.CurrentToolArgs = ""
  625. state.CurrentToolHadDelta = false
  626. events = append(events, AnthropicStreamEvent{
  627. Type: "content_block_stop",
  628. Index: &idx,
  629. })
  630. return events
  631. }